Browse Clojure Foundations for Java Developers

Using `def` for Definitions

Learn what `def` actually creates, when it belongs in a namespace, and why it is not the same as a mutable local variable.

def is one of the first forms Java developers overinterpret in Clojure.

It does not mean “ordinary variable assignment” in the Java sense.

What it actually does is create or update a Var in the current namespace and, when you provide an initial value, set that var’s root binding.

1(def answer 42)
2answer
3;; => 42
4
5#'answer
6;; => #'current-ns/answer

That distinction matters because a var is a named slot in the namespace, while the value stored there can be any immutable value or function.

The Mental Model To Use

For day-to-day reading, this table is the safest model.

Form What it creates Typical use
def A namespace var Top-level named values, REPL definitions, special cases
defn A var whose value is a function Ordinary named functions in a namespace
let Local bindings Temporary names inside an expression
fn A function value Anonymous functions or function literals

Java analogy:

  • let is closer to a local variable name
  • def is closer to introducing a namespace-level name
  • defn is the normal way to publish a function from that namespace

That is better than thinking “Clojure variables are all globals.”

When def Is The Right Tool

Use def at the top level of a namespace when you want a stable name for something readers should be able to refer to directly.

Common examples:

1(def default-timeout-ms 5000)
2
3(def currency-scale 2)
4
5(def supported-statuses
6  #{:draft :submitted :paid :cancelled})

These are ordinary top-level definitions. They are easy to discover, easy to inspect at the REPL, and easy to reuse from other functions.

def is also normal during interactive work:

1(def sample-order
2  {:order/id 1001
3   :status   :draft})

That REPL-friendly workflow is one reason vars exist in the first place. You can define and redefine named values while exploring code.

What def Is Not

def is not the form you use for routine step-by-step computation inside functions.

This is the wrong instinct:

1(defn total-with-tax [amount]
2  (def tax-rate 0.13M)   ;; bad style
3  (* amount (+ 1 tax-rate)))

The function works, but tax-rate is now a namespace var, not a local name. That means the function leaked a global definition as a side effect.

The right tool is let:

1(defn total-with-tax [amount]
2  (let [tax-rate 0.13M]
3    (* amount (+ 1 tax-rate))))

That keeps the temporary binding local to the function body.

Values Stay Immutable, But Vars Can Be Redefined

This is the subtle part that trips up many Java engineers.

The value 42 is immutable. The map stored in a var is immutable. The function stored in a var is immutable as a value.

But the var itself can later be redefined:

1(def tax-rate 0.13M)
2(def tax-rate 0.15M)

That is not in-place mutation of a local variable. It is rebinding the root of the namespace var to a new value.

In production code, you usually want such redefinition to be intentional and rare. At the REPL, it is normal and useful.

Practical Guidance For Java Teams

Use def for:

  • top-level named values in a namespace
  • REPL exploration values
  • occasionally, top-level function definitions built another way

Do not use def for:

  • local scratch names inside a function
  • carrying state across steps in a computation
  • modeling ordinary application mutation

If you are thinking “I need to change this as the function runs,” you probably want either:

  • a new local binding with let, or
  • an explicit state reference such as an atom at the boundary

Knowledge Check

### What does `def` create in ordinary Clojure code? - [x] A var in the current namespace, optionally with a root value - [ ] A mutable local variable - [ ] A Java field - [ ] A new namespace > **Explanation:** `def` works at namespace scope. It creates or locates a var and can assign its root binding to a value. ### Why is using `def` inside a function usually a bad idea? - [x] Because it creates or changes a namespace-level var instead of a local binding - [ ] Because `def` is only valid in Java interop code - [ ] Because `def` cannot store numbers - [ ] Because it automatically makes the function recursive > **Explanation:** The usual problem is not syntax but scope. `def` leaks a global-style definition where a local name should have been used. ### Which form is the better choice for a temporary name inside a calculation? - [x] `let` - [ ] `def` - [ ] `ns` - [ ] `var` > **Explanation:** `let` introduces local bindings for ordinary expression-level work. ### What is the correct way to think about redefining a name with `def` at the REPL? - [x] You are changing the root value of a namespace var, not mutating a local variable in place - [ ] You are editing JVM bytecode directly - [ ] You are changing every local binding named the same thing - [ ] You are forcing Clojure values to become mutable > **Explanation:** The important distinction is between immutable values and a var that can point to a new root value later.
Revised on Friday, April 24, 2026