Getting started
Hello world
;; Simple output
(println "Hello, world!")
;; Function definition
(defn greet [name]
(str "Hello, " name "!"))
(greet "Clojure")
;=> "Hello, Clojure!"
Clojure is a functional Lisp dialect for the JVM with immutable data structures and powerful concurrency primitives.
Basic syntax
;; Everything is an expression
(+ 1 2 3) ;=> 6
;; Prefix notation
(* 2 (+ 3 4)) ;=> 14
;; Function call
(println "text") ;=> nil
;; Comments
; Single line comment
Parentheses define S-expressions. The first element is always the function or special form.
Literals
42 ; Integer
3.14 ; Float
22/7 ; Ratio
"hello" ; String
\a ; Character
true, false ; Booleans
nil ; Null
:keyword ; Keyword (like symbol)
Data structures
Lists
'(1 2 3) ; Quoted list (literal)
(list 1 2 3) ; List constructor
(cons 0 '(1 2)) ; Add to front
;=> (0 1 2)
(first '(1 2 3)) ; Get first
;=> 1
(rest '(1 2 3)) ; Get rest
;=> (2 3)
Lists are linked lists. Use ' to prevent evaluation.
Vectors
[1 2 3] ; Vector literal
(vector 1 2 3) ; Vector constructor
(nth [1 2 3] 0) ; Index access
;=> 1
([1 2 3] 0) ; Vector as function
;=> 1
(conj [1 2] 3) ; Add to end
;=> [1 2 3]
Vectors have efficient indexed access. Prefer vectors over lists for most use cases.
Maps
{:a 1 :b 2} ; Map with keyword keys
{"a" 1 "b" 2} ; Map with string keys
(get {:a 1} :a) ; Get value
;=> 1
({:a 1} :a) ; Map as function
;=> 1
(:a {:a 1}) ; Keyword as function
;=> 1
(assoc {:a 1} :b 2) ; Add/update
;=> {:a 1 :b 2}
(dissoc {:a 1} :a) ; Remove
;=> {}
Maps are hash maps. Keywords can be used as functions to look themselves up in maps.
Sets
#{1 2 3} ; Set literal
(set [1 2 2 3]) ; From collection
;=> #{1 2 3}
(conj #{1 2} 3) ; Add element
;=> #{1 2 3}
(disj #{1 2 3} 2) ; Remove element
;=> #{1 3}
(contains? #{1 2} 1) ; Check membership
;=> true
(#{1 2 3} 2) ; Set as function
;=> 2
Functions
Defining functions
;; Named function
(defn add [a b]
(+ a b))
;; Multi-arity
(defn greet
([] (greet "World"))
([name] (str "Hello, " name "!")))
;; Docstring
(defn square
"Returns the square of x"
[x]
(* x x))
;; Anonymous function
(fn [x] (* x x))
;; Shorthand anonymous
#(* % %) ; Single arg
#(+ %1 %2) ; Multiple args
let bindings
(let [x 10
y 20]
(+ x y))
;=> 30
;; Nested bindings
(let [x 10
y (+ x 5)]
y)
;=> 15
;; Destructuring
(let [[a b] [1 2]]
(+ a b))
;=> 3
let creates local bindings. Bindings are evaluated sequentially.
Functional programming
map
(map inc [1 2 3])
;=> (2 3 4)
(map + [1 2 3] [4 5 6])
;=> (5 7 9)
(map #(* % 2) [1 2 3])
;=> (2 4 6)
Apply function to each element. Returns lazy sequence.
filter
(filter even? [1 2 3 4])
;=> (2 4)
(filter #(> % 5) [1 5 10 15])
;=> (10 15)
(remove odd? [1 2 3 4])
;=> (2 4)
Keep elements that satisfy predicate. remove is opposite of filter.
reduce
(reduce + [1 2 3 4])
;=> 10
(reduce + 10 [1 2 3])
;=> 16 ; With initial value
(reduce conj [] [1 2 3])
;=> [1 2 3]
Accumulate values with function. Second form takes initial value.
Other sequence functions
(take 3 [1 2 3 4 5])
;=> (1 2 3)
(drop 2 [1 2 3 4 5])
;=> (3 4 5)
(concat [1 2] [3 4])
;=> (1 2 3 4)
(partition 2 [1 2 3 4])
;=> ((1 2) (3 4))
(interleave [1 2] [:a :b])
;=> (1 :a 2 :b)
Destructuring
Sequential destructuring
;; Vectors
(let [[a b c] [1 2 3]]
[a b c])
;=> [1 2 3]
;; With rest
(let [[head & tail] [1 2 3 4]]
[head tail])
;=> [1 (2 3 4)]
;; With :as
(let [[a b :as all] [1 2 3]]
[a b all])
;=> [1 2 [1 2 3]]
Associative destructuring
;; Maps with keys
(let [{:keys [a b]} {:a 1 :b 2}]
[a b])
;=> [1 2]
;; Rename keys
(let [{x :a y :b} {:a 1 :b 2}]
[x y])
;=> [1 2]
;; With defaults
(let [{:keys [a b] :or {b 10}} {:a 1}]
[a b])
;=> [1 10]
;; With :as
(let [{:keys [a] :as m} {:a 1 :b 2}]
[a m])
;=> [1 {:a 1 :b 2}]
Threading macros
Thread-first (->)
;; Without threading
(first (reverse (rest [1 2 3])))
;=> 2
;; With thread-first
(-> [1 2 3]
rest
reverse
first)
;=> 2
Threads value as first argument. Good for transformations.
Thread-last (->>)
;; Without threading
(reduce + (filter even? (map inc [1 2 3])))
;=> 6
;; With thread-last
(->> [1 2 3]
(map inc)
(filter even?)
(reduce +))
;=> 6
Threads value as last argument. Good for sequence operations.
Other threading
;; Thread-as (as->)
(as-> [1 2 3] $
(map inc $)
(conj $ 0)
(apply + $))
;=> 9
;; Some threading (some->)
(some-> {:a {:b 1}}
:a
:b
inc)
;=> 2
(some-> nil :a :b)
;=> nil ; Short-circuits on nil
Conditionals
if and when
(if (even? 2)
"yes"
"no")
;=> "yes"
;; when (no else clause)
(when (even? 2)
(println "even")
"yes")
;=> "yes"
;; if-let
(if-let [x (get {:a 1} :a)]
(inc x)
0)
;=> 2
;; when-let
(when-let [x (get {:a 1} :a)]
(inc x))
;=> 2
cond and case
(cond
(< x 0) "negative"
(> x 0) "positive"
:else "zero")
(case x
1 "one"
2 "two"
3 "three"
"other")
;; condp (compare with predicate)
(condp = x
1 "one"
2 "two"
"other")
cond evaluates predicates in order. case does constant-time dispatch.
Namespaces
Defining namespaces
(ns my.app.core
(:require [clojure.string :as str]
[clojure.set :refer [union]]))
;; Use required namespaces
(str/upper-case "hello")
;=> "HELLO"
(union #{1 2} #{2 3})
;=> #{1 2 3}
Requiring and importing
;; Require Clojure namespace
(require '[clojure.string :as str])
;; Import Java class
(import 'java.util.Date)
(Date.)
;=> #inst "2026-02-06T..."
;; Use from namespace
(use 'clojure.set) ; ⚠️ Not recommended (pollutes namespace)
Use :require in ns form. Avoid use in production code.
Java interop
Calling methods
;; Instance method
(.toUpperCase "hello")
;=> "HELLO"
;; Static method
(Math/pow 2 3)
;=> 8.0
;; Method with args
(.substring "hello" 1 3)
;=> "el"
Use .methodName for instance methods, Class/method for static.
Accessing fields
;; Instance field
(.x (java.awt.Point. 10 20))
;=> 10
;; Static field
Math/PI
;=> 3.141592653589793
Creating objects
;; Constructor
(new java.util.Date)
;=> #inst "2026-02-06T..."
;; Shorthand
(java.util.Date.)
;=> #inst "2026-02-06T..."
;; doto (builder pattern)
(doto (java.util.HashMap.)
(.put "a" 1)
(.put "b" 2))
;=> {"a" 1 "b" 2}
Concurrency
Atoms
;; Create atom
(def counter (atom 0))
;; Read value
@counter
;=> 0
;; Update
(swap! counter inc)
;=> 1
(reset! counter 0)
;=> 0
Atoms for synchronous, independent state changes.
Refs
;; Create refs
(def account1 (ref 100))
(def account2 (ref 0))
;; Transaction
(dosync
(alter account1 - 50)
(alter account2 + 50))
@account1 ;=> 50
@account2 ;=> 50
Refs for coordinated, synchronous updates (STM).
Agents
;; Create agent
(def logger (agent []))
;; Asynchronous update
(send logger conj "message 1")
(send logger conj "message 2")
@logger
;=> ["message 1" "message 2"]
Agents for asynchronous, independent actions.
Macros
Defining macros
(defmacro unless [test then]
`(if (not ~test)
~then))
(unless false
(println "executed"))
;=> prints "executed"
;; Syntax quote and unquote
(defmacro debug [expr]
`(let [result# ~expr]
(println "Result:" result#)
result#))
Quote forms
'form ; Quote (don't eval)
`form ; Syntax quote (namespace-qualify)
~form ; Unquote (inside syntax quote)
~@form ; Unquote-splicing
;; Example
`(list ~(+ 1 2))
;=> (clojure.core/list 3)
`(list ~@[1 2 3])
;=> (clojure.core/list 1 2 3)
Use # suffix for auto-gensym in macros to avoid variable capture.
REPL workflow
REPL basics
;; Start REPL
; lein repl
; clj
;; Load file
(load-file "src/core.clj")
;; Reload namespace
(require 'my.app.core :reload)
;; Show docs
(doc map)
;; Find by name
(find-doc "map")
;; Show source
(source map)
Useful REPL vars
*1 ; Last result
*2 ; Second-to-last result
*3 ; Third-to-last result
*e ; Last exception
;; Example
(+ 1 2)
;=> 3
(* *1 10)
;=> 30
Common patterns
Immutability
;; All data is immutable
(def v [1 2 3])
(conj v 4)
;=> [1 2 3 4]
v ; Original unchanged
;=> [1 2 3]
;; Use 'update' for nested changes
(update {:a 1} :a inc)
;=> {:a 2}
(update-in {:a {:b 1}} [:a :b] inc)
;=> {:a {:b 2}}
Lazy sequences
;; Infinite sequence
(def nums (iterate inc 0))
(take 5 nums)
;=> (0 1 2 3 4)
;; Lazy map
(def evens (map #(* % 2) (range)))
(take 5 evens)
;=> (0 2 4 6 8)
;; Force evaluation
(doall (map println [1 2 3]))
Many sequence functions return lazy sequences. Use doall or dorun to force evaluation.
Transducers
;; Composable transformations
(def xf
(comp
(map inc)
(filter even?)))
(transduce xf + [1 2 3 4 5])
;=> 12
(into [] xf [1 2 3 4 5])
;=> [2 4 6]
Transducers compose transformations without creating intermediate collections.
Gotchas
Common mistakes
;; ⚠️ Forgetting quote on lists
(1 2 3) ; Error: 1 is not a function
'(1 2 3) ; Correct
;; ⚠️ Using '=' on collections with different types
(= [1 2] '(1 2))
;=> true ; Collections of same elements are equal
;; ⚠️ Lazy sequence side effects
(map println [1 2 3])
;=> Nothing prints! (lazy)
(dorun (map println [1 2 3]))
;=> Prints 1 2 3
;; ⚠️ Nil punning
(if [] "yes" "no")
;=> "yes" ; Empty collections are truthy!
(if nil "yes" "no")
;=> "no" ; Only nil and false are falsy
Performance tips
;; Use transients for building collections
(persistent!
(reduce conj! (transient []) (range 1000)))
;; Prefer 'into' over 'concat + vec'
(into [] coll1 coll2) ; Faster
;; Use 'mapv' for strict (non-lazy) map
(mapv inc [1 2 3])
;=> [1 2 3] ; Vector (strict)
Also see
- Clojure Official - Official documentation
- Clojure Cheatsheet - Official quick reference
- ClojureDocs - Community documentation with examples
- Learn Clojure - Official syntax guide
- Clojure for the Brave and True - Beginner-friendly book