What does cidrsubnet("10.0.0.0/16", 8, 3) return in Terraform, and what do the two numeric arguments mean?
answer
- newbits extends, netnum selects
- how many subnets does newbits give
- index three of a sixteen-bit space
- the plural form packs a series
- positional indexes renumber on insert
basics
~20 sIt returns "10.0.3.0/24". The second argument is newbits - how many bits to add to the given prefix length, so /16 plus 8 becomes /24. The third is netnum - the index of which of those equally sized subnets to return, counting from zero.
solid answer
~50 s`cidrsubnet(prefix, newbits, netnum)` carves a smaller network out of a larger one. `newbits` extends the prefix length: a `/16` plus 8 newbits gives `/24`, which yields 256 candidate subnets. `netnum` selects one of them by index, so index 3 of `10.0.0.0/16` is `10.0.3.0/24`. Asking for a netnum that does not fit in `newbits` bits is an error rather than a wrap-around. The related functions are `cidrsubnets`, which allocates a whole series in one call and lets each have a different size, `cidrhost` for a specific address inside a prefix, and `cidrnetmask` for the dotted-quad mask. The operational caution matters more than the arithmetic: netnum is usually derived from a position in a list, so inserting an entry near the front shifts every subsequent subnet's address — and changing a subnet's CIDR is a destroy-and-recreate, not an in-place update.
code
hcl · 15 linesvariable "vpc_cidr" {
type = string
default = "10.0.0.0/16"
}
locals {
# positional: inserting a network shifts every later address
fragile = [for i in range(3) : cidrsubnet(var.vpc_cidr, 8, i)]
# keyed: each network owns a stable index
stable = {
for name, i in { public-a = 0, public-b = 1, private-a = 2 } :
name => cidrsubnet(var.vpc_cidr, 8, i)
}
}go deeper
Be able to state the three arguments and compute a simple case: newbits is added to the prefix length, netnum picks which of the resulting subnets you get, counting from zero.
Explain how many subnets a given newbits yields, why an out-of-range netnum errors instead of wrapping, and what cidrsubnets, cidrhost and cidrnetmask each add.
Show the operational judgment: netnum derived from list position renumbers on insertion, a CIDR change forces replacement of live subnets, and keying the index explicitly is what makes an additive change safe.
Own address-space allocation as an estate-level decision — who hands out ranges, whether modules compute them or receive them, and how you keep a growing multi-account network from colliding. Computed CIDRs are DRY but opaque to audit, and that tradeoff is yours to set.
## The arithmetic ```hcl cidrsubnet("10.0.0.0/16", 8, 3) # => "10.0.3.0/24" cidrsubnet("10.0.0.0/16", 4, 2) # => "10.0.32.0/20" cidrsubnet("10.0.0.0/16", 8, 255) # => "10.0.255.0/24" ``` Read it as: *take this prefix, make it `newbits` bits longer, and give me subnet number `netnum` of the result.* The new prefix length is `prefix_length + newbits`, and there are `2^newbits` subnets available, indexed `0` to `2^newbits - 1`. In the second example, `/16 + 4 = /20`; each `/20` holds 4096 addresses, which is 16 whole third-octet steps, so index 2 lands on `10.0.32.0`. Requesting a netnum that does not fit — `cidrsubnet("10.0.0.0/16", 4, 20)` when only 16 subnets exist — is an error. Terraform does not wrap around or truncate. ## Why it exists Without it, network layouts get hardcoded: a variable per subnet, or a list of literal CIDR strings that a human calculated once and nobody dares touch. `cidrsubnet` lets a module take one address-space input and derive the rest, so the same code stands up a network in any region or account by changing one string: ```hcl variable "vpc_cidr" { type = string } locals { # three /24s carved out of whatever space the caller gave us subnet_cidrs = [for i in range(3) : cidrsubnet(var.vpc_cidr, 8, i)] } ``` `range(3)` produces `[0, 1, 2]`, and the `for` expression maps each index through the function. ## The companion functions - **`cidrsubnets(prefix, newbits...)`** — allocates a *series* in one call, and each `newbits` argument may differ, so you can hand out one `/22` and several `/24`s from the same space without computing offsets by hand. It packs them consecutively, which is usually what you actually want. - **`cidrhost(prefix, hostnum)`** — returns a single host address within a prefix, useful for a fixed gateway or resolver address. - **`cidrnetmask(prefix)`** — returns the dotted-decimal mask; only meaningful for IPv4. ## The trap worth talking about The function is pure arithmetic and always correct. The risk lives in where `netnum` comes from. When it is derived from a position in an input list, the mapping between a logical subnet and its address is positional — so inserting a new entry in the middle of the list renumbers everything after it. Because a subnet's address range cannot be changed in place, that renumbering is a plan full of destroy-and-recreate on live networks, and everything attached to those subnets goes with them. The defence is to make the index stable rather than positional: derive `netnum` from a key you control — an explicit map of name to index — so adding a network appends a fresh index and never disturbs the existing ones. ```hcl variable "subnet_indexes" { type = map(number) default = { public-a = 0, public-b = 1, private-a = 2 } } locals { cidrs = { for name, i in var.subnet_indexes : name => cidrsubnet(var.vpc_cidr, 8, i) } } ``` Now a new entry with index 3 changes nothing that already exists, and the allocation is documented in the configuration rather than implied by list order. ## Readability tradeoff Computed CIDRs are DRY but opaque: a reviewer reading `cidrsubnet(var.vpc_cidr, 8, 7)` cannot tell what address that is without doing the arithmetic. On a small, stable estate, an explicit map of literal CIDRs is often easier to review and audit against a network diagram. On a module that stands up the same layout in many accounts, computing them is the only sane option. Being able to argue both sides — rather than reciting the arithmetic — is what distinguishes the answer.
- How many subnets can cidrsubnet produce from a /20 with newbits set to 4, and what is the resulting prefix length?Sixteen subnets, each a /24. newbits adds to the prefix length, so /20 plus 4 is /24, and 2^4 = 16 subnets are available with netnum valid from 0 to 15. Asking for netnum 16 is an error — Terraform rejects an index that does not fit in newbits bits rather than wrapping.
- When would you use cidrsubnets rather than several cidrsubnet calls?When the subnets differ in size or you want them packed consecutively without computing offsets. `cidrsubnets` takes a list of newbits values and allocates each one in sequence, so a /22 followed by three /24s comes out correctly laid out. Separate cidrsubnet calls force you to work out each netnum by hand.
- Why is a subnet CIDR change so much more dangerous than most argument changes?An address range is not modifiable in place, so the provider plans a destroy and recreate — and every resource attached to that subnet is affected. That is why the source of netnum matters: an index derived from list position renumbers everything after an insertion, turning an additive change into a rebuild of live networking.
saying these in an interview costs you the question
- Thinks newbits is the resulting prefix length
- Expects netnum to wrap around when it overflows
- Believes the function validates against real allocations
- Derives netnum from list position and calls it stable
- Assumes a CIDR change updates a subnet in place