~ / track A / clojure basics

REPL-driven development

Basic

REPL-driven development is not "using a REPL." Every language has a REPL. What Clojure (and Lisp generally) gives you is the discipline of editing a running program: you keep a single JVM process alive for hours or days, sending it one form at a time, and the program grows under your hands. The compile-run-crash cycle goes away.

The inner loop

In a typical language:

edit → save → recompile → restart → click through to the bug → log

In Clojure with a connected editor:

edit → C-x C-e → see the result inline → try again

That last loop runs in tens of milliseconds. You're never restarting. You load new function definitions, redefine them, exercise them with the real data sitting in an atom, and only commit the result once you've watched it behave.

What "send to the REPL" means

Your editor (Emacs+CIDER, Calva for VS Code, Cursive for IntelliJ, Conjure for Neovim) connects to the running JVM via nREPL, a Clojure protocol that streams forms over a socket. When you press a keybinding on a form, the editor:

  1. Reads the form your cursor is in.
  2. Sends it to the JVM.
  3. Receives the value (and any printed output).
  4. Renders it inline beside your code.

The JVM's environment changes as a result — defs become new bindings, defns redefine functions, requires pull in new namespaces. The next form you send sees the updated world.

A tiny session

loading sci
press ⌘/Ctrl-↵ or click ▶ run to evaluate

That is the entire workflow. No save-and-restart. The definitions you keep live in the file; the experiments stay at the REPL.

Why it changes how you write code

  • Small, composable functions. Each one is something you can probe individually with a single form.
  • Data is concrete. You hold the actual parsed JSON, the actual DB row, in a def and reshape your code around what you see.
  • Errors become exploratory. A thrown exception doesn't kill the program; you *e to inspect, fix the function, and re-run.
  • Tests are an afterthought, not the loop. You build correctness by watching the function behave, then crystallize what you saw into a test.

This is the inverted version of TDD: in Clojure, the REPL feedback comes before the test, and the test is the receipt.

Reloaded workflow

For longer-lived processes (web apps, daemons), the reloaded workflow (Stuart Sierra's pattern, supported by [[component-systems]]) adds a (reset) function that stops all components, reloads namespaces, and restarts cleanly — all without leaving the JVM. The discipline: your code must be reloadable, which means no module-level side effects and all state held in injectable components.

Tooling

EditorPluginNotes
EmacsCIDERThe reference experience. Inline results, debugger, profiler
VS CodeCalvaMost common starting point in 2026; based on cider-nrepl
IntelliJCursiveCommercial, deep static analysis
NeovimConjure, vim-icedLean and fast
Cursor / ZedCalva-compatible via LSPIncreasingly popular

All speak nREPL or the newer Socket REPL (built into Clojure itself, no deps). The protocol is open: agentic tools and AI assistants can drive the same loop.

Real-world: how Clojure shops actually develop

PracticeWhy it's load-bearing
One JVM stays up for the whole workdayCold-start latency disappears
Production debugging via REPL connectionConnect a remote nREPL to a running container, inspect live
"Rich comment blocks" — code in (comment ...) for replaySource-of-truth of experiments alongside the function
Jacking-in starts the app and a REPL togetherThe dev process is a debuggable program
Datafy / nav for tabular browsingTools like Reveal, Portal, Clerk explore live data
Hot-reload of CLJS via shadow-cljsSame loop for the browser

The cultural marker: when a Clojure programmer says "I'll try it," they mean right now, in the running system — not "I'll write a script."

Check yourself

? quiz

What is the defining feature of REPL-driven development, as Clojure practitioners use the term?

Exercise

Pick any function you've recently written in another language that took several restart-test cycles to get right. Imagine the Clojure version: what would each iteration look like as a single form sent to a live process? What state would you keep in a def between iterations? The point of the exercise is to notice that the restart was never doing anything for you.

 status: new