Learn how namespaces organize Clojure code, why aliases and clear dependency boundaries matter, and how to structure projects so code remains readable, reloadable, and maintainable.
Namespaces are one of the quiet structural strengths of Clojure. They do more than prevent naming collisions. They shape how code is grouped, how dependencies are made visible, and how easily a system can be explored or reloaded during development.
Namespace: A named scope that groups related Vars and controls how symbols from other namespaces are required and referenced.
If you ignore namespace design, even clean functions can become difficult to navigate. If you use namespaces well, the codebase becomes easier to read, test, and drive from the REPL.
A namespace should usually correspond to a real unit of responsibility:
Good namespaces make responsibility visible. Bad namespaces become catch-all utility drawers or giant files where unrelated concerns drift together.
ns Declarations and AliasesThe ns macro is the normal entry point:
1(ns myapp.user
2 (:require [clojure.string :as str]
3 [myapp.db :as db]))
This is idiomatic because it:
Alias-based usage usually reads well:
1(defn normalized-email [user]
2 (-> user :email str/trim str/lower-case))
require with aliases is usually the best default. Broad use-style importing is discouraged because it makes symbol origins harder to track and increases collision risk.
In practice, strong namespace style means:
refer only a few very deliberate symbolsThis is not just style trivia. Clear dependency edges make REPL work, onboarding, and refactoring much easier.
In ordinary Clojure project structure, namespace names map cleanly to file paths:
myapp.user -> src/myapp/user.cljmyapp.web.middleware -> src/myapp/web/middleware.cljThat consistency helps readers jump quickly between conceptual names and source layout. When namespace names and folder structure drift apart, the codebase becomes harder to navigate than it needs to be.
There is no one perfect package layout, but a few habits help:
util namespaces as dumping groundsA small number of well-shaped namespaces beats a huge number of tiny ones with unclear purposes. Organization should serve clarity, not ceremony.
Good namespace design pays off during interactive development:
That workflow is harder in codebases where dependencies are implicit or symbols are imported broadly without context.
Even inside one project, each namespace should be written as if it exposes a small public surface to other code.
That mindset helps with:
The goal is not heavy encapsulation mechanics. It is discipline about what a namespace is actually for.
graph TD;
A["Project"] --> B["Domain namespaces"]
A --> C["Adapter namespaces"]
A --> D["Infrastructure namespaces"]
B --> E["Pure transformations and rules"]
C --> F["External service, DB, or HTTP boundaries"]
D --> G["Lifecycle or shared runtime setup"]
The diagram below captures a practical structure: use namespaces to make responsibility and dependency direction visible.