How to spot recurring Clojure design mistakes early, before they harden into the codebase's normal operating style.
An anti-pattern is a recurring solution shape that seems helpful but reliably creates worse code over time. In Clojure, anti-patterns often arise when developers import habits from other ecosystems without adjusting for Clojure’s strengths: plain data, explicit transformation, local reasoning, immutability, and careful use of state.
Recognizing anti-patterns early matters because once they spread through a guide, a codebase, or a team habit, they stop looking like mistakes. They start looking normal.
An anti-pattern is not just a style preference you dislike. It is usually a pattern that:
For example, a large shared mutable root may seem convenient for a while. But if it hides dependencies, complicates tests, and causes unexpected coupling, it has become more than a neutral style choice.
Clojure shares some anti-patterns with other languages, but several show up in Clojure-specific ways:
The key idea is that anti-patterns often emerge from misapplied strengths. The feature is real. The usage model is what goes wrong.
You are often looking at an anti-pattern when code becomes harder in predictable ways:
These are not random annoyances. They are symptoms of design choices that are not paying for themselves.
This chapter explores several families repeatedly because they show up again and again:
The important thing is not memorizing a taxonomy. It is learning to see the same structural mistakes early.
Many anti-patterns become obvious if you ask structural questions before reaching for a feature:
These questions are often more useful than debating whether a particular construct is “idiomatic.”
Another reason recognition matters: anti-patterns are contagious.
One macro-heavy pattern, one giant app-state atom, or one misleading abstraction can teach the next contributor the wrong lesson. Soon the shape repeats not because it is best, but because it appears to be precedent.
That means early correction matters disproportionately. A small cleanup can stop a whole family of mistakes from being copied.
flowchart TD
A["Notice friction or surprise"] --> B["Ask what assumption the code is hiding"]
B --> C["Compare with a simpler Clojure-native approach"]
C --> D{"Does the current pattern still pay for itself?"}
D -->|No| E["Name the anti-pattern and refactor"]
D -->|Yes| F["Keep it, but document why"]
This is useful because anti-pattern recognition is less about doctrine and more about cost accounting.
Once you recognize a likely anti-pattern:
The goal is not purism. The goal is to keep the system easy to understand and evolve.