Compile-Time Branching in Nim with `when` and `defined`
Use Nim's when statement to remove code before compilation based on defined flags.
24 Aug 2025, 18:40 UTC

Problem and takeaway
You need different code for debug and release builds, or for different Nim backends, but you do not want runtime checks or unused code in the final binary. Nim's when statement evaluates conditions during compilation. A false branch is removed during semantic analysis, so it never becomes C, JavaScript, or machine code. The useful rule is: use when defined(flag): for compile-time choices, and reserve runtime if for values known only while the program runs.
How when and defined work
when is a Nim construct whose condition must be a compile-time constant. The built-in defined(symbol) returns true when the symbol exists. Symbols can come from Nim itself, such as release or js, or from the -d: flag passed to the compiler. Unlike if, when does not produce a runtime branch. The compiler discards the false branch before code generation, which means the discarded code does not increase binary size and does not trigger dead-code warnings.
A discarded branch still has to be syntactically valid Nim. Mismatched brackets or invalid tokens inside a false branch can abort compilation even though that branch never runs.
Worked configuration
Create a file named app.nim:
when defined(debug):
echo "Running a debug build"
else:
echo "Running a release build"
proc log(message: string) =
when defined(debug):
echo "[debug] ", message
# No else branch: the call vanishes entirely in release builds.
log("startup")Run from the project directory in a terminal. No special permissions are required.
- Debug build:
nim c -d:debug app.nim
./appnim c -d:release app.nim
./appExpected check: the debug build prints the debug line and the [debug] prefix; the release build prints only the release line and omits the log output. The exact text depends on your Nim version and is not guaranteed here.
Conditional fields in an object
when can also appear inside an object type definition to select fields before code generation:
type
Connection = object
host: string
port: int
when defined(useTls):
certFile: stringCompile with nim c -d:useTls app.nim to include certFile; omit the flag and the field does not exist in the compiled type.
Limits and common mistakes
- Runtime values are not allowed. A condition such as
when someProc()orwhen x > 0wherexis a variable causes a compile error.whencannot inspect runtime state. - Flag names are case-sensitive.
nim c -d:debugdefinesdebug, notDebug. A checkdefined(Debug)returns false. - False branches must parse. The compiler does not semantically analyze a discarded branch, but it still parses it. A missing bracket or invalid token in the false branch stops compilation.
- Version differences. Exact
definedbehavior and built-in symbols can differ between Nim 1.x and 2.x releases. Verify on the target compiler before relying on version-specific symbols.
Practical verification
- Run
nim --versionand record the Nim release. - Compile the example with and without
-d:debug. - Compare the program output. If the debug-only line appears only when the flag is present,
whenis working as expected. - Optionally inspect generated C or binary symbols to confirm the discarded call is absent, though symbol names vary by backend and version.
This is a conservative draft: confirm the exact defined builtins and backend symbols for your Nim version before using them in production builds.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.