This article was formatted for the web. The original document is available here.
Ask any infrastructure engineer:
"Is Kubernetes declarative?"
→ They will likely say yes.
"Is SQL declarative?"
→ Yes.
"Is OpenTofu declarative?"
→ Yes.
Then ask:
"Why do you need Helm?"
Or "Why do you use query hints?"
Or "Why does OpenTofu have for_each and count?"
The answers reveal a contradiction. Systems marketed as declarative consistently require imperative escape hatches when applied to complex problems.
Let us define declarativity not as a binary property but as a function:
Where represents a bounded use case — the "canon" within which the system's design matches user needs without requiring imperative logic.
Conversely, let represent imperative operation when context expands beyond the canon:
In English: If a system works declaratively in a bounded context, there exists some expanded context where it will fail to be declarative.
When a system encounters context beyond its declarative boundary, the user must employ an imperative escape hatch. This escape hatch, once useful, is often wrapped in a new "declarative" abstraction, creating a recursive tower:
Each layer presents a declarative interface that internally contains the imperative logic of .
Let = "deploy a single static pod". = Kubernetes YAML is declarative for .
Now = "deploy the same app across multiple environments". Kubernetes cannot express this declaratively. The escape hatch is Helm — a template engine with conditionals and loops, which is imperative generation.
Let be Helm. It works, but now = "deploy multiple Helm releases with ordering". Helm cannot express this declaratively. The escape hatch is Helmfile — an imperative orchestrator.
This continues. = "custom runtime logic". Kubernetes cannot express this declaratively. The escape hatch is Operators — imperative controllers written in Go.
= "manage Kubernetes clusters themselves". Operators cannot express this declaratively. The escape hatch is Terraform/OpenTofu — a declarative wrapper around imperative cluster management.
The recursion does not terminate:
Each layer presents a declarative interface. Each layer wraps the imperative escape hatch of the layer below. The recursion continues as long as new contexts emerge.
Let represent the "easiness" of a system — the cognitive load required to use it declaratively within a given context. For the sampled use case , easiness is high:
As the context expands beyond the canon, easiness degrades. The system becomes harder to learn, harder to reason about, and harder to debug.
There exists a decay function mapping context expansion to easiness degradation. Its exact shape requires empirical study; as a first heuristic, we propose the exponential form:
Where:
: Initial easiness within the canonical context
: Decay rate (how quickly complexity accumulates)
: Degree of context expansion beyond the canon
: Asymptotic easiness of honest imperative tooling
The exponential form captures a plausible degradation pattern: rapid initial decay as the first escape hatches are encountered, tapering toward a baseline of imperative complexity. However, the exact functional relationship — whether exponential, power law, step function, or other — remains an open question. The central claim is not the specific shape but the existence of decay itself.
Empirical observation: Attempts to extend declarative systems to broader contexts inevitably reduce the easiness advantage that motivated their adoption in the first place. The system becomes harder to use exactly where it needs to be most powerful.
We can test any system for declarative purity using a simple criterion:
A system is purely declarative for context if and only if the user never needs to express:
Ordering (then, before, after)
Iteration (for, while, repeat)
Conditionals (if, case, switch)
State management outside the system's own model
true — the simplest Unix command — is purely declarative when the context remains under "return a successful exit status code of 0". It becomes imperative as soon as the context extends beyond that initial design:
export VAR=true
if [ "$VAR" = "true" ]; then
echo "Now true is data, not a command"
fi The symbol true has escaped its declarative canon.
The most subtle aspect of this formalism is the recursive application of declarative procedures:
Each application of a declarative system to solve a problem expands the effective context. Expanded context reveals edges where the system is not declarative. Those edges become wrapped in new abstractions. The new abstractions claim to be declarative, but they merely hide imperativity one level deeper.
This is not a bug. It is a necessary condition for any system that solves complex problems. The only purely declarative system is one that is never used for anything else than its original bounded context.
No tool is inherently declarative. Design for bounded contexts. Acknowledge escape hatches explicitly. The attempt to make a tool "fully declarative" leads to complexity that erodes its value.
When evaluating a declarative tool, ask: "What is the sampled use case? Where are the escape hatches? How deep does the recursion go?" The declarative claim is marketing. The escape hatches are engineering.
Declarativity is not a property of a system. It is a relationship between a system and a bounded context. Expand the context, and the declarative illusion shatters. Recursively apply declarative tools, and you build towers of imperative escape hatches wrapped in declarative syntax.
The industry's obsession with "declarative infrastructure" obscures the real engineering: understanding the boundaries of each abstraction, knowing where the imperative escapes live, and accepting that every useful system is imperative at the edges.
The recursive twist is final proof: if you manage to stay declarative as context expands, you lose the easiness that made declarativity desirable in the first place. You cannot have both.
Let:
= system
= context (use case)
= declarativity function
= imperativity function
= easiness function
= transformation (wrapping imperative logic in declarative interface)
Then:
This recursion is unbounded:
And the easiness function decays:
Q.E.D.