REPL-driven development
BasicREPL-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:
- Reads the form your cursor is in.
- Sends it to the JVM.
- Receives the value (and any printed output).
- 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
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
defand reshape your code around what you see. - Errors become exploratory. A thrown exception doesn't kill the
program; you
*eto 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
| Editor | Plugin | Notes |
|---|---|---|
| Emacs | CIDER | The reference experience. Inline results, debugger, profiler |
| VS Code | Calva | Most common starting point in 2026; based on cider-nrepl |
| IntelliJ | Cursive | Commercial, deep static analysis |
| Neovim | Conjure, vim-iced | Lean and fast |
| Cursor / Zed | Calva-compatible via LSP | Increasingly 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
| Practice | Why it's load-bearing |
|---|---|
| One JVM stays up for the whole workday | Cold-start latency disappears |
| Production debugging via REPL connection | Connect a remote nREPL to a running container, inspect live |
"Rich comment blocks" — code in (comment ...) for replay | Source-of-truth of experiments alongside the function |
| Jacking-in starts the app and a REPL together | The dev process is a debuggable program |
| Datafy / nav for tabular browsing | Tools like Reveal, Portal, Clerk explore live data |
| Hot-reload of CLJS via shadow-cljs | Same 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.