Browse Clojure Foundations for Java Developers

Filtering Collections with `filter`

Learn how `filter` keeps matching items in a Clojure pipeline, how laziness affects it, and how it differs from Java loops and stream filtering.

filter is the function you use when the collection shape stays the same but only some items should survive.

For Java developers, the reliable mental model is:

  • map changes every item
  • filter decides which items stay
  • reduce combines items into one result

If you keep those jobs distinct, collection pipelines become much easier to read.

What filter Does

filter takes:

  • a predicate function
  • a collection

and returns a lazy sequence containing only the items for which the predicate is truthy.

1(filter even? [1 2 3 4 5 6])
2;; => (2 4 6)

The predicate is just the rule for keeping an item.

A Production-Shaped Example

Suppose you want only billable time entries.

1(defn billable? [entry]
2  (and (:time-entry/approved? entry)
3       (not (:time-entry/internal? entry))))
4
5(defn billable-entries [entries]
6  (->> entries
7       (filter billable?)
8       (into [])))

This reads well because the rule has a clear name and the pipeline says exactly what happens.

That is the main design benefit of filter: it turns selection logic into an explicit, reusable rule.

filter Versus Java Habits

Java habit Clojure shape
Loop with if before adding to a result list filter plus a predicate
.stream().filter(...) (filter pred coll)
Boolean-heavy selection buried inside traversal code Named predicate passed into the pipeline

The syntax difference is minor. The more important difference is that Clojure encourages you to make the selection rule a first-class function instead of letting it disappear into loop machinery.

Predicate Quality Matters

filter itself is simple. Most of the code quality lives in the predicate you pass to it.

Good predicates:

  • return a clear truthy/falsy result
  • encode one coherent rule
  • are easy to reuse and test
1(defn overdue? [invoice]
2  (and (= :open (:invoice/status invoice))
3       (> (:invoice/days-late invoice) 30)))

Weak predicates usually try to smuggle too much business logic into one anonymous function.

Laziness Matters Here Too

Like map, filter returns a lazy sequence.

That means:

  • filtering does not necessarily happen immediately
  • work is performed as the result is consumed

This is often efficient, but it matters around resources and side effects.

If you filter lines from a file inside with-open, for example, you usually need to realize the result before the reader closes.

filter, remove, and keep

These are related but not interchangeable.

Function Use it when…
filter You want to keep items that satisfy a predicate
remove You want to drop items that satisfy a predicate
keep You want to transform items and drop nil results

This is a common source of muddy code. If the operation is “transform and maybe keep”, keep may be a better fit than forcing the logic into filter.

Pipeline Pattern: Filter Then Transform

A common and readable shape is:

1(->> invoices
2     (filter overdue?)
3     (map :invoice/id)
4     (into []))

This works well because each step has one job:

  • filter chooses survivors
  • map transforms survivors

Many awkward pipelines come from collapsing both concerns into one step.

Common Early Mistakes

Mistake Why it hurts Better move
Using filter to transform values The predicate should decide keep/drop, not reshape data Use map after filter
Returning non-boolean values without clear intent Truthiness works, but the rule becomes harder to read Prefer predicates that make the selection rule obvious
Forgetting laziness around resources Evaluation may happen later than you expect Realize the sequence when needed
Embedding a large anonymous predicate in the pipeline The business rule becomes harder to review Name the predicate

Knowledge Check

### What is the main job of `filter` in a Clojure pipeline? - [x] Keep only the items whose predicate result is truthy - [ ] Transform every item into a new value - [ ] Combine the whole collection into one result - [ ] Sort items into a new order > **Explanation:** `filter` is for selection. It keeps matching items and discards the rest. ### In a clean pipeline, what is usually the next step after `filter` if you want to reshape the surviving items? - [x] `map` - [ ] Another `reduce` - [ ] `assoc` - [ ] `recur` > **Explanation:** `filter` chooses which items stay, and `map` is then a natural next step when you want to transform the survivors. ### When might `keep` be a better fit than `filter`? - [x] When the operation both transforms items and drops `nil` results - [ ] When you need a mutable result list - [ ] When every input must become one output - [ ] When you need to aggregate into a single value > **Explanation:** `keep` is useful for "transform and maybe keep" behavior. `filter` is a better fit when the logic is purely about selection. ### Why can `filter` surprise people around files or other resources? - [x] It is lazy, so evaluation may happen after the resource scope has ended - [ ] It always forces the whole collection eagerly - [ ] It mutates the source collection in place - [ ] It only works on vectors > **Explanation:** Because `filter` is lazy, the actual traversal can happen later than expected. Around short-lived resources, you may need to realize the results explicitly.
Revised on Friday, April 24, 2026