Browse Clojure Foundations for Java Developers

Vars and Bindings

What vars and bindings are in Clojure, how `def`, `let`, and `binding` differ, and where Java developers usually get confused.

Var and binding are two terms Java developers often mix together early in Clojure. The confusion is understandable because Java uses one word—“variable”—for several ideas that Clojure keeps more separate.

The short version is:

  • a var is a named reference stored in a namespace
  • a binding is a name-to-value association in some scope

That distinction matters because it helps you read Clojure code correctly.

What a var is

When you write def, you usually create a var in the current namespace.

1(def tax-rate 0.13)
2
3(defn total-with-tax [subtotal]
4  (* subtotal (+ 1 tax-rate)))

Here, tax-rate and total-with-tax are vars in the namespace. A var is not just a raw value. It is a named slot that can hold or point to a value.

That is why vars support behaviors such as metadata, dynamic rebinding, and interactive redefinition in the REPL.

Why vars are useful

Vars are one reason REPL-driven development works so well in Clojure. You can redefine a function var while the program is running and then call the new version immediately.

For Java developers, that feels very different from the normal compile-run cycle.

What a binding is

A binding is the association between a name and a value in a given scope.

The most common example is let.

1(defn invoice-total [subtotal discount]
2  (let [discounted (- subtotal discount)
3        tax (* discounted 0.13)]
4    (+ discounted tax)))

Inside the let, discounted and tax are local bindings. They are not namespace vars. They exist only within that lexical scope.

That is the important split:

  • def creates namespace-level vars
  • let creates local bindings

Do not think of let names as mini mutable variables

A common Java habit is to read local names as values that might be reassigned later. In Clojure, let bindings are normally fixed for that scope.

You create a new binding when you need a new name or a nested scope. You do not usually “update” a let binding in place.

That pushes code toward clearer data flow:

  • compute a value
  • bind a name to it
  • pass it on or return it

Dynamic vars are a special case

Some vars are marked dynamic and can be temporarily rebound with binding.

1(def ^:dynamic *currency* "USD")
2
3(defn format-price [amount]
4  (str *currency* " " amount))
5
6(binding [*currency* "CAD"]
7  (format-price 10))
8;; => "CAD 10"

This does not mutate the global var permanently. It creates a temporary dynamic binding for the duration of that scope.

That is useful for context-like values such as logging settings, print behavior, or request-scoped configuration, but it should be used deliberately.

Java comparison that actually helps

A rough comparison is:

  • Clojure def gives you a namespace-level named reference, somewhat like a named static slot, but more dynamic
  • Clojure let gives you lexical local bindings, closer to local names in an expression-oriented language
  • Clojure binding temporarily rebinds a dynamic var for a scope

The important warning is that none of these are exactly the same as a mutable Java local variable or field.

Common beginner mistakes

Confusing vars with values

If you see (def x 10), the var is x; the value currently held by that var is 10. Keeping that distinction in mind makes REPL behavior easier to understand.

Using dynamic vars for normal application state

Dynamic vars are for scoped context, not as a default way to manage changing business data.

Overusing globals

Just because def is easy does not mean every shared name should be global. Prefer passing values through functions unless a namespace-level var truly makes sense.

A practical rule

When reading code, ask two questions:

  1. is this name a namespace var or a local binding?
  2. is the value lexically fixed, or is this a deliberate dynamic rebinding case?

Those two questions remove a lot of early confusion.

Knowledge Check

### What does `def` usually create in Clojure? - [x] A var in the current namespace - [ ] A mutable local variable inside the nearest function - [ ] A Java field on a generated class - [ ] A temporary thread-local automatically > **Explanation:** `def` normally creates a namespace-level var. That is different from creating a local binding with `let`. ### What is the usual role of `let`? - [x] To create local lexical bindings for a limited scope - [ ] To redefine global vars permanently - [ ] To mark functions as dynamic - [ ] To import another namespace > **Explanation:** `let` is for local names inside an expression or function body. Those bindings are scoped and normally fixed within that block. ### When is `binding` appropriate? - [x] When you need a temporary rebinding of a dynamic var for a specific scope - [ ] When you want to update every local binding in a function - [ ] When you want all application state to be global - [ ] When you need to replace `let` in normal code > **Explanation:** `binding` is a special tool for dynamic vars. It is useful for scoped context, not as the default state-management approach.
Revised on Friday, April 24, 2026