NexusCS

Clojure

Programming
Quick reference for Clojure - a dynamic, functional Lisp dialect on the JVM with immutable data structures and a powerful concurrency model.
featured

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