Browse Clojure Foundations for Java Developers

Using `map` for Transformation

Learn when `map` is the right tool in Clojure, how it differs from Java loop and stream habits, and how to use it clearly with single and multiple collections.

map is the function you reach for when every input item should produce a corresponding output item.

That “one in, one out” mental model is the simplest reliable way for a Java developer to recognize when map belongs in a Clojure pipeline.

What map Means

map applies a function to each item in a collection and returns a lazy sequence of the results.

1(map inc [1 2 3])
2;; => (2 3 4)

That is the whole contract:

  • take each item
  • transform it
  • preserve the item count

If your operation keeps some items and drops others, that is filter, not map.

If your operation combines many items into one result, that is reduce, not map.

The Fastest Decision Rule

If you need to… Reach for
turn each order into its amount map
keep only paid orders filter
total all order amounts reduce

That distinction matters because many awkward Clojure pipelines come from using map where the code is really selecting or aggregating.

A Realistic Example

Suppose you have order maps and want a vector of invoice lines.

1(defn ->invoice-line [{:order/keys [id customer total]}]
2  {:invoice/order-id id
3   :invoice/customer customer
4   :invoice/total total})
5
6(defn invoice-lines [orders]
7  (->> orders
8       (map ->invoice-line)
9       (into [])))

The value of map here is not brevity. The value is that the transformation is explicit:

  • one order in
  • one invoice line out
  • no mutation required

That clarity is why map is such a core tool.

map Versus Java Habits

Java developers usually meet this shape first through loops or streams.

Java habit Clojure shape
for loop that builds a result list map plus a consumer such as into []
.stream().map(...) (map f coll)
getter-heavy projection keyword functions or small named functions

The important shift is that Clojure does not treat mapping as a special collection API bolted onto one data type. It is an ordinary sequence operation that works across seq-able data.

Keywords And Named Functions Work Well Here

You often do not need a verbose anonymous function.

If the transformation is “extract one field”, a keyword function is usually the cleanest version:

1(map :order/id orders)

If the transformation names real domain behavior, a named function is often clearer:

1(defn normalize-email [customer]
2  (update customer :customer/email clojure.string/lower-case))
3
4(map normalize-email customers)

Use #(...) only when it genuinely keeps the code short and readable.

map With Multiple Collections

map can also walk several collections in parallel.

1(map vector [:customer/a :customer/b] [100M 250M])
2;; => ([:customer/a 100M] [:customer/b 250M])

This works because vector can accept one item from each collection.

That makes map useful for:

  • zipping keys and values together
  • combining parallel inputs into richer records
  • applying multi-argument transformations to aligned data

Laziness Matters

By default, map returns a lazy sequence.

That is usually a benefit:

  • work happens only when needed
  • large pipelines can avoid unnecessary computation

But it also means you should be deliberate when callers need a concrete collection:

1(->> orders
2     (map :order/id)
3     (into []))

Using into [] here says, clearly, that the caller wants a vector result.

Common Early Mistakes

Mistake Why it is wrong Better move
Using map to remove items by returning nil The pipeline still has one output per input Use filter, remove, or keep depending on the intent
Building a huge anonymous mapping function The transformation is doing too much inline Name the function or split the pipeline
Forgetting that map is lazy Side effects or closed resources can behave unexpectedly Realize the result or keep the mapping function pure
Expecting map to aggregate map does not combine values into one result Use reduce for aggregation

That first mistake is especially common. If you want to both transform and drop missing values, keep or a filter + map pipeline usually expresses the idea better than “map to nil and hope.”

A Useful Pipeline Pattern

One of the most common production shapes is:

1(->> orders
2     (filter :order/paid?)
3     (map :order/total)
4     (into []))

The roles stay clean:

  • filter decides which items survive
  • map decides how survivors change

That separation is one of the reasons Clojure pipelines read well when each step does one job.

Knowledge Check

### When is `map` the right tool? - [x] When each input item should produce a corresponding output item - [ ] When items need to be dropped from the collection - [ ] When the whole collection should be combined into one summary value - [ ] When a mutable accumulator is required > **Explanation:** `map` is for one-to-one transformation. If items need to be removed, use filtering; if many items must become one result, use reduction. ### Why is `(map :order/id orders)` idiomatic Clojure? - [x] Keywords can act as functions on maps, so this cleanly extracts one field from each order - [ ] `map` only accepts keywords, not ordinary functions - [ ] It forces `map` to return a vector - [ ] It mutates each map in place > **Explanation:** Keywords are callable on maps, which makes field extraction concise and readable in mapping pipelines. ### What does `map` with multiple collections do? - [x] It calls the mapping function with corresponding items from each collection - [ ] It concatenates the collections before mapping - [ ] It ignores all but the first collection - [ ] It automatically reduces the collections into one value > **Explanation:** `map` can walk collections in parallel, passing one item from each to the supplied function. ### What is a common reason to follow `map` with `into []`? - [x] To realize the lazy sequence into a vector when callers need a concrete collection - [ ] To make `map` behave like `reduce` - [ ] To discard `nil` values automatically - [ ] To force the mapping function to be pure > **Explanation:** `map` returns a lazy sequence. `into []` is a clear way to say "produce a vector here."
Revised on Friday, April 24, 2026