Browse Learn Clojure Foundations as a Java Developer

Bindings and State in Clojure

Replace Java-style reassignment with immutable local bindings, shadowing where useful, and explicit reference types when identity must change over time.

In Clojure, a local binding does not change value. That is the default. Instead of reassigning, you compute a new value and bind it (often in a new scope).

1(let [x 1
2      x (inc x)]
3  x)  ;; => 2

That second x is not “mutating x”. It is introducing a new binding that shadows the old one.

Where does state live then?

When you truly need changing state, Clojure makes it explicit: you use references like atoms, refs, or agents (covered later). This keeps most code pure while still letting programs model reality.

Java mental model: locals are effectively final, and state is pushed into dedicated concurrency-safe containers instead of being scattered across objects.

In this section

  • Avoiding Reassignment in Clojure
    Model work as successive immutable values instead of reassigning locals, and learn why this makes Clojure code easier to review and test.
  • Local Bindings with let
    Use let to name intermediate values, destructure inputs, and keep calculations local without leaking temporary state into the namespace.
  • Managing State with Atoms
    Use atoms when one in-process identity must change over time, and keep each update function pure so state stays explicit and reviewable.
Revised on Saturday, May 23, 2026