Clojure Internals, History and Best Practices

46.1 How Clojure Works

Every Clojure form is compiled to bytecode (even at the REPL). There's no interpreter on the JVM.

Key interfaces: IFn, ISeq, IPersistentCollection, Associative, ILookup, IDeref, IReduce. Protocols compile to JVM interfaces plus a dispatch cache.

Source → Reader (text → data) → Macroexpansion → Analyzer → Compiler → JVM bytecode (classes per fn) → JIT
Persistent collections: HAMTs (hash array mapped tries) for maps/sets, 32-way tries with a tail for vectors
Vars: namespaced mutable references with thread-local binding; fns are compiled classes implementing IFn

46.2 History

Rich Hickey develops Clojure; public release in October 2007 Datomic released; reducers (1.5)

Transducers (1.7, released 2015); reader conditionals clojure.spec (1.9, released 2017)

Clojure CLI and deps.edn become official; Clojure 1.10 (2018)

Nubank acquires Cognitect (Clojure's steward) Clojure 1.11 (keyword-arg maps, parse-long, update-vals, iteration)

Clojure 1.12 (qualified methods as values, add-lib, functional interface conversion, array class syntax)

46.3 Best Practices

✅ Model with plain data (maps, vectors, sets) and namespaced keywords. Keep functions small and pure.

✅ Push side effects to the edges (a functional core and an imperative shell).

✅ Use one atom per application state (often a map); use refs only for coordinated multi-identity updates.

✅ Develop at the REPL with rich comment blocks; reload with tools.namespace/integrant-repl.

✅ Validate at boundaries with Malli or spec. Use ex-info with data for errors.

✅ Lint with clj-kondo, format with cljfmt/zprint, and test with clojure.test + test.check.

✅ Prefer threading macros for readable pipelines and destructuring for clear parameters.

✅ Enable reflection warnings in performance-sensitive code.

❌ Side effects inside lazy sequences, swap! functions, or dosync

❌ Deeply nested data access without get-in/update-in/Specter ❌ Macros when a function would do

❌ Holding the head of large lazy sequences ❌ Using read-string on untrusted input (use clojure.edn/read-string)

❌ Overusing records and protocols where plain maps and functions suffice