Clojure Programming Language Cheatsheet: From Basics to Best Practices

Introduction to Clojure

Clojure is a modern Lisp dialect that runs on the Java Virtual Machine (JVM), offering the expressiveness of a functional programming language with the robustness and performance of the JVM. Created by Rich Hickey in 2007, Clojure emphasizes immutability, simplicity, and practicality, making it excellent for concurrent programming, data processing, and building robust applications.

Core Concepts

Fundamental Principles

  • Functional Programming: Emphasizes pure functions and immutable data
  • Hosted Language: Runs on JVM, JavaScript runtimes (ClojureScript), or CLR (.NET)
  • Dynamic Typing: Types are determined at runtime
  • Immutability: Data structures are immutable by default
  • REPL-Driven Development: Interactive development with immediate feedback

Syntax Basics

ElementDescriptionExample
S-expressionsCode as data; expressions in parentheses(+ 1 2 3)
Prefix notationFunction name comes first in expression(str "Hello" " " "World")
ListsOrdered collections of items'(1 2 3) or (list 1 2 3)
VectorsIndexed collections[1 2 3]
MapsKey-value pairs{:a 1 :b 2}
SetsUnique collections#{1 2 3}
KeywordsSymbolic identifiers (often used as keys):name

Data Types and Collections

Scalar Types

;; Numbers
42          ; integer
3.14        ; floating-point
22/7        ; ratio
1N          ; arbitrary precision integer
1.0M        ; arbitrary precision decimal

;; Strings and Characters
"hello"     ; string
\a          ; character

;; Other Scalars
true false  ; booleans
nil         ; null value
:keyword    ; keyword

Collections

;; Lists - linked lists, efficient for sequential access
'(1 2 3)                ; quoted list literal
(list 1 2 3)            ; list function

;; Vectors - indexed access, efficient for random access
[1 2 3]                 ; vector literal
(vector 1 2 3)          ; vector function
(get [1 2 3] 0)         ; returns 1 (zero-indexed)

;; Maps - key-value associations
{:a 1 :b 2}             ; map literal
(hash-map :a 1 :b 2)    ; hash-map function
(get {:a 1 :b 2} :a)    ; returns 1
({:a 1 :b 2} :a)        ; also returns 1 (maps are functions of their keys)

;; Sets - unique values
#{1 2 3}                ; set literal
(hash-set 1 2 3)        ; hash-set function
(get #{1 2 3} 1)        ; returns 1
(#{1 2 3} 1)            ; also returns 1 (sets are functions of their members)

Functions and Control Flow

Function Definition

;; Named function
(defn add [a b]
  (+ a b))

;; Anonymous function (lambda)
(fn [a b] (+ a b))

;; Shorthand anonymous function
#(+ %1 %2)  ; % for single arg, %1, %2, etc. for multiple args

;; Multi-arity function
(defn greeting
  ([] (greeting "World"))
  ([name] (str "Hello, " name "!")))

;; Variadic function (variable number of arguments)
(defn sum [& numbers]
  (apply + numbers))

Control Flow

;; Conditionals
(if condition
  true-expr
  false-expr)

(when condition  ; when is like if without an else
  expr1
  expr2)

(cond
  condition1 result1
  condition2 result2
  :else default-result)

(case value
  value1 result1
  value2 result2
  default-result)

;; Iteration
(dotimes [i 3]
  (println i))  ; prints 0, 1, 2

(doseq [item [1 2 3]]
  (println item))  ; prints each item

(for [x [1 2 3]
      y [4 5 6]
      :when (> (* x y) 10)]
  [x y])  ; list comprehension with filtering

Sequence Operations

Core Sequence Functions

;; Creating sequences
(range 5)               ; returns (0 1 2 3 4)
(repeat 3 "hi")         ; returns ("hi" "hi" "hi")
(iterate inc 1)         ; returns infinite seq (1 2 3 ...)

;; Transforming sequences
(map inc [1 2 3])       ; returns (2 3 4)
(filter even? [1 2 3 4]) ; returns (2 4)
(remove odd? [1 2 3 4]) ; returns (2 4)
(reduce + [1 2 3 4])    ; returns 10

;; Other useful sequence functions
(count [1 2 3])         ; returns 3
(first [1 2 3])         ; returns 1
(rest [1 2 3])          ; returns (2 3)
(take 2 [1 2 3 4])      ; returns (1 2)
(drop 2 [1 2 3 4])      ; returns (3 4)

Common Sequence Patterns

;; Transforming a collection
(->> [1 2 3 4]
     (filter even?)
     (map #(* % %))
     (reduce +))        ; returns 20 (sum of squares of even numbers)

;; Grouping and counting
(frequencies ["a" "b" "a" "c"]) ; returns {"a" 2, "b" 1, "c" 1}
(group-by count ["a" "aa" "b" "bb"]) ; returns {1 ["a" "b"], 2 ["aa" "bb"]}

State and Concurrency

Managing State

;; Atoms - synchronous, uncoordinated reference type
(def counter (atom 0))
(swap! counter inc)     ; atomically increments counter
(reset! counter 0)      ; resets counter to 0
@counter                ; dereferences counter

;; Refs - coordinated, synchronous reference type (for transactions)
(def account (ref 1000))
(dosync
  (alter account - 100) ; withdraw 100
  (alter other-account + 100)) ; deposit 100

;; Agents - asynchronous reference type
(def counter (agent 0))
(send counter inc)      ; asynchronously increments counter

Concurrency Patterns

PatternDescriptionBest Used For
AtomsIndependent, synchronous changesSimple shared state
RefsCoordinated synchronous changes within transactionsComplex state changes requiring consistency
AgentsAsynchronous changesFire-and-forget operations
FuturesCompute a value in another threadParallelizing independent computation
PromisesSet a value once, possibly from another threadOne-time coordination point

Namespaces and Project Structure

;; Namespace declaration
(ns myapp.core
  (:require [clojure.string :as str]
            [clojure.set :refer [union difference]]
            [myapp.utils :refer :all])
  (:import [java.util Date UUID]))

;; Using imported things
(str/join ", " ["a" "b" "c"])
(union #{1 2} #{2 3})
(Date.)                 ; constructing a Java class

Working with Java

;; Importing and using Java classes
(import '(java.util Date Calendar))
(def now (Date.))                 ; constructor
(. now toString)                  ; method call
(.toString now)                   ; shorthand method call

;; Static method call
(Calendar/getInstance)            ; static method
(Math/sqrt 16)                    ; static method with arg

;; Using Java collections
(doto (java.util.ArrayList.)
  (.add "item1")
  (.add "item2"))

Common Challenges and Solutions

Challenge: Debugging Complex Expressions

Solution: Use (println) or (prn) to inspect values. The thread-last macro (->>) helps break down complex pipelines.

(->> [1 2 3 4]
     (map inc)
     (do (println "After inc:" %) %)  ; Debug output in a pipeline
     (filter even?))

Challenge: Managing Side Effects

Solution: Isolate side effects and use functional core, imperative shell pattern.

;; Bad: Mixed side effects and logic
(defn process-data [data]
  (println "Processing data...")
  (let [result (transform-data data)]
    (save-to-db result)
    result))

;; Better: Separate logic from side effects
(defn transform-data [data]
  ;; Pure function, no side effects
  (map inc data))

(defn process-data [data]
  (println "Processing data...")
  (let [result (transform-data data)]
    (save-to-db result)
    result))

Challenge: Handling Lazy Sequences

Solution: Be mindful of when sequences are realized and use doall when needed.

;; This won't print anything because map is lazy
(def seq1 (map #(do (println %) (inc %)) [1 2 3]))

;; Force evaluation
(def seq2 (doall (map #(do (println %) (inc %)) [1 2 3])))

Best Practices

Code Style

  • Prefer functional style with immutable data structures
  • Use meaningful naming for functions and variables
  • Use threading macros (->, ->>) for readability
  • Keep functions small and focused on a single task
  • Favor composing functions over complex logic

REPL-Driven Development

  1. Start with a running REPL
  2. Write small functions and test them immediately
  3. Develop incrementally, building up functionality
  4. Refactor and refine as understanding evolves

Performance Tips

  • Use transients for batch operations on large collections
  • Consider type hints for performance-critical code
  • Use reducers for parallel processing of large collections
  • Profile before optimizing (use tools like YourKit, VisualVM)

Testing Best Practices

  • Write small, focused tests
  • Use clojure.test or libraries like expectations
  • Test logic, not implementation details
  • Consider property-based testing with test.check
;; Example test with clojure.test
(ns myapp.core-test
  (:require [clojure.test :refer :all]
            [myapp.core :refer :all]))

(deftest test-add
  (is (= 3 (add 1 2)))
  (is (= 0 (add -1 1))))

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

Online Resources

Tools

  • Leiningen or deps.edn for dependency management
  • CIDER for Emacs or Cursive for IntelliJ for development
  • Figwheel for ClojureScript development
  • Gorilla REPL for literate programming and data visualization

Community

Scroll to Top