Skip to content
The Sequence Abstraction
step 1/7

Reading — step 1 of 7

Learn

~3 min readSequences, Strings, Destructuring

Clojure unifies vectors, lists, maps, sets, and strings under one abstraction: the sequence (seq). Most Clojure functions take and return seqs, regardless of the input collection type. "Clojure for the Brave and True" treats this as the language's central organizing idea.

What's a seq?

An ordered, lazy view over data. Three primitives:

  • (first coll) — first element
  • (rest coll) — everything after first (returns empty seq if no more)
  • (cons x coll) — prepend x

From these, the entire seq library is built.

seq-able types

Most collections become seqs automatically:

(seq [1 2 3])             ;; (1 2 3)  — list-like
(seq {:a 1 :b 2})         ;; ([:a 1] [:b 2])  — pairs
(seq #{1 2 3})            ;; (1 2 3)  — set elements
(seq "hello")             ;; (\h \e \l \l \o)  — chars
(seq [])                  ;; nil  — empty becomes nil

Most seq functions auto-call seq on their input — you rarely call it directly.

Common seq operations

(count [1 2 3])             ;; 3
(first [1 2 3])             ;; 1
(rest [1 2 3])              ;; (2 3)
(last [1 2 3])              ;; 3
(take 2 [1 2 3 4 5])         ;; (1 2)
(drop 2 [1 2 3 4 5])         ;; (3 4 5)
(reverse [1 2 3])            ;; (3 2 1)
(sort [3 1 2])               ;; (1 2 3)
(distinct [1 1 2 3 3])       ;; (1 2 3)
(partition 2 [1 2 3 4 5])    ;; ((1 2) (3 4))
(interleave [1 2 3] [:a :b]) ;; (1 :a 2 :b)
(zipmap [:a :b] [1 2])       ;; {:a 1 :b 2}

These all work on ANY seq-able input — vector, list, map, set, string.

Lazy sequences

Most Clojure seq operations are LAZY — they don't compute results until consumed:

(def big (range 1e9))           ;; doesn't compute a billion numbers
(take 5 (map inc big))           ;; only computes 5 incs
;; => (1 2 3 4 5)

range is infinite-friendly. map, filter, take, drop are all lazy. Only realize-ing operations like count, first, into, reduce force evaluation.

Infinite sequences are normal in Clojure:

(def naturals (iterate inc 1))   ;; 1, 2, 3, ...
(take 10 naturals)               ;; (1 2 3 4 5 6 7 8 9 10)

Realization gotchas

(def items (map prn [1 2 3]))   ;; PRN as a side effect
;; nothing prints — the map is lazy!

(doall items)                    ;; forces realization, prn runs

For side effects, use doall (eager) or dorun (eager, discards results) — or doseq instead of map:

(doseq [x [1 2 3]]
  (prn x))

Sequences vs concrete collections

(map inc [1 2 3])              ;; (2 3 4) — lazy seq, NOT a vector
(into [] (map inc [1 2 3]))    ;; [2 3 4] — vector
(mapv inc [1 2 3])             ;; [2 3 4] — vector, eager

Most seq operations return LISTS (lazy seqs). To get back a specific collection type, use into or the *v variants (mapv, filterv).

Common mistakes

  • Forgetting laziness — side effects in lazy operations don't run unless realized.
  • Holding the head — keeping a reference to the start of a long lazy sequence prevents GC of computed values; can OOM. Pass through; don't retain.
  • Trying to take/drop on infinite seqs without limit(map identity (range)) is fine; (reduce + (range)) runs forever.
  • Expecting collection type to round-trip(map inc vec) returns a seq, not a vector. Use mapv or into if you need the original type.
  • Comparing [] and () — both are sequential but [1 2 3] is a vector, '(1 2 3) is a list. Use = for structural equality (works across types).

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…