In Rego, what is the difference between an expression that is undefined and one that is false?
answer
- three outcomes, not two
- false is a value; undefined is not
- a missing key yields no value
- one undefined expression collapses the body
- no result, no error, no denial
basics
~20 sFalse is a value; undefined means Rego found no value at all. Both stop a rule body, but false is an answer, while undefined leaves the rule with no result to emit and no error to report.
solid answer
~50 sA Rego expression has three possible outcomes: it produces a value, it is undefined, or it raises an error. `input.spec.replicas > 3` is false when `replicas` is 2 — a real comparison that came back negative. It is *undefined* when the object has no `replicas` field at all: there is nothing to compare, so the expression yields no value and Rego moves on. Undefined propagates. One undefined expression makes the whole rule body undefined, so a complete rule has no value at that path (unless a `default` supplies one) and a partial rule such as `deny contains msg` contributes no element. Nothing is logged and nothing fails. That is why a typo in a field path does not produce an error message — it produces a policy that quietly stops deciding, and an empty deny set that a gate happily reads as a pass.
go deeper
Be ready to state the three outcomes of a Rego expression — a value, undefined, or an error — and to say plainly that a missing input field gives you undefined, not false and not a crash.
Explain how undefined propagates: one unreachable reference makes the whole body undefined, so a complete rule has no value and a partial rule contributes no element. Know that default supplies a constant for a complete rule only.
Show that you never read an empty result as compliance. Demonstrate how you prove a rule can still deny — a known-bad fixture asserted in CI — and how you make an unreadable document shape fail loudly rather than pass quietly.
Own the consequence for assurance: a control whose failure mode is silence produces evidence that looks identical whether it worked or not. Be able to say what your platform requires of every rule before it counts as an enforced control.
## Three outcomes, not two Most languages a policy author comes from have two outcomes for a boolean test: true or false, with an exception if something is badly wrong. Rego has three, and confusing two of them is the single most common source of policies that appear to work and enforce nothing. 1. **A value.** The expression evaluated and produced something. In Rego an expression is *true* — that is, the body continues — when it is defined and not `false`. So `input.replicas` is a true expression when `replicas` is `3`, and also when it is `0`, `""`, `[]` or `{}`. Rego has no JavaScript-style truthiness: only the literal `false` is falsy. 2. **Undefined.** The expression has no value. There were no bindings that satisfied it. The commonest cause is a reference into data that is not there: `input.metadata.labels.owner` when the object carries no `labels` key. Comparisons with an undefined operand are undefined too, and so is iteration over a collection that does not exist. 3. **An error.** Something is wrong with the policy itself or with evaluation. Parse errors, unsafe variables and type errors are caught when the policy is compiled and loaded, so the policy never runs at all. Runtime errors are subtler: by default OPA treats a failing built-in function as *undefined* rather than as a hard error, so an expression like `to_number(x)` on a non-numeric string simply drops the rest of the body. (`opa eval --strict-builtin-errors` turns that into a visible error instead.) ## Why the difference matters False and undefined both stop a rule body, which is exactly why they get conflated. The difference is what they tell you afterwards. - **False is an answer.** The rule looked at the object, ran the comparison, and the comparison did not hold. Absence of a denial genuinely means the object satisfied the check. - **Undefined is silence.** The rule could not even ask the question. Absence of a denial means nothing whatsoever about the object. Because undefined propagates, a single unreachable reference anywhere in a body collapses the entire rule. If a rule reads `input.spec.template.spec.containers` and the document under evaluation is a bare Pod (`spec.containers`) or a CronJob (`spec.jobTemplate.spec.template.spec.containers`), the reference is undefined, the loop never runs, the body never completes, and the rule emits nothing. Querying that rule comes back with no result, not with `false` and not with an error. ## How the rule shape reacts A **complete rule** — `allow if { ... }` — is undefined at its path when every one of its bodies is undefined. Nothing lives at `data.example.allow`. This is what `default` exists for: `default allow := false` gives the rule a constant value to fall back to when no body produced one. `default` attaches only to a complete rule; you cannot put one on a partial rule, and OPA rejects the policy at load time if you try. A **partial rule** — `deny contains msg if { ... }` — builds a set. Each body that is satisfied adds an element; each body that is undefined adds nothing. A rule whose body is undefined for every candidate leaves the set empty, which is indistinguishable from a set that is empty because everything was compliant. ## The practical consequence Every well-known way a policy silently stops working goes through undefined: - a field path that was renamed, moved or mistyped; - a document shape the rule never anticipated; - a built-in that errored and degraded to undefined; - a guard expression whose value was never present in the input the engine actually receives. None of these produce an error, a log line or a metric. They produce green. The defences follow directly. Keep a deliberately non-compliant fixture for every rule and assert in CI that it *does* produce a denial — a rule that cannot deny its own counterexample is broken regardless of what it does in production. Give complete rules an explicit `default` so the decision always has a value. Consider a companion rule that denies any document whose expected structure cannot be read, so an unexpected shape becomes a visible failure instead of a quiet pass. And when you read a report saying zero violations, ask first whether the rule was able to look. ## The one-line version False means "I checked and it was fine." Undefined means "I never checked." A gate that cannot tell those apart is not a gate.
- Are an empty string, zero or an empty array treated as false in Rego?No. An expression in Rego is true when it is defined and not `false`, so `""`, `0`, `[]` and `{}` all make a bare term expression succeed. Only the literal `false` and undefined stop a body. Candidates coming from JavaScript or Python frequently assume otherwise and write guards that never filter anything.
- What happens to a complete rule whose body is undefined?The rule has no value at its path — the virtual document simply has nothing there, and a query for it returns no result rather than `false`. Adding `default allow := false` gives the rule a constant fallback that applies whenever every body is undefined. The default value must be a constant; it cannot reference `input` or `data`.
- Does an undefined expression ever show up as an error you can alert on?Not by itself. Undefined is normal, expected control flow in Rego — it is how every non-matching rule behaves — so there is nothing to log. You only get an error for compile-time problems such as a parse failure or an unsafe variable, or at runtime if you have opted into strict built-in errors.
False is a witness who takes the stand and answers no. Undefined is a witness who never showed up — the trial simply proceeds without them, and nobody announces the absence.
saying these in an interview costs you the question
- Says undefined behaves like false in a rule body
- Assumes a missing input field raises an error
- Thinks an empty string or zero is falsy in Rego
- Expects an undefined rule to return false to the caller
- Treats an empty deny set as proof the object was compliant