Use Clojure sets for uniqueness, membership checks, set algebra, deduplication, and Java Set comparisons while preserving immutable value semantics.
Sets represent membership and uniqueness. If the question is “is this value present?” or “what values are unique?”, a set is usually the clearest Clojure collection.
1(def roles #{:admin :editor :viewer})
Like vectors and maps, Clojure sets are immutable persistent values. Adding or removing a member returns a new set.
1(def ids #{101 102 103})
2
3(conj ids 104)
4;; => #{101 102 103 104}
5
6(conj ids 102)
7;; => #{101 102 103}
8
9(disj ids 102)
10;; => #{101 103}
11
12ids
13;; => #{101 102 103}
Adding a duplicate does not change the set. Removing a missing element is safe and returns an equivalent set.
Use contains? for membership:
1(contains? roles :admin)
2;; => true
3
4(contains? roles :guest)
5;; => false
Sets can also be invoked as functions:
1(roles :editor)
2;; => :editor
Use contains? when you need a boolean answer, especially if the set may contain nil or false.
Java Set habit |
Clojure set form | Difference |
|---|---|---|
set.add(x) |
(conj s x) |
Returns a new set |
set.remove(x) |
(disj s x) |
Returns a new set |
set.contains(x) |
(contains? s x) |
Value-oriented membership check |
retainAll |
clojure.set/intersection |
Returns a new set |
addAll |
clojure.set/union |
Returns a new set |
removeAll |
clojure.set/difference |
Returns a new set |
Java sets are mutable objects by default. Clojure sets are values by default.
Require clojure.set:
1(require '[clojure.set :as set])
Then use ordinary set operations:
1(def backend #{:java :clojure :sql})
2(def data-team #{:clojure :python :sql})
3
4(set/union backend data-team)
5;; => #{:java :clojure :sql :python}
6
7(set/intersection backend data-team)
8;; => #{:clojure :sql}
9
10(set/difference backend data-team)
11;; => #{:java}
Use set to remove duplicates when order is not important:
1(set [1 1 2 3 3])
2;; => #{1 2 3}
If order matters, use distinct:
1(distinct [1 1 2 3 3])
2;; => (1 2 3)
Choose deliberately. Sets do not promise insertion order.
Sets work well as predicates in filters:
1(def allowed-statuses #{:draft :submitted :paid})
2
3(filter allowed-statuses [:draft :archived :paid])
4;; => (:draft :paid)
That works because a set returns the member when present and nil when absent. Again, use contains? when the result must be a strict boolean or when members could be falsey.
| Use a set when… | Use something else when… |
|---|---|
| You need uniqueness | You need stable order |
| Membership is the main operation | Position/index matters |
| You are comparing groups | You need key-value data |
| You want a predicate of allowed values | Values may be nil or false and boolean precision matters |
(set xs) and (distinct xs) for a vector with duplicates.set/intersection to find shared permissions between two users.(contains? #{nil} nil) is clearer than calling the set as a function.conj and disj return new sets.contains? for boolean membership checks.clojure.set for union, intersection, and difference.distinct when order preservation matters.