Understand symbols as Clojure names that resolve to locals, vars, namespace-qualified references, class names, or quoted code data.
For Java developers, the fastest useful mental model is:
A symbol is a name Clojure may resolve in the current context.
That context might be lexical scope, the current namespace, a required namespace alias, or Java interop. Symbols are how Clojure names code.
| Symbol shape | Usually means | Example |
|---|---|---|
| Bare local | A local binding or parameter | user, email, subtotal |
| Bare var | A var from the current namespace or clojure.core |
map, reduce, println |
| Qualified var | A var through a namespace or alias | str/trim, clojure.set/union |
| Class name | A Java class visible to the compiler | String, java.time.Instant |
| Quoted symbol | Symbol data, not name lookup | 'customer/id |
When you see this function:
1(require '[clojure.string :as str])
2
3(defn normalize-user [user]
4 (update user :email str/lower-case))
Read the tokens this way:
| Token | Kind | Why |
|---|---|---|
defn |
symbol | Resolves to the macro that defines a function |
normalize-user |
symbol | Introduces a var name |
user |
symbol | Function parameter and local binding |
update |
symbol | Resolves to clojure.core/update |
:email |
keyword | Literal data label |
str/lower-case |
qualified symbol | Refers to a var through a namespace alias |
The keyword does not resolve. The symbols do.
A symbol often resolves to the nearest relevant binding:
1(def tax-rate 0.13)
2
3(defn total-with-tax [subtotal]
4 (let [tax (* subtotal tax-rate)]
5 (+ subtotal tax)))
Here:
subtotal and tax are local symbols.tax-rate resolves to a var in the current namespace.* and + resolve to vars from clojure.core.Java comparison: this is closer to name lookup across locals, static members, and imports than to a single category such as “variable.”
Namespace-qualified symbols make dependencies visible:
1(ns my.app.users
2 (:require [clojure.string :as str]))
3
4(defn canonical-email [email]
5 (str/lower-case (str/trim email)))
str/lower-case and str/trim are qualified symbols:
| Part | Meaning |
|---|---|
str |
Namespace alias |
lower-case or trim |
Var name inside that namespace |
This is one reason Clojure code often has less import noise than Java. The call site still shows where the function comes from.
Symbols can appear as data when you quote them:
1'customer-id
2;; => customer-id
3
4'(map inc [1 2 3])
5;; => (map inc [1 2 3])
Without the quote, Clojure would try to resolve customer-id. With the quote, the evaluator returns the symbol value.
Quoted symbols matter in:
For normal application data, keywords are usually better labels than quoted symbols.
You can construct symbols:
1(symbol "customer" "id")
2;; => customer/id
This is useful in macro and tooling code. It is rarely the right default for business logic. If you are dynamically constructing symbols to access data, a map with keyword keys is usually simpler.
| If you see… | Read it as… |
|---|---|
| A bare token in function position | A symbol naming a function, macro, or special form |
| A token in a binding vector | A symbol introducing a local name |
alias/name |
A qualified symbol resolved through an alias |
'name |
Symbol data, not a lookup |
:name |
A keyword, not a symbol |
resolve or programmatic symbol construction in normal application code.The healthy default is simple: use symbols for code names and keywords for data labels.