Sequential Destructuring
Sequential destructuring binds names to positions within a vector or list using a vector pattern on the left-hand side of a let binding. (let [[a b c] [1 2 3]] (+ a b c)) binds a to 1, b to 2, and c to 3, returning 6. You can skip elements with an underscore placeholder, and you can capture any leftover elements with &, just as in function parameter lists: (let [[first & rest] [1 2 3 4]] rest) binds rest to (2 3 4). If the pattern requests more positions than the collection has, the missing bindings simply become nil rather than throwing an error.
Cricket analogy: Sequential destructuring like [opener & middle-order] on a batting lineup [Rohit Gill Kohli Iyer Pant] is like pulling out Rohit Sharma into a named opener slot while the rest of the order stays grouped together as 'the middle order,' all in one line instead of indexing each position separately.
Associative Destructuring
Associative destructuring pulls values out of maps by key, using a map pattern on the left side of a binding: (let [{name :name lang :lang} {:name "Ada" :lang "Clojure"}] (str name " uses " lang)). Because binding a local with the same name as a keyword is so common, Clojure provides the :keys shorthand: (let [{:keys [name lang]} person] ...) is equivalent but more concise. The :as keyword additionally binds a name to the entire original map, and :or supplies default values for keys that might be missing: (let [{:keys [name lang] :or {lang "unknown"}} person] ...).
Cricket analogy: Using :keys [name average] to pull fields out of a player-stats map is like a commentator glancing at Steve Smith's profile card and immediately noting just the name and average fields needed for commentary, ignoring every other stat on the card.
Nested Destructuring
Destructuring patterns can nest arbitrarily deep, mixing sequential and associative forms to match the actual shape of the data. Given {:player {:name "Ada" :scores [10 20 30]}}, the pattern {{:keys [name scores]} :player} destructures the nested :player map directly, and you could further destructure scores with [first-score & rest-scores] in the same binding form to reach into the vector nested two levels deep, all without any intermediate get-in calls.
Cricket analogy: Nested destructuring on {:match {:innings [{:runs 120} {:runs 95}]}} is like a scorer pulling the first innings' run total directly out of a fully nested World Cup match record in one step, instead of manually drilling down through match, then innings, then runs separately.
Destructuring in Function Parameters
Because function parameter vectors are themselves binding forms, every destructuring pattern that works in let also works directly in defn or fn argument lists. (defn describe-player [{:keys [name team]}] (str name " plays for " team)) destructures its single map argument right in the parameter position, so callers can pass {:name "Ada" :team "Falcons"} without any manual key lookups inside the function body. This is idiomatic in Clojure for functions that accept a single map of named options, since it documents the expected keys directly at the call site.
Cricket analogy: Destructuring directly in a function's parameter list, like [{:keys [name team]}], is like a team-sheet form for the IPL auction that already has 'Name' and 'Team' printed as labeled fields, so whoever fills it out knows exactly what's expected without reading separate instructions.
;; Sequential destructuring
(let [[a b c] [1 2 3]]
(+ a b c)) ;=> 6
(let [[first-item & rest-items] [1 2 3 4]]
rest-items) ;=> (2 3 4)
;; Associative destructuring
(let [{:keys [name lang] :or {lang "unknown"}} {:name "Ada"}]
(str name " uses " lang)) ;=> "Ada uses unknown"
;; Nested destructuring
(let [{{:keys [name scores]} :player} {:player {:name "Ada" :scores [10 20 30]}}]
[name (first scores)]) ;=> ["Ada" 10]
;; Destructuring in function parameters
(defn describe-player [{:keys [name team]}]
(str name " plays for " team))
(describe-player {:name "Ada" :team "Falcons"})
;=> "Ada plays for Falcons"
Destructuring is purely syntactic sugar handled at macro-expansion time by let, defn, fn, and loop — it compiles down to ordinary get/nth calls under the hood, so there's no runtime performance penalty for using it over manual lookups.
Requesting a key or position that doesn't exist in the source data does not throw an error — it silently binds to nil (unless you supply a default with :or). This can hide typos in keyword names, so double-check spelling when a destructured value unexpectedly comes back nil.
- Sequential destructuring binds names to positions in a vector or list, e.g. (let [[a b c] coll] ...).
- The & symbol inside a destructuring pattern collects remaining elements, just as it does in function parameter lists.
- Associative destructuring pulls values out of maps by key using either the explicit {name :key} form or the :keys shorthand.
- :as binds a name to the entire original collection alongside the destructured pieces; :or supplies default values for missing keys.
- Destructuring patterns can nest to match arbitrarily deep data shapes without intermediate get-in calls.
- Because parameter vectors are binding forms, destructuring works directly in defn and fn argument lists, not just let.
- Requesting a missing key or position yields nil rather than an error, unless a default is supplied via :or.
Practice what you learned
1. What does (let [[a b c] [1 2 3]] (+ a b c)) return?
2. What is the shorthand equivalent of {name :name lang :lang}?
3. What does :or provide in a destructuring pattern?
4. What happens if you destructure a key that doesn't exist in the source map and no :or default is given?
5. Where can destructuring patterns be used besides let?
Was this page helpful?
You May Also Like
Functions in Clojure
Learn how to define, call, and compose functions in Clojure, from simple defn forms to anonymous functions, multi-arity definitions, and higher-order functions.
Immutable Data Structures
Explore Clojure's core persistent data structures — vectors, lists, maps, and sets — and understand how immutability and structural sharing make them both safe and efficient.
Sequences in Clojure
Understand Clojure's sequence abstraction — the uniform interface of first, rest, and cons that lets the same functions work across lists, vectors, maps, and lazy, potentially infinite streams.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics