skip to content

What rules about a Terraform input variable can a `validation` block enforce that a `type` constraint cannot?

level: middleimportance: must knowfreq 55%

answer

  1. shape versus value
  2. no enum type in HCL
  3. ranges, regexes, lengths
  4. one variable checked against another
  5. would the wrong value still parse?

basics

~20 s

A type constraint checks the shape of a value — string, number, an object with named attributes. A validation block checks the value itself: allowed set, numeric range, naming pattern, collection length, and cross-field rules that no type can express.

solid answer

~50 s

The `type` argument answers "is this the right kind of thing" — a string, a number, a list of objects with these attributes. It cannot answer "is this a sensible thing". Everything value-shaped needs a `validation` block: membership in an allowed set like dev/staging/prod, a numeric range such as retention days between 1 and 365, a naming pattern expressed with `regex`, a minimum or maximum collection length, or a structural fact such as a subnet CIDR having to sit inside the VPC CIDR. From Terraform 1.9 a validation condition may also reference other objects, which is what makes cross-field rules — this variable must be set when that one is true — expressible at all. The practical test I apply is: if the wrong value would still parse but would build the wrong infrastructure, the type system will not save you and a validation rule will.

code

hcl · 22 lines
hcl
variable "vpc_cidr" {
  type = string

  validation {
    condition     = can(cidrnetmask(var.vpc_cidr))
    error_message = "The vpc_cidr value must be a valid IPv4 CIDR block."
  }

  validation {
    condition     = tonumber(split("/", var.vpc_cidr)[1]) <= 20
    error_message = "The vpc_cidr prefix must be /20 or larger to fit the planned subnets."
  }
}

variable "availability_zones" {
  type = list(string)

  validation {
    condition     = length(var.availability_zones) >= 2
    error_message = "At least two availability zones are required for a highly available deployment."
  }
}

go deeper

for a junior

Remember the one-line distinction: type says what kind of value, validation says which values are acceptable. Have one concrete example ready, such as restricting an environment variable to dev, staging or prod.

for a middle

Be able to name the families — allowed set, numeric range, naming pattern, collection length, cross-field — and write the for plus alltrue idiom for validating every element of a list. Know types come first and validations second.

for a senior

Show the judgment call: which module assumptions deserve to be enforced at the interface versus documented. Mention that cross-variable conditions need Terraform 1.9 and that older codebases moved those rules into preconditions.

for a principal

Talk about validations as the contract of a platform module — the mechanism by which a small team encodes standards for many consumers, and the reason error message wording is a design decision, not a comment.

## Two different questions Every input variable is checked twice before Terraform will use it, and the two checks answer different questions. The `type` argument answers **is this the right kind of thing**. `string`, `number`, `bool`, `list(string)`, `map(number)`, `set(string)`, and structural types like `object({ name = string, size = number })` or `tuple([string, number])`. Terraform will convert where it safely can and reject where it cannot. Structural types can go surprisingly far — an attribute can be declared `optional()` with a default — but everything the type layer knows is about shape. A `validation` block answers **is this a sensible thing**. It is a boolean rule about the value. The dividing line that matters in practice: *if a wrong value would still satisfy the type and would still build — just build the wrong thing — the type system cannot help you.* `"Production"` is as valid a string as `"prod"`. `0` is as valid a number as `30`. `"10.0.0.0/8"` is as valid a string as any bucket name. ## The five families of rule you actually write **Allowed set.** The most common by a distance. An environment, an instance class, a log level. `contains(["dev", "staging", "prod"], var.environment)`. There is no enum type in HCL, so this rule *is* the enum. **Numeric range.** Retention days, replica counts, ports, timeouts. ```hcl variable "retention_days" { type = number validation { condition = var.retention_days >= 1 && var.retention_days <= 365 error_message = "The retention_days value must be between 1 and 365." } } ``` **Naming pattern.** Cloud resource names have real syntax rules — lengths, allowed characters, no leading digit, lowercase only. `can(regex("...", var.name))` expresses them, and `can()` is there because `regex()` raises an error rather than returning `false` when it does not match. **Collection shape and content.** `length(var.subnets) >= 2` for a module that requires two availability zones. `alltrue([for p in var.ports : p > 1024])` to require every element of a list to satisfy something — the `for` expression plus `alltrue` idiom is how you validate collections element-wise. **Structural or cross-field rules.** A subnet CIDR that must be inside the VPC CIDR. A `backup_bucket` that must be set whenever `backups_enabled` is true. These involve either arithmetic no type can perform, or two variables at once. Cross-variable references inside a validation condition became legal in Terraform 1.9; before that, a condition could refer only to its own variable, and teams worked around it with a `null_resource` or a contrived local. If you are targeting an older version, say so, and put the cross-field rule in a `precondition` instead. ## Where types are still the better tool Do not turn validation into a hand-rolled type system. If the value is a number, declare `type = number` rather than accepting a string and validating that it parses. If it is a record of three fields, declare an `object({...})` rather than a `map(string)` plus five validations checking which keys exist. A type failure produces a clear message for free; a validation is code you have to write and maintain. Types first, values second. ## Why this earns points in an interview Because it is where a module stops being a pile of resources and starts being an interface. The module author knows things the caller does not: that this CIDR must be at least a /20 to fit the subnets it will carve, that this name becomes part of a DNS record and cannot contain an underscore. Encoding those as validations turns tribal knowledge into an error message that arrives at the moment somebody gets it wrong, in the terminal they are already looking at — at plan time, with no cloud calls, no CI minutes and no half-built environment.

  • How do you validate that every element of a list variable satisfies a rule, not just the list as a whole?
    Combine a `for` expression with `alltrue`: `alltrue([for p in var.ports : p > 1024])`. The `for` expression maps each element to a boolean and `alltrue` collapses them into one result. `anytrue` is the same idiom for "at least one must". The error message should name the rule, since it will not tell you which element failed.
  • When would you push a rule into the type system instead of writing a validation for it?
    Whenever the rule is really about shape. Declare `type = number` rather than accepting a string and validating that it parses; declare `object({ name = string, size = number })` rather than a `map(string)` plus validations checking which keys exist. Types produce a clear error for free and are less code to maintain — validations are for values types cannot describe.
  • A module needs "backup_bucket must be set when backups_enabled is true". Where does that rule go?
    On Terraform 1.9 or later it can be a `validation` on either variable, because conditions may now reference other objects. On earlier versions a validation could only see its own variable, so the rule has to move to a `precondition` in a `lifecycle` block on the resource that consumes it, which has always been able to reference the whole module.

saying these in an interview costs you the question

  • Believes a type constraint can express an allowed set of values
  • Validates that a string parses as a number instead of typing it number
  • Reinvents object types as a map plus key-existence validations
  • Assumes cross-variable references always worked in validation conditions
  • Uses regex for everything, including simple membership checks

context