Keystone SystemsKS Systems

Declarative vs Procedural Programming

The distinction between declarative and procedural programming represents one of the most fundamental concepts in software engineering, particularly relevant when working with infrastructure and system configuration.

The Core Distinction

Procedural programming answers the question: "How do I accomplish this?"

You write step-by-step instructions that the computer executes in sequence. Each step mutates state and moves toward the goal.

Declarative programming answers the question: "What do I want?"

You describe the desired end state, and the system figures out how to achieve it.

Examples in Practice

SQL: Declarative

SELECT name, email FROM users WHERE active = true;

You don't tell the database how to find the rows. You declare what you want, and the query optimizer determines the most efficient path.

Shell Script: Procedural

if [ ! -d "/opt/myapp" ]; then
  mkdir -p /opt/myapp
fi
if [ ! -f "/opt/myapp/config.json" ]; then
  cp /tmp/config.json /opt/myapp/
fi
systemctl restart myapp

Each step checks current state and takes action. Run it twice, and you might get different results depending on timing.

Nix: Declarative

environment.systemPackages = [ pkgs.git pkgs.vim pkgs.htop ];
services.postgresql.enable = true;

You declare what packages should exist and what services should run. Nix computes the difference and applies changes atomically.

Why Declarative for Infrastructure

Idempotency

Declarative systems are naturally idempotent. Apply the same configuration multiple times, get the same result. No "did I already run that script?" anxiety.

Reproducibility

The configuration is the documentation. Give someone your Nix flake, they get your exact system. No "works on my machine" problems.

Version Control Friendly

Declarative configs are data, not scripts. Diffs show what changed in the desired state, not which commands were added.

Easier Rollbacks

Since you're describing complete states, rolling back means applying a previous state description. The system handles the transition.

Trade-offs

Declarative systems often have a steeper learning curve. You're not just learning a new syntax; you're adopting a different mental model.

Procedural approaches can be more flexible for edge cases. When you need precise control over execution order or conditional logic based on runtime state, imperative code is more natural.

The best infrastructure tools provide declarative interfaces with procedural escape hatches when necessary.

How Nix Embodies These Principles

NixOS takes declarative programming to the system level:

  • Your entire OS configuration is a single expression
  • Changes are computed as derivations (build recipes)
  • Applying a configuration doesn't mutate state; it builds a new system generation
  • Previous generations remain available for rollback

This isn't just convenience—it's a fundamental shift in how we think about system state.