What is Clojure?
Clojure is a modern, dynamic, functional Lisp dialect for the JVM. It emphasizes immutability, functional programming, and seamless Java interoperability.
Functional
First-class functions, immutable data structures, and emphasis on pure functions. Data transformation pipelines.
Lisp Dialect
Code as data (homoiconicity). Powerful macro system for language extension and DSL creation.
JVM-Powered
Access to Java libraries, excellent performance, and deployment anywhere the JVM runs.
Concurrent
Built-in concurrency primitives: atoms, refs (STM), agents. Immutability makes concurrency safer.
Basics
Hello World
;; Print to console (println "Hello, world!") ;; Return a value (defn greet [name] (str "Hello, " name "!"))
REPL
;; Start REPL ;; $ clj or lein repl ;; Evaluate expressions user=> (+ 1 2 3) 6 user=> (map inc [1 2 3]) (2 3 4)
Comments
;; Single-line comment (comment ;; This block is ignored but valid code (println "Not executed")) ;; Discard next form (reader macro) #_(println "This is ignored")
Vars & Bindings
;; Define a var (global, dynamic) (def x 42) ;; Define a function (defn square [n] (* n n)) ;; Local bindings (let [a 10 b 20 c (+ a b)] c) ;; 30 ;; Private var (def ^:private secret "hidden")
Data Types
Primitive Types
| Type | Examples | Notes |
|---|---|---|
| Numbers | 42, 3.14, 22/7, 1N, 1M |
Long, Double, Ratio, BigInt, BigDecimal |
| Strings | "hello", "multi\nline" |
Java strings, UTF-16 |
| Characters | \a, \newline, \u0041 |
Java char type |
| Booleans | true, false |
nil and false are falsy |
| Nil | nil |
Represents null/nothing |
| Keywords | :name, :user/id, ::local |
Interned, efficient for keys |
| Symbols | 'foo, 'my-var |
Identifiers, quoted |
Keywords & Symbols
;; Keywords (evaluate to themselves) :name :user/email ;; namespaced keyword ::local ;; auto-resolved to current ns ;; Keywords as functions (map lookup) (:name {:name "Alice"}) ;; "Alice" ;; Symbols (identifiers) 'foo 'my-namespace/my-var
Regular Expressions
;; Regex literal #"\d+" ;; Pattern matching (re-find #"\d+" "abc123") ;; "123" (re-seq #"\w+" "hello world") ;; ("hello" "world") (re-matches #"\d+" "123") ;; "123"
Collections
All core collections are immutable and persistent. Updates return new versions efficiently.
Lists (Linked List)
;; Create (sequential access) '(1 2 3) (list 1 2 3) ;; Add to front (O(1)) (cons 0 '(1 2 3)) ;; (0 1 2 3) ;; Access (first '(1 2 3)) ;; 1 (rest '(1 2 3)) ;; (2 3) (next '(1 2 3)) ;; (2 3) or nil
Vectors (Indexed)
;; Create (random access O(log32 n) ≈ O(1)) [1 2 3] (vector 1 2 3) (vec '(1 2 3)) ;; Add to end (conj [1 2] 3) ;; [1 2 3] ;; Access by index (nth [1 2 3] 0) ;; 1 (get [1 2 3] 1) ;; 2 ([1 2 3] 2) ;; 3 (vectors are functions) ;; Update (assoc [1 2 3] 1 42) ;; [1 42 3] ;; Subvector (subvec [1 2 3 4] 1 3) ;; [2 3]
Maps (Hash Map)
;; Create {:name "Alice" :age 30} (hash-map :name "Alice" :age 30) ;; Access (get {:a 1} :a) ;; 1 (get {:a 1} :b 99) ;; 99 (default) ({:a 1} :a) ;; 1 (maps are functions) (:a {:a 1}) ;; 1 (keywords are functions) ;; Add/update (assoc {:a 1} :b 2) ;; {:a 1, :b 2} (dissoc {:a 1 :b 2} :a) ;; {:b 2} ;; Merge (merge {:a 1} {:b 2}) ;; {:a 1, :b 2} ;; Nested update (assoc-in {:user {:name "Alice"}} [:user :age] 30) (update-in {:user {:age 30}} [:user :age] inc) ;; Keys and values (keys {:a 1 :b 2}) ;; (:a :b) (vals {:a 1 :b 2}) ;; (1 2)
Sets
;; Create #{1 2 3} (hash-set 1 2 3) (set [1 2 3 2 1]) ;; #{1 2 3} ;; Add/remove (conj #{1 2} 3) ;; #{1 2 3} (disj #{1 2 3} 2) ;; #{1 3} ;; Test membership (sets are functions) (#{1 2 3} 2) ;; 2 (contains? #{1 2} 1) ;; true ;; Set operations (require '[clojure.set :as set]) (set/union #{1 2} #{2 3}) ;; #{1 2 3} (set/intersection #{1 2} #{2 3}) ;; #{2} (set/difference #{1 2} #{2 3}) ;; #{1}
Functions
Defining Functions
;; Named function (defn greet [name] (str "Hello, " name)) ;; Multiple arity (defn greet ([] (greet "World")) ([name] (str "Hello, " name)) ([greeting name] (str greeting ", " name))) ;; Variadic (rest args) (defn sum [& nums] (apply + nums)) (sum 1 2 3) ;; 6 ;; With docstring (defn square "Returns the square of n" [n] (* n n))
Anonymous Functions
;; fn form (fn [x] (* x x)) ;; Short form (reader macro) #(* % %) #(+ %1 %2) ;; % = %1 ;; Usage (map #(* % 2) [1 2 3]) ;; (2 4 6) ;; Partial application (def add5 (partial + 5)) (add5 10) ;; 15 ;; Composition (def f (comp str inc)) (f 5) ;; "6"
Higher-Order Functions
;; map - transform each element (map inc [1 2 3]) ;; (2 3 4) ;; filter - keep matching elements (filter even? [1 2 3 4]) ;; (2 4) ;; remove - remove matching elements (remove even? [1 2 3 4]) ;; (1 3) ;; reduce - accumulate (reduce + [1 2 3 4]) ;; 10 (reduce + 10 [1 2 3]) ;; 16 (with init) ;; apply - spread collection as args (apply + [1 2 3]) ;; 6 ;; Threading macros (-> 5 (+ 3) (* 2)) ;; (-> thread-first) 16 (->> [1 2 3] ;; (->> thread-last) (map inc) (filter even?)) ;; (2 4) ;; as-> (named threading) (as-> [1 2 3] $ (map inc $) (filter even? $)) ;; (2 4)
Predicates
;; Type predicates (string? "hello") ;; true (number? 42) ;; true (keyword? :foo) ;; true (map? {}) ;; true (vector? []) ;; true (set? #{}) ;; true ;; Value predicates (nil? nil) ;; true (empty? []) ;; true (even? 4) ;; true (odd? 3) ;; true (pos? 5) ;; true (neg? -1) ;; true
Destructuring
Extract values from collections in function parameters and let bindings.
Sequential Destructuring
;; Vector/list destructuring (let [[a b c] [1 2 3]] (+ a b c)) ;; 6 ;; Rest pattern (let [[first & rest] [1 2 3 4]] [first rest]) ;; [1 (2 3 4)] ;; As (keep original) (let [[a b :as all] [1 2 3]] [a b all]) ;; [1 2 [1 2 3]] ;; Nested (let [[[a b] c] [[1 2] 3]] [a b c]) ;; [1 2 3]
Map Destructuring
;; Basic map destructuring (let [{name :name age :age} {:name "Alice" :age 30}] [name age]) ;; ["Alice" 30] ;; Keys shorthand (when local = keyword) (let [{:keys [name age]} {:name "Alice" :age 30}] [name age]) ;; ["Alice" 30] ;; Strings and symbols (let [{:strs [name]} {"name" "Bob"}] name) (let [{:syms [x]} {'x 42}] x) ;; Default values (let [{:keys [name age] :or {age 0}} {:name "Alice"}] [name age]) ;; ["Alice" 0] ;; As (keep original map) (let [{:keys [name] :as person} {:name "Alice" :age 30}] [name person])
Function Parameters
;; Destructure in parameters (defn greet [{:keys [name age]}] (str "Hello " name ", age " age)) (greet {:name "Alice" :age 30}) ;; Sequential destructuring (defn distance [[x1 y1] [x2 y2]] (Math/sqrt (+ (Math/pow (- x2 x1) 2) (Math/pow (- y2 y1) 2))))
Sequences
Sequences are logical lists. All collections can be viewed as sequences.
Sequence Operations
;; Create sequence view (seq [1 2 3]) ;; (1 2 3) (seq {:a 1 :b 2}) ;; ([:a 1] [:b 2]) ;; Lazy sequences (range) ;; (0 1 2 3 ...) infinite! (range 5) ;; (0 1 2 3 4) (range 1 10 2) ;; (1 3 5 7 9) (repeat 3 "x") ;; ("x" "x" "x") (repeatedly 3 rand) ;; (0.xxx 0.yyy 0.zzz) (cycle [1 2]) ;; (1 2 1 2 1 2 ...) infinite! ;; Take/drop (take 3 (range)) ;; (0 1 2) (drop 2 [1 2 3 4]) ;; (3 4) (take-while pos? [1 2 0 3]) ;; (1 2) (drop-while neg? [-1 -2 3]) ;; (3)
Lazy Sequences
;; Lazy operations (don't execute until needed) (def big-seq (map inc (range 1000000))) ;; instant! ;; Force realization (doall (map println [1 2 3])) ;; realize & retain (dorun (map println [1 2 3])) ;; realize & discard ;; Create lazy seq (defn fib-seq ([] (fib-seq 0 1)) ([a b] (lazy-seq (cons a (fib-seq b (+ a b)))))) (take 10 (fib-seq)) ;; (0 1 1 2 3 5 8 13 21 34)
Transducers
;; Composable transformations (no intermediate seqs) (def xf (comp (map inc) (filter even?))) ;; Apply to collection (transduce xf + [1 2 3 4]) ;; 6 (2+4) ;; Into (build collection) (into [] xf [1 2 3 4]) ;; [2 4]
Macros
Macros transform code at compile time. They receive code as data and return new code.
Common Macros
;; Conditionals (if condition then-expr else-expr) (when condition body...) ;; implicit do, no else (cond (= x 1) "one" (= x 2) "two" :else "other") (case x 1 "one" 2 "two" "other") ;; constant-time dispatch (if-let [x (get-value)] (use x) (default)) (when-let [x (get-value)] (use x))
Loops & Iteration
;; doseq - side effects (returns nil) (doseq [x [1 2 3]] (println x)) ;; for - list comprehension (lazy) (for [x [1 2 3] y [10 20]] (+ x y)) ;; (11 21 12 22 13 23) ;; with :when guard (for [x (range 10) :when (even? x)] x) ;; (0 2 4 6 8) ;; loop/recur - tail recursion (loop [i 0 acc 0] (if (< i 5) (recur (inc i) (+ acc i)) acc)) ;; 10
Defining Macros
;; defmacro (defmacro unless [condition & body] `(if (not ~condition) (do ~@body))) (unless false (println "This runs")) ;; Syntax quote ` (namespace-qualify) ;; Unquote ~ (evaluate) ;; Unquote-splicing ~@ (splice sequence) ;; macroexpand - see expanded code (macroexpand-1 '(unless false (println "hi")))
Java Interop
Seamless access to Java classes, methods, and libraries.
Calling Java
;; Static method (Math/sqrt 16) ;; 4.0 (System/currentTimeMillis) ;; 1234567890 ;; Static field Math/PI ;; 3.141592653589793 ;; Constructor (new java.util.Date) (java.util.Date.) ;; shorter form ;; Instance method (.toUpperCase "hello") ;; "HELLO" (.length "hello") ;; 5 ;; Instance field (.-x point-object) ;; Chained calls (.. macro) (.. "hello" (toUpperCase) (substring 0 3)) ;; "HEL" ;; doto - call multiple methods on same object (doto (new java.util.HashMap) (.put "a" 1) (.put "b" 2))
Importing & Type Hints
;; Import classes (import java.util.Date) (import '[java.util Date Calendar]) (import '[java.io File InputStream]) ;; Type hints (avoid reflection) (defn length [^String s] (.length s)) ;; Array creation (make-array Integer/TYPE 10) (int-array 10) (into-array [1 2 3]) ;; Array access (aget arr 0) (aset arr 0 42)
Exceptions
;; Try/catch (try (/ 1 0) (catch ArithmeticException e (println "Division by zero!")) (finally (println "Cleanup"))) ;; Throw (throw (Exception. "Error message"))
Concurrency
Clojure provides several reference types for managing state safely in concurrent programs.
Atoms (Synchronous, Independent)
;; Create atom (def counter (atom 0)) ;; Dereference (read) @counter ;; 0 ;; Update with function (atomic) (swap! counter inc) ;; 1 (swap! counter + 10) ;; 11 ;; Reset to value (reset! counter 0) ;; 0 ;; Compare and set (compare-and-set! counter 0 100)
Refs (Coordinated, Transactional)
;; Create refs (def account-a (ref 100)) (def account-b (ref 200)) ;; Software Transactional Memory (STM) (dosync (alter account-a - 50) (alter account-b + 50)) ;; Read in transaction (dosync (+ @account-a @account-b)) ;; ref-set (like reset! for atoms) (dosync (ref-set account-a 1000))
Agents (Asynchronous, Independent)
;; Create agent (def logger (agent [])) ;; Send action (async) (send logger conj "Log entry 1") (send logger conj "Log entry 2") ;; Read current value @logger ;; ["Log entry 1" "Log entry 2"] ;; Wait for all actions to complete (await logger) ;; send-off for blocking operations (send-off logger identity)
Futures & Promises
;; Future - run in background (def f (future (Thread/sleep 1000) 42)) @f ;; blocks until ready, returns 42 ;; Promise - placeholder for value (def p (promise)) ;; Deliver value (once) (deliver p 42) @p ;; 42 ;; pmap - parallel map (pmap slow-function large-collection)
Delays
;; Lazy evaluation, run once (def d (delay (println "Computing...") 42)) @d ;; prints "Computing...", returns 42 @d ;; just returns 42, doesn't recompute
Namespaces & Code Organization
Namespace Declaration
;; At top of file (ns myapp.core "Namespace docstring" (:require [clojure.string :as str] [clojure.set :as set] [myapp.util :refer [helper]]) (:import [java.util Date Calendar]))
Require & Use
;; Require (recommended) (require '[clojure.string :as str]) (str/upper-case "hello") ;; Refer specific vars (require '[clojure.set :refer [union intersection]]) (union #{1 2} #{2 3}) ;; Use (not recommended, pollutes namespace) (use 'clojure.string) ;; brings in everything ;; Reload namespace (require 'myapp.core :reload)
Public & Private
;; Public function (default) (defn public-fn [] ...) ;; Private function (defn- private-fn [] ...) (defn ^:private private-fn [] ...)
Tooling & Project Structure
deps.edn (Clojure CLI)
;; deps.edn {:paths ["src" "resources"] :deps {org.clojure/clojure {:mvn/version "1.12.4"} compojure/compojure {:mvn/version "1.7.0"}} :aliases {:test {:extra-paths ["test"] :extra-deps {lambdaisland/kaocha {:mvn/version "1.91.1392"}}}}}
Common Commands
# Clojure CLI clj # Start REPL clj -M -m myapp.core # Run main function clj -X:test # Run with alias # Leiningen lein new app myapp # Create new project lein repl # Start REPL lein run # Run application lein test # Run tests lein uberjar # Build standalone JAR
Project Structure
myapp/ ├── deps.edn # Dependencies (CLI) ├── project.clj # Project config (Lein) ├── src/ │ └── myapp/ │ ├── core.clj │ └── util.clj ├── test/ │ └── myapp/ │ └── core_test.clj └── resources/ # Static files
Testing
;; test/myapp/core_test.clj (ns myapp.core-test (:require [clojure.test :refer [deftest is testing]] [myapp.core :as core])) (deftest my-test (testing "Addition" (is (= 4 (+ 2 2)))) (testing "Strings" (is (= "HELLO" (clojure.string/upper-case "hello"))))) ;; Run: lein test or clj -X:test
clj-kondo for linting, cljfmt for formatting. Most editors have excellent Clojure support via CIDER (Emacs), Calva (VSCode), or Cursive (IntelliJ).