Inefficient Use of Sequences

Common Clojure sequence mistakes that create extra work, repeated realization, and avoidable allocation on real workloads.

Inefficient use of sequences usually means asking sequence abstractions to do more work than the problem requires. The code often still looks elegant, which is why this anti-pattern is easy to miss. A pipeline feels “functional,” but under load it may be allocating too much, realizing data repeatedly, or traversing collections in the wrong shape.

The goal is not to avoid sequences. Sequences are one of Clojure’s strongest abstractions. The goal is to understand where sequence convenience turns into hidden cost.

Anti-Pattern: Re-Scanning Data Instead of Re-Shaping It

One of the most expensive habits is repeatedly traversing the same collection when the real need is indexed lookup.

Weak:

1(defn find-order [orders order-id]
2  (first (filter #(= order-id (:id %)) orders)))

That is fine once. It becomes wasteful if called over and over on the same collection.

Often the better move is to change the data shape:

1(defn orders-by-id [orders]
2  (into {} (map (juxt :id identity)) orders))

Then the lookup cost becomes explicit and cheap.

The anti-pattern here is treating every collection problem as a fresh sequence pipeline instead of recognizing when the workload wants an index.

Anti-Pattern: Multiple Lazy Passes on a Hot Path

Sequence pipelines are readable, but each stage may create additional lazy work and allocation pressure.

1(->> orders
2     (filter :active?)
3     (map :total-cents)
4     (map #(* % 1.13))
5     (reduce + 0))

This may be perfectly fine. It becomes suspicious when:

  • it is on a measured hot path
  • the result is realized more than once
  • intermediate work keeps showing up in profiles

At that point, transducers or a tighter reduce may be justified:

1(transduce
2  (comp
3    (filter :active?)
4    (map :total-cents)
5    (map #(* % 1.13)))
6  +
7  0
8  orders)

The anti-pattern is not writing the pipeline. The anti-pattern is keeping the more allocation-heavy version after measurement shows it is the wrong tool for that path.

Anti-Pattern: Using Sequences for Random Access Thinking

Sequences are excellent for stepwise traversal. They are not the right abstraction when the real need is repeated indexed access.

Weak:

1(defn third-item [xs]
2  (nth (seq xs) 2))

If xs is already a vector, nth is fine. But once code starts treating arbitrary seqs like cheap random-access collections, the performance model gets blurry.

If your algorithm expects:

  • repeated nth
  • jumping around by index
  • windowed lookups by position

then a vector or array-backed representation may be the more honest choice.

The anti-pattern is pretending all collection shapes are equally suited to positional work.

Anti-Pattern: Realizing the Same Lazy Work More Than Once

This is one of the most common sequence mistakes:

1(def active-orders
2  (filter :active? orders))
3
4(count active-orders)
5(take 5 active-orders)

If active-orders is lazy and the source is not cached elsewhere in a realized form, you may be recomputing the filtering work more than expected.

In some cases, making the boundary explicit is better:

1(def active-orders
2  (into [] (filter :active? orders)))

The anti-pattern is accidental repeated realization, especially when the code visually reads as if the filtered result were already “there.”

Anti-Pattern: Side Effects in Lazy Pipelines

Lazy sequence functions and side effects are a fragile combination.

Weak:

1(map println orders)

Nothing guarantees when those prints happen, or whether they happen at all, unless something consumes the sequence.

Even if you force realization with doall, the design is often still muddy because transformation and effect execution are mixed together.

Better options include:

  • using run! for side-effecting traversal
  • using doseq when the purpose is iteration with effects
  • separating pure transformation from effectful consumption
1(run! println orders)

The anti-pattern is treating lazy sequence functions as if they were eager iteration constructs.

Anti-Pattern: Holding Sequence Convenience Above Collection Intent

Some code stays sequence-shaped longer than it should because the abstraction is comfortable.

Examples:

  • building vectors through lazy pipelines when a transient-backed accumulation would be clearer on a hot path
  • converting maps to seqs and back repeatedly for keyed operations
  • using list-like traversal patterns when the data is really a table or graph

This is not an argument against sequences. It is a reminder that sequences are one abstraction layer, not the truth of the underlying workload.

Anti-Pattern: Forgetting That distinct, sort, and Friends Have Costs

Some sequence operations imply significant work:

  • distinct needs membership tracking
  • sort realizes and orders the whole input
  • group-by builds a full keyed structure
  • partition and windowing operations may retain more context than expected

These are not bad operations. They become anti-patterns when used casually in performance-sensitive paths without acknowledging what they imply.

For example, sorting at the end of every request pipeline may be far more expensive than maintaining the needed order earlier or using a more appropriate data structure.

A Better Sequence Decision Model

    flowchart TD
	    A["Need collection processing"] --> B{"Single traversal and readable pipeline good enough?"}
	    B -->|Yes| C["Use ordinary sequence functions"]
	    B -->|No| D{"Repeated realization or intermediate allocation showing up?"}
	    D -->|Yes| E["Consider transducers, reduce, or eager boundary"]
	    D -->|No| F{"Need repeated keyed or indexed access?"}
	    F -->|Yes| G["Reshape data structure first"]
	    F -->|No| H["Keep the simpler sequence version"]

This keeps optimization grounded in access pattern rather than style preference.

What to Do Instead

  • profile before rewriting pipelines
  • make realization boundaries explicit when results are reused
  • use run! or doseq for side-effectful traversal
  • prefer vectors, maps, or sets when the access pattern calls for them
  • use transducers when measurement shows intermediate allocations matter

Common Mistakes

  • filtering the same collection repeatedly instead of indexing once
  • assuming a lazy result will behave like a cached concrete collection
  • mixing side effects into map or filter
  • using sequence-style traversal for inherently indexed workloads
  • keeping elegant-looking pipelines after profiling shows they are the bottleneck

Key Takeaways

  • Sequence elegance does not guarantee sequence efficiency.
  • Access pattern matters more than abstraction loyalty.
  • Repeated realization and wrong data shape are common hidden costs.
  • Side effects belong in effect-oriented traversal tools, not lazy pipelines.
  • Reshape the data or use a different collection when the workload demands it.

Ready to Test Your Knowledge?

Loading quiz…
Revised on Thursday, April 23, 2026