Anatomy of a Clojure file
Clojure is a dialect of Lisp, hosted on the JVM, emphasizing functional programming and immutable data structures.
File extensions: .clj, .cljs
Every part of the example below is labelled and explained. This page is one of 55 annotated tours on AnatomyOf, a free, open-source project by LunarWerx Studios.
What is inside a Clojure file
Namespace (ns)
Defines the module and its dependencies.
The ns macro declares a namespace and, in the same breath, pulls in everything the file needs: :require loads other Clojure namespaces (optionally aliased with :as), :import brings in Java classes, and :refer pulls specific symbols into the current namespace so they can be used unqualified. A namespace usually maps one-to-one with a file path (my-app.core lives at my_app/core.clj, underscored per JVM classloader rules). Because the JVM is the host, namespaces are ultimately just Java packages/classes under the hood, but you rarely think about that when writing idiomatic Clojure. Convention kee
Docstring
Documentation for a namespace or function.
A string literal placed right after the name in ns, def, or defn becomes that var's docstring, retrievable at the REPL with (doc my-fn) or by inspecting the var's metadata map under the :doc key. Multi-line docstrings are just regular Clojure strings, so newlines are written literally between the quotes. Docstrings are metadata, not comments. They are compiled into the running program and available for tooling like clojure.repl/doc, cider, or generated API docs, whereas a ; comment is stripped before the reader ever sees it.
Comment
Uses ; for line comments; (comment ...) is a read but unevaluated scratch form, not true comment syntax.
A semicolon ; starts a line comment that runs to the end of the line; everything after it is invisible to the reader, the part of Clojure that turns text into data before evaluation even begins. Two or three semicolons are a style convention for emphasis, not a different syntax. The (comment ...) special form is a different beast: it is a real form the reader parses like any other, but it macro-expands to nil and never evaluates its body. That makes it a popular "rich comment block" for stashing scratch expressions you want to eval individually at the REPL without them running when the file lo
Def (variable)
Defines a global var (variable).
def creates a top-level var and binds it to a name in the current namespace. Vars in Clojure are mutable containers by design (def again to change one, or reach for alter-var-root) but idiomatic code treats top-level defs as effectively constant values, favoring immutable data and local bindings for anything that actually changes. Because Clojure has no static types, a def'd value can be any of the built-in persistent data structures (maps, vectors, lists, sets) or a plain scalar; the reader and the data structure literals below do the heavy lifting of describing shape.
Map (data structure)
Key-value pairs in {}.
Curly braces {} construct a persistent hash map: an immutable, structurally-shared collection of key-value pairs. Commas are optional whitespace in Clojure ({:a 1, :b 2} and {:a 1 :b 2} read identically) so they are used only where they help human eyes group pairs. Maps are themselves functions of their keys, so (:env config) and (config :env) both look up the :env entry; combined with keywords as first-class, self-evaluating values, this makes maps Clojure's default answer to "I need a record type."
Vector (data structure)
Ordered collection in [].
Square brackets [] construct a persistent vector: an indexed, ordered collection with near-constant-time access and update via structural sharing, unlike a linked list. Vectors show up constantly outside of "data" too: function argument lists ([name]) and let binding pairs ([greeting (str ...)]) are both vectors. Because vectors are ordered and lists are not treated as code by default, Clojure uses the bracket shape itself as a signal: [1 2 3] is inert data, while (1 2 3) would be read as an attempt to call 1 as a function.
Keyword
Starts with :, a self-evaluating identifier.
A keyword like :env or :wip starts with a colon and evaluates to itself: no lookup, no quoting needed, unlike a symbol. Keywords are interned, so equality checks between them are a cheap identity comparison, which is part of why they are the idiomatic choice for map keys. Double-colon keywords (::foo) are namespace-qualified automatically to the current namespace, which helps avoid key collisions when merging maps from different parts of a program, a common pattern in larger Clojure codebases.
Defn (function)
Defines a named function.
defn is sugar over (def name (fn [...] ...)): it names a function, optionally attaches a docstring, and takes a vector of parameters followed by a body. The last expression in the body is the return value. There is no return keyword, because everything in Clojure is an expression. defn also supports arity overloading (multiple [params] body pairs in one function) and variadic args via &, e.g. [x & rest], letting one function name handle several call shapes.
Let (local binding)
Creates local scope for variables.
let takes a vector of alternating name/value pairs and a body, introducing local bindings that are visible only within that body. Bindings are evaluated left to right, so a later binding in the same vector can refer to an earlier one. [a 1 b (inc a)] is perfectly legal. Like everything else in Clojure, these locals are immutable once bound: there is no reassignment, only shadowing a name in a nested scope or threading a new value through a recursive call with loop/recur.
List / form (expression)
Code to be evaluated: a function or macro call in ().
Parentheses () construct a list, and a list in "code position" is evaluated as a call: the first element is resolved as a function, macro, or special form, and the rest are its arguments. This uniform "operator first" shape is what people mean by Lisp's S-expressions. (str greeting ", " name "!") calls str with three arguments. Because code and data share the same list/vector/map literal syntax, Clojure programs are themselves data structures a program can construct and manipulate before evaluation: the basis for its macro system, though everyday code mostly just enjoys the uniform, deeply-nes
Symbol
An identifier that refers to a var or function.
A symbol like greet or str/capitalize is an identifier that resolves to a var, a local binding, or a special form, depending on context. Symbols can be namespace-qualified with a slash (str/capitalize means "the capitalize var in the str-aliased namespace"), which is how required namespaces are actually used after :require ... :as str. Unlike a keyword, a symbol does not evaluate to itself, evaluating the symbol greet looks up whatever greet is bound to. Quoting a symbol with a leading apostrophe (e.g. 'greet) suppresses that lookup and yields the symbol itself as data, which is central to how
Official Clojure site · All languages on AnatomyOf
The interactive tour needs JavaScript. Enable it to hover a callout and trace it into the code.