Conditionals, Lists, and Built-in Operations

Updated

September 12, 2026

Conditionals, Lists, and Built-in Operations

if in Nix is a value, not a statement. The boring default is: if c then a else b (else required), lists with ++, and builtins.map / filter / length / elem.

Mental model

if isProd then 5 else 1

No if without else. No if that “does” something. Both branches are expressions; the unused one stays lazy.

Lists:

[ 1 "two" ./three (4 + 5) ]

Elements are space-separated. [ 1 2 ] ++ [ 3 ] is [ 1 2 3 ]. Indexing is builtins.elemAt xs 0 (0-based). There is no xs[0] in the language.

lib.optional cond x is if cond then [ x ] else [ ] — the boring way to maybe-append one item.

Worked examples

Case 1: Flags

Save as conditional_config.nix:

# conditional_config.nix
let
  isProduction = true;
in
{
  replicas = if isProduction then 5 else 1;
  logging = if isProduction then "warn" else "debug";
}
nix eval --file conditional_config.nix --json

Output:

{"logging":"warn","replicas":5}

Flip isProduction to false and eval again.

Case 2: map / filter

Save as list_operations.nix:

# list_operations.nix
let
  tableNumbers = [ 1 2 3 4 5 6 7 8 9 10 ];
  isPriority = n: n <= 3;
  priorityTables = builtins.filter isPriority tableNumbers;
  deskWorkers = builtins.map (n: { table = n; status = "active"; }) priorityTables;
in
{
  inherit deskWorkers;
  n = builtins.length priorityTables;
  hasTen = builtins.elem 10 tableNumbers;
}
nix eval --file list_operations.nix --json

Output:

{"deskWorkers":[{"status":"active","table":1},{"status":"active","table":2},{"status":"active","table":3}],"hasTen":true,"n":3}

Case 3: else is required

Save as bad_if.nix:

# bad_if.nix
if true then 1
nix eval --file bad_if.nix

Output (shape):

error: syntax error, unexpected end of file, expecting 'else'

To omit an attr, do not skip else. Use:

{ } // (if enable then { workers = 4; } else { })

or lib.optionalAttrs enable { workers = 4; }.

Case 4: Concatenate packages-style lists

Save as packages.nix:

# packages.nix
let
  common = [ "git" "jq" ];
  extra = [ "ripgrep" ];
in
common ++ extra ++ (if true then [ "htop" ] else [ ])
nix eval --file packages.nix --json

Output:

["git","jq","ripgrep","htop"]

This is how environment.systemPackages concatenates across modules (the module system merges lists). Here you do it by hand.

Case 5: Lazy unused branch

Save as lazy_if.nix:

# lazy_if.nix
if true then "ok" else throw "no"
nix eval --file lazy_if.nix

Output:

"ok"

The throw is not forced. Invert the condition to see it.

The trap

The trap is if cond then x; with no else, copied from Bash. Parse error.

The other trap is [ 1, 2 ] with commas. Nix lists are spaces. Commas are attrset / function-arg style. [ 1, 2 ] is often a parse error or a surprising function call.

The boring rule

  • if always has else. Both sides are values.
  • Spaces in lists. ++ to join.
  • map / filter / length / elem from builtins.
  • optional / optionalAttrs (lib) for maybe-one-item.
  • Unused else throw stays quiet — good for flags, bad if you meant to test both.

Try this

  1. builtins.length already in Case 2; add builtins.elem 3 priorityTables.
  2. Write [ 1, 2 ] and read the error (or the odd parse).
  3. Case 5 with false.
  4. lib.optional — after you have pkgs.lib in the imports chapter, replace the if true then [ "htop" ] else [ ] pattern.