Clojure Programming Cheat Sheet: The Ultimate Guide for Functional Development

Introduction: What is Clojure and Why It Matters

Clojure is a modern, dynamic, functional programming language that runs on the Java Virtual Machine (JVM), JavaScript engines, and the Common Language Runtime (CLR). Created by Rich Hickey in 2007, Clojure combines the interactive development style of scripting languages with an immutable, persistent data structure design and robust concurrency features. As a Lisp dialect, Clojure embraces code-as-data philosophy, making it exceptionally powerful for metaprogramming.

Why Clojure matters:

  • Functional programming with emphasis on immutability and pure functions
  • Concurrency support through Software Transactional Memory (STM) and other mechanisms
  • JVM/JS/CLR integration allowing easy access to existing libraries and ecosystems
  • Data-oriented design with powerful built-in data structures
  • REPL-driven development promoting interactive, iterative coding

Core Concepts and Principles

Functional Programming Fundamentals

ConceptDescriptionExample
ImmutabilityData cannot be changed after creation(def x [1 2 3]) creates an immutable vector
Pure FunctionsFunctions without side effects(defn add [a b] (+ a b))
First-class FunctionsFunctions can be passed as values(map inc [1 2 3])inc is passed to map
RecursionFunctions calling themselves(defn factorial [n] (if (= n 1) 1 (* n (factorial (dec n)))))
Lazy EvaluationComputation deferred until needed(def infinite (iterate inc 0))

Clojure’s LISP Heritage

  • Homoiconicity: Code represented as data structures (S-expressions)
  • Prefix notation: Operations come before operands (+ 1 2) not 1 + 2
  • Macros: Code that generates code at compile time
  • Everything is an expression: All forms return values

Clojure Data Structures

TypeDescriptionLiteral SyntaxConstructorExample
ListsLinked lists (sequential access)'(1 2 3)(list 1 2 3)(first '(1 2 3)) → 1
VectorsIndexed arrays (random access)[1 2 3](vector 1 2 3)(get [1 2 3] 1) → 2
MapsKey-value pairs{:a 1 :b 2}(hash-map :a 1 :b 2)(:a {:a 1 :b 2}) → 1
SetsUnique values#{1 2 3}(hash-set 1 2 3)(contains? #{1 2 3} 1) → true

Syntax and Core Functions

Basic Syntax

;; Comments start with semicolons

;; Defining variables
(def name "value")

;; Defining functions
(defn function-name
  "Optional docstring"
  [param1 param2]
  (body))

;; Anonymous functions
(fn [x] (+ x 1))
#(+ % 1)  ;; Short syntax, % refers to the argument

;; Conditional statements
(if condition
  then-expr
  else-expr)

(cond
  test1 expr1
  test2 expr2
  :else default-expr)

;; Binding local variables
(let [x 1
      y 2]
  (+ x y))

Essential Functions

Collections Processing

;; Sequence operations
(map inc [1 2 3])                 ;; => (2 3 4)
(filter even? [1 2 3 4])          ;; => (2 4)
(reduce + [1 2 3 4])              ;; => 10
(take 3 (range))                  ;; => (0 1 2)
(drop 2 [1 2 3 4])                ;; => (3 4)
(concat [1 2] [3 4])              ;; => (1 2 3 4)

;; List/Vector operations
(first [1 2 3])                   ;; => 1
(rest [1 2 3])                    ;; => (2 3)
(nth [1 2 3] 1)                   ;; => 2
(conj [1 2 3] 4)                  ;; => [1 2 3 4]
(count [1 2 3])                   ;; => 3

;; Map operations
(get {:a 1 :b 2} :a)              ;; => 1
(:a {:a 1 :b 2})                  ;; => 1
(assoc {:a 1} :b 2)               ;; => {:a 1 :b 2}
(dissoc {:a 1 :b 2} :a)           ;; => {:b 2}
(merge {:a 1} {:b 2})             ;; => {:a 1 :b 2}
(keys {:a 1 :b 2})                ;; => (:a :b)
(vals {:a 1 :b 2})                ;; => (1 2)

;; Set operations
(conj #{1 2} 3)                   ;; => #{1 2 3}
(disj #{1 2 3} 3)                 ;; => #{1 2}
(clojure.set/union #{1 2} #{2 3}) ;; => #{1 2 3}

Function Composition

;; Compose functions
(comp f g h)           ;; Same as (f (g (h x)))
(partial f arg1 arg2)  ;; Creates a new function with args pre-filled
(complement pred)      ;; Creates a function that returns the opposite of pred

;; Threading macros
(-> x (f) (g) (h))     ;; Threads x as first argument: (h (g (f x)))
(->> x (f) (g) (h))    ;; Threads x as last argument: (h (g (f x)))

;; Function application
(apply + [1 2 3 4])    ;; => 10, same as (+ 1 2 3 4)

Data Transformation and Manipulation

Destructuring

;; Vector destructuring
(let [[a b & rest] [1 2 3 4 5]]
  [a b rest])  ;; => [1 2 (3 4 5)]

;; Map destructuring
(let [{a :a b :b :or {b 0}} {:a 1}]
  [a b])  ;; => [1 0]

;; Keyword shorthand
(let [{:keys [a b] :or {b 0}} {:a 1}]
  [a b])  ;; => [1 0]

Transducers

;; Create a transducer (reusable transformation)
(def xform (comp (filter even?) (map #(* % %))))

;; Apply to different contexts
(transduce xform + [1 2 3 4])        ;; => 20
(into [] xform [1 2 3 4])            ;; => [4 16]
(sequence xform [1 2 3 4])           ;; => (4 16)

Concurrency and State Management

Atoms (Synchronous, Uncoordinated)

;; Create an atom
(def counter (atom 0))

;; Update methods
(reset! counter 10)           ;; => 10
(swap! counter inc)           ;; => 11
(swap! counter + 5)           ;; => 16

;; Read value
@counter                      ;; => 16
(deref counter)               ;; => 16

Refs (Synchronous, Coordinated)

;; Create refs
(def account1 (ref 100))
(def account2 (ref 50))

;; Transaction
(dosync
  (alter account1 - 30)
  (alter account2 + 30))

;; Read values
@account1                     ;; => 70
@account2                     ;; => 80

Agents (Asynchronous, Uncoordinated)

;; Create an agent
(def log (agent []))

;; Send actions (asynchronous)
(send log conj "message1")    ;; Returns agent immediately
(send log conj "message2")    ;; Actions queued and processed in order

;; Read value (may not reflect pending actions)
@log                          ;; => ["message1" "message2"]

Promises and Futures

;; Futures - run task in another thread
(def result (future (Thread/sleep 1000) 42))
@result                    ;; Blocks until computation completes

;; Promises - placeholder for a future value
(def p (promise))
(deliver p 42)             ;; Set the value
@p                         ;; Get the value (blocks if not delivered)

Java Interoperability

Accessing Java Classes and Methods

;; Creating Java objects
(new java.util.Date)
(java.util.Date.)  ;; Shorthand syntax

;; Static method call
(Math/abs -42)

;; Instance method call
(.toString (java.util.Date.))

;; Accessing fields
(.-x point)        ;; Access field x of point

Working with Java Collections

;; Create Java collection
(doto (java.util.ArrayList.)
  (.add "item1")
  (.add "item2"))

;; Convert between Java and Clojure collections
(seq java-collection)   ;; Java to Clojure
(into [] java-list)     ;; Java list to vector
(into {} java-map)      ;; Java map to Clojure map

Namespaces and Project Structure

Namespace Management

;; Define/switch namespace
(ns my-project.core
  (:require [clojure.string :as str]
            [clojure.set :refer [union]]
            [my-project.utils :refer :all])
  (:import [java.util Date UUID]))

;; Access namespaced functions
(str/upper-case "hello")
(union #{1 2} #{3 4})

Project Structure with Leiningen

my-project/
├── project.clj          ; Project definition
├── README.md
├── src/
│   └── my_project/      
│       └── core.clj     ; Main namespace
└── test/
    └── my_project/
        └── core_test.clj

Project.clj example:

(defproject my-project "0.1.0-SNAPSHOT"
  :description "Project description"
  :url "http://example.com/project"
  :license {:name "EPL-2.0"}
  :dependencies [[org.clojure/clojure "1.11.1"]]
  :main ^:skip-aot my-project.core
  :target-path "target/%s"
  :profiles {:uberjar {:aot :all}})

Testing and Debugging

Testing with clojure.test

(ns my-project.core-test
  (:require [clojure.test :refer :all]
            [my-project.core :refer :all]))

(deftest test-addition
  (testing "Basic addition"
    (is (= 4 (add 2 2)))
    (is (= 0 (add -2 2))))
  
  (testing "Multiple arguments"
    (is (= 10 (add 1 2 3 4)))))

;; Run tests with (run-tests)

Debugging Tools

;; Print debugging
(prn "Debug value:" x)
(println "Hello")

;; Function tracing
(use 'clojure.tools.trace)
(trace-vars my-function)
(trace-ns my-namespace)

;; Detailed inspection
(use 'clojure.pprint)
(pprint complex-data)

Common Challenges and Solutions

Stacktrace Management

ChallengeSolution
Unreadable stacktracesUse clojure.stacktrace/print-stack-trace or clojure.repl/pst
Find error sourceUse clojure.repl/source to examine function code
Prevent lazy seq errorsForce evaluation with doall or dorun

Performance Issues

ChallengeSolution
Slow startup timeUse AOT compilation, GraalVM native-image
Inefficient data processingUse transducers for complex transformations
Memory consumptionUse lazy sequences, reducers library
Boxed math operationsUse primitive hints ^long, ^double

Null Handling

ChallengeSolution
NullPointerExceptionsUse nil? checks
Safer nil handlingUse some->, some->> threading macros
Value presenceUse if-let, when-let for conditional binding

Best Practices and Tips

Code Style

  • Indent with 2 spaces
  • Use kebab-case for names (e.g., calculate-total)
  • Prefer let over nested function calls for clarity
  • Keep functions small and focused on a single task
  • Use docstrings liberally
  • Follow the Clojure Style Guide

REPL-Driven Development

  1. Start a REPL and connect to your project
  2. Load code with (require '[namespace] :reload)
  3. Test functions interactively
  4. Iterate, modify, and reload
  5. Extract and organize successful code

Functional Design Patterns

PatternDescriptionExample
MiddlewareFunctions that transform other functionsRing web server middleware
MultimethodsPolymorphic dispatch based on arbitrary values(defmulti) and (defmethod)
ProtocolsInterface-like abstractions(defprotocol)
ValidatorsFunctions that check data integritySpec, Schema, Malli
Component PatternLifecycle management for stateful componentsComponent, Mount, Integrant

Performance Optimization

  • Use transients for performance-critical code that builds collections
  • Consider type hints for performance-critical functions
  • Profile using tools like VisualVM or YourKit
  • Memoize expensive pure functions
  • Use reduce instead of map+filter combinations

Resources for Further Learning

Books

  • “Clojure for the Brave and True” by Daniel Higginbotham
  • “Programming Clojure” by Stuart Halloway and Aaron Bedra
  • “The Joy of Clojure” by Michael Fogus and Chris Houser
  • “Elements of Clojure” by Zachary Tellman
  • “Web Development with Clojure” by Dmitri Sotnikov

Websites and Documentation

Libraries Worth Exploring

CategoryLibraries
Web DevelopmentRing, Compojure, Reitit, Luminus
Database Accessnext.jdbc, HoneySQL, Datomic
UI DevelopmentReagent, re-frame, Fulcro
Data Validationclojure.spec, Malli, Schema
Testingclojure.test, test.check, kaocha
Utilitymedley, clojure.tools.cli
Scroll to Top