Learn how namespaces create modular boundaries in Clojure, and how `require`, aliases, and clear file layout keep larger systems understandable.
Namespace: A mapping from symbols to Vars and classes that gives code a modular boundary, a place for names to live, and a way to control what gets exposed or imported.
Namespaces are not just a file header detail in Clojure. They are one of the main mechanisms for turning a pile of functions into a maintainable system. A well-structured namespace tells you what a module owns, what it depends on, and what it wants the rest of the program to know.
A namespace should usually group code that belongs together conceptually:
That means the job is not merely “split files when they get long.” The real question is whether the namespace expresses a coherent responsibility.
require with AliasesIn modern Clojure code, require with explicit aliases is usually the clearest dependency style.
1(ns my-app.orders
2 (:require [my-app.db :as db]
3 [my-app.pricing :as pricing]
4 [clojure.string :as str]))
This keeps dependencies visible at the top and call sites readable:
1(pricing/final-total order)
2(db/save-order! datasource order)
The explicit alias communicates origin without flooding the code with full namespace paths.
The Clojure namespace system still supports refer and use, but they are easy to overdo. Pulling too many names directly into a namespace makes it harder to see where functions come from and increases the risk of collisions.
The practical rule is:
use in ordinary source codeThat keeps code easier to scan and refactor.
A good Clojure project typically mirrors namespace structure in its source tree:
1src/my_app/orders.clj -> my-app.orders
2src/my_app/orders/db.clj -> my-app.orders.db
3src/my_app/orders/http.clj -> my-app.orders.http
This is not just convention for convention’s sake. It makes navigation and system shape legible, especially once a project spans many namespaces.
A namespace should often gather functions that tend to change together or are owned by the same conceptual part of the system.
Good examples:
my-app.billingmy-app.auth.tokensmy-app.user-profileWeaker examples are namespaces that exist only because of implementation trivia, such as one namespace per helper category with no real domain meaning.
A namespace starts to smell wrong when it:
That kind of namespace becomes hard to test and hard to reason about because it has lost a clear center.
flowchart TD
A["Project structure"] --> B["Namespace per coherent responsibility"]
B --> C["Explicit dependencies via require and aliases"]
C --> D["Readable call sites and clearer modular boundaries"]
Not every helper belongs in the public face of a namespace. One of the healthy habits in modular Clojure code is to think about which vars are the intended entry points and which ones are merely support.
That distinction may not always need elaborate tooling, but it should guide naming, documentation, and how much other namespaces are encouraged to depend on internals.
Namespace problems compound. A small project can survive with a few messy imports and broad utility files. A larger project usually cannot. Once many teams or features depend on the same namespace structure, weak boundaries create long-term coupling.
This is why namespace design matters early, even when the project is still small.
use broadly in ordinary application coderequire with aliases for most dependencies.