Clojure Cheat Sheet
Overview
Clojure is a dynamic, general-purpose programming language that combines the approachability of a scripting language with a robust infrastructure for multithreaded programming. As a dialect of Lisp, it features a powerful macro system, but adds immutable persistent data structures, software transactional memory, and first-class functions. Running on the JVM, Clojure provides seamless Java interop while encouraging a fundamentally different approach to state management and program design.
Created by Rich Hickey in 2007, Clojure was designed to be a practical language for professional use. It emphasizes simplicity over complexity, immutability by default, and data-oriented programming. Clojure’s REPL-driven development workflow enables rapid iteration, and its persistent data structures provide efficient immutable collections. The language also targets JavaScript (ClojureScript) and .NET (Clojure CLR), making it truly cross-platform.
Installation
Using the CLI Tools
# macOS
brew install clojure/tools/clojure
# Linux
curl -L -O https://github.com/clojure/brew-install/releases/latest/download/linux-install.sh
chmod +x linux-install.sh
sudo ./linux-install.sh
# Verify
clj --version
Using Leiningen
# macOS
brew install leiningen
# Linux/macOS manual
curl -O https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein
chmod +x lein && sudo mv lein /usr/local/bin/
# Verify
lein version
Start a REPL
# Clojure CLI
clj
# Leiningen
lein repl
# With dependencies
clj -Sdeps '{:deps {cheshire/cheshire {:mvn/version "5.12.0"}}}'
Core Language
Data Types
| Type | Example | Description |
|---|---|---|
| Number | 42, 3.14, 22/7 | Integers, floats, ratios |
| String | "hello" | Java strings |
| Character | \a, \newline | Java characters |
| Keyword | :name, :user/id | Self-evaluating identifiers |
| Symbol | foo, my-ns/bar | Names for vars |
| Boolean | true, false | Java booleans |
| Nil | nil | Null/nothing |
| Regex | #"pattern" | Java regex pattern |
| List | '(1 2 3) | Linked list |
| Vector | [1 2 3] | Indexed collection |
| Map | {:a 1 :b 2} | Key-value pairs |
| Set | #{1 2 3} | Unique values |
Functions
;; Define a function
(defn greet [name]
(str "Hello, " name "!"))
;; Multi-arity
(defn greet
([] (greet "World"))
([name] (str "Hello, " name "!"))
([first last] (str "Hello, " first " " last "!")))
;; Variadic
(defn sum [& numbers]
(reduce + 0 numbers))
;; Anonymous functions
(fn [x] (* x 2))
#(* % 2) ; shorthand
#(+ %1 %2) ; multiple args
;; Higher-order functions
(map inc [1 2 3]) ; => (2 3 4)
(filter even? (range 10)) ; => (0 2 4 6 8)
(reduce + [1 2 3 4]) ; => 10
(apply str ["a" "b" "c"]) ; => "abc"
;; Composition
(def process (comp str/upper-case str/trim))
(process " hello ") ; => "HELLO"
;; Partial application
(def add5 (partial + 5))
(add5 3) ; => 8
Collections
;; Vectors
(def v [1 2 3 4 5])
(conj v 6) ; => [1 2 3 4 5 6]
(nth v 2) ; => 3
(get v 2) ; => 3
(subvec v 1 3) ; => [2 3]
(assoc v 0 99) ; => [99 2 3 4 5]
;; Maps
(def m {:name "Alice" :age 30})
(:name m) ; => "Alice"
(get m :name) ; => "Alice"
(assoc m :email "a@b.com")
(dissoc m :age)
(merge m {:role :admin})
(select-keys m [:name])
(update m :age inc)
;; Sets
(def s #{1 2 3})
(conj s 4) ; => #{1 2 3 4}
(disj s 2) ; => #{1 3}
(contains? s 2) ; => true
(clojure.set/union #{1 2} #{2 3}) ; => #{1 2 3}
(clojure.set/intersection #{1 2} #{2 3}) ; => #{2}
;; Sequences (lazy)
(take 5 (range)) ; => (0 1 2 3 4)
(take 5 (iterate inc 0)) ; => (0 1 2 3 4)
(take 5 (repeat :x)) ; => (:x :x :x :x :x)
(take 5 (cycle [1 2 3])) ; => (1 2 3 1 2)
Destructuring
;; Vector destructuring
(let [[a b & rest] [1 2 3 4 5]]
{:a a :b b :rest rest})
; => {:a 1 :b 2 :rest (3 4 5)}
;; Map destructuring
(let [{:keys [name age] :or {age 0}} {:name "Alice" :age 30}]
(str name " is " age))
; => "Alice is 30"
;; Nested destructuring
(let [{:keys [name] {:keys [city]} :address} person]
(str name " lives in " city))
;; Function parameter destructuring
(defn process-user [{:keys [name email]}]
(str "User: " name " <" email ">"))
Control Flow
;; if/when
(if (> x 0) "positive" "non-positive")
(when (> x 0) (println "positive") x)
;; cond
(cond
(< x 0) "negative"
(= x 0) "zero"
:else "positive")
;; case
(case day
:monday "Start of week"
:friday "Almost weekend"
"Regular day")
;; Threading macros
(->> (range 10)
(filter even?)
(map #(* % %))
(reduce +))
(-> {:name "Alice"}
(assoc :role :admin)
(update :name str/upper-case))
;; some-> (nil-safe threading)
(some-> user :address :city str/upper-case)
Concurrency
;; Atoms (uncoordinated, synchronous)
(def counter (atom 0))
(swap! counter inc)
(reset! counter 0)
@counter ; => 0
;; Refs (coordinated, synchronous via STM)
(def account-a (ref 100))
(def account-b (ref 200))
(dosync
(alter account-a - 50)
(alter account-b + 50))
;; Agents (uncoordinated, asynchronous)
(def logger (agent []))
(send logger conj "Event occurred")
@logger ; => ["Event occurred"]
;; Futures and Promises
(def result (future (Thread/sleep 1000) 42))
@result ; => 42 (blocks until ready)
(def p (promise))
(deliver p 42)
@p ; => 42
;; core.async channels
(require '[clojure.core.async :as async])
(let [ch (async/chan 10)]
(async/go (async/>! ch "hello"))
(async/go (println (async/<! ch))))
Build Tools
deps.edn (Clojure CLI)
{:paths ["src" "resources"]
:deps {org.clojure/clojure {:mvn/version "1.11.2"}
cheshire/cheshire {:mvn/version "5.12.0"}
ring/ring-core {:mvn/version "1.11.0"}}
:aliases
{:dev {:extra-paths ["dev"]
:extra-deps {nrepl/nrepl {:mvn/version "1.1.0"}}}
:test {:extra-paths ["test"]
:extra-deps {lambdaisland/kaocha {:mvn/version "1.87.1366"}}}
:build {:deps {io.github.clojure/tools.build {:mvn/version "0.9.6"}}
:ns-default build}}}
project.clj (Leiningen)
(defproject my-app "0.1.0"
:description "My Clojure application"
:dependencies [[org.clojure/clojure "1.11.2"]
[ring/ring-core "1.11.0"]
[compojure "1.7.1"]]
:main my-app.core
:profiles {:dev {:dependencies [[nrepl "1.1.0"]]}
:uberjar {:aot :all}})
Configuration
System Configuration Pattern
;; Using Integrant or Component
(def config
{:db/pool {:jdbc-url (System/getenv "DATABASE_URL")
:max-pool-size 10}
:http/server {:port (Integer/parseInt (or (System/getenv "PORT") "3000"))
:handler (ig/ref :app/handler)}})
Advanced Usage
Macros
(defmacro unless [condition & body]
`(when (not ~condition)
~@body))
(defmacro with-timing [label & body]
`(let [start# (System/nanoTime)
result# (do ~@body)
elapsed# (/ (- (System/nanoTime) start#) 1e6)]
(println (str ~label ": " elapsed# "ms"))
result#))
(with-timing "Query"
(fetch-data db query))
Transducers
;; Composable algorithmic transformations
(def xf (comp
(filter even?)
(map #(* % 3))
(take 5)))
(transduce xf + (range 100))
(into [] xf (range 100))
(sequence xf (range 100))
Spec for Validation
(require '[clojure.spec.alpha :as s])
(s/def ::name (s/and string? #(> (count %) 0)))
(s/def ::age (s/and int? #(> % 0)))
(s/def ::email (s/and string? #(re-matches #".+@.+" %)))
(s/def ::user (s/keys :req-un [::name ::age ::email]))
(s/valid? ::user {:name "Alice" :age 30 :email "a@b.com"}) ; => true
(s/explain ::user {:name "" :age -1}) ; prints explanation
Troubleshooting
| Problem | Solution |
|---|---|
ClassNotFoundException | Check deps; run clj -P to download |
NullPointerException | Use some?, nil? checks; avoid Java null interop issues |
ArityException | Check function argument count; look for missing parens |
| Slow startup | Use clj -J-Dclojure.spec.skip-macros=true; consider GraalVM native |
| REPL not connecting | Check nREPL port; ensure cider-nrepl middleware is loaded |
| Stack overflow | Convert recursion to loop/recur or trampoline |
| Memory issues | Check for head retention in lazy sequences |
| Reflection warnings | Add type hints: ^String, ^long; use *warn-on-reflection* |