Browse Clojure Foundations for Java Developers

Scope and Immutability

Distinguish namespace vars from local bindings, and learn where Clojure's immutability guarantee actually applies.

This page exists because the phrase “Clojure is immutable” is true in one sense and misleading in another.

Clojure strongly prefers immutable values and immutable local bindings.

But names in a namespace are usually vars, and vars can be redefined or, when marked dynamic, rebound per thread.

That is the real model you need if you want def, defn, let, and explicit state references to make sense together.

Scope: Namespace Names Versus Local Names

The most useful comparison is short:

Kind of name Created by Scope Normal use
Namespace var def, defn Current namespace, reachable via its qualified name Public API functions, shared top-level values
Local binding let, function parameters Only inside the enclosing expression or function body Intermediate calculation steps

Example:

1(def tax-rate 0.13M)
2
3(defn invoice-total [subtotal]
4  (let [tax (* subtotal tax-rate)]
5    (+ subtotal tax)))

Here:

  • tax-rate is a namespace var
  • subtotal is a parameter binding
  • tax is a local binding

Those names are not equivalent just because they all look like symbols in code.

What Is Immutable Here?

The local bindings are immutable. Once subtotal or tax is bound inside that call, you do not reassign them.

The values are immutable too. A number or map does not change in place.

But the namespace var tax-rate can later be redefined at the REPL or in source:

1(def tax-rate 0.15M)

That does not mean Clojure abandoned immutability. It means a var now refers to a different root value.

This is why saying “def creates an immutable variable” is not precise enough. The value is immutable. The var is a named reference in the namespace.

Why This Design Is Useful

For Java developers, this can feel strange until you connect it to the REPL.

Because functions are stored in vars:

  • you can redefine a function while the program is running
  • you can inspect vars directly
  • you can change top-level definitions during exploration without recompiling a class hierarchy

That dynamic workflow is one of Clojure’s biggest productivity gains, but it only feels safe if most actual business logic still uses immutable data and local bindings.

Do Not Confuse Vars With Application State

Another common migration mistake is to treat top-level vars as the normal place for changing application state.

That usually leads to brittle code because:

  • global-style names become hidden dependencies
  • tests leak into each other
  • concurrency becomes harder to reason about

If state should change during program execution in a controlled way, use an explicit reference type such as an atom or ref instead of repeatedly redefining ordinary vars.

1(def current-orders (atom {}))

That makes the changing identity explicit. It is much clearer than pretending repeated def calls are your state model.

A Practical Rule Set

Use these rules in code review:

  1. def and defn introduce namespace names.
  2. let and function parameters introduce local names.
  3. Local names are not reassigned.
  4. Values are immutable by default.
  5. If a value must change over time, model that through an explicit reference type, not accidental var redefinition.

If you keep those five rules straight, most beginner confusion around scope and immutability disappears.

Knowledge Check

### In `invoice-total`, which name is a local binding rather than a namespace var? - [x] `tax` - [ ] `tax-rate` - [ ] The namespace itself - [ ] None of them > **Explanation:** `tax` is introduced by `let`, so it exists only inside that expression. `tax-rate` is a namespace-level var created by `def`. ### What is the most accurate statement about immutability in this section? - [x] Values and local bindings are immutable by default, while vars can later refer to a new root value - [ ] Every symbol in Clojure is permanently fixed forever - [ ] Only functions are immutable - [ ] Immutability applies only at compile time > **Explanation:** This is the key distinction: immutable values are the norm, but vars are namespace references and can be redefined. ### Why is repeatedly using `def` as application state usually a bad idea? - [x] It hides changing state behind namespace vars instead of making state management explicit - [ ] It makes maps mutable - [ ] It disables the REPL - [ ] It prevents the use of `let` > **Explanation:** Application state is clearer and safer when modeled through explicit reference types like atoms or refs. ### When is redefining a var most normal and useful? - [x] During REPL-driven development and intentional top-level redefinition - [ ] As the main way every function updates intermediate values - [ ] Inside every loop body - [ ] As a substitute for `let` > **Explanation:** Redefinition is a strength of the interactive workflow, not the usual mechanism for local computation.
Revised on Friday, April 24, 2026