DevOpsInterviewPrep logo
Infrastructure as Code & Configuration / 09
medium★ EssentialNewHashiCorpAccentureInfosys

When does duplicated Terraform code deserve extraction into a module, and how do you version modules so consumers do not break?

Extract too early and you maintain a wrapper nobody thanks you for. Here is the extraction trigger, the semver contract that maps onto Terraform surfaces, and the parallel-release path for breaking changes.

Updated Sep 2026 · Grounded in researched DevOps, SRE and platform engineering interview loops, written to a senior-engineer editorial bar, and never padded to hit a word count.

TL;DR: Extract a module when a second stack genuinely needs the pattern, not before, and publish it behind semantic version tags that consumers pin explicitly. A module is a product with consumers: breaking changes ship as a new major with a migration window, never as a surprise on main.

How to approach it

Frame the answer around two decisions: the extraction trigger and the compatibility contract. Interviewers screening for seniority want the failure mode of getting each wrong: wrapper modules with one caller, and pipelines that break on someone else's refactor.

A strong answer

Extraction has a trigger. Code in a root module stays there until a second stack needs it. Copy-paste once and watch; the second real consumer, with a shape that has stopped moving weekly, is the moment to extract. Extract earlier and you maintain an indirection layer with a single caller, plus every interface change now has a downstream victim. Draw the boundary around things that change together: a VPC with its subnets and routes, a queue with its DLQ and its alarms. A module spanning concerns that evolve on different clocks turns into the thirty-variable monster where every consumer passes defaults it does not care about.

The contract is the version tag. Publish modules as git tags or registry versions and consume them pinned:

module "queue" {
  source = "git::https://github.com/acme/tf-modules//sns-queue?ref=v3.2.0"

  name_prefix = "payments"
}

Semantic versioning maps cleanly onto Terraform surfaces. Major: a variable removed or retyped, an output dropped, a resource renamed underneath existing state. Minor: a new optional variable or a new output. Patch: compatible fixes and documentation. Changing a default can be breaking if existing consumers inherit different behavior. Anything that changes what an existing consumer's plan does is major regardless of how small the diff feels. Consumers pin an exact release and upgrade deliberately; ref=main in a production stack means every apply is an unreviewed upgrade, which is how a Tuesday refactor becomes a Friday incident.

Breaking changes ship in parallel. Release v4 while v3 continues to exist, migrate consuming stacks one pull request each, and retire v3 only after the last pin moves. Most migrations are mechanical: rename variables, re-pin, plan, confirm zero destroys. The dangerous case is a refactor that renamed resources inside the module, because consumer plans then show destroy-and-recreate against their state; provide retained moved blocks to preserve address compatibility where possible; a rename with a complete compatible migration need not itself require a major version.

Interviewers are often screening for scars here. If you have inherited a repository where forty stacks consume unversioned sources, describe the stabilisation: freeze on tags bottom-up, stand up a registry when discoverability hurts, wire a Renovate-style bot so version bumps arrive as PRs with plan output attached. That story lands better than theory.

What interviewers probe next

"A consumer needs a fix today and your release lands Thursday." Cut a prerelease tag such as v3.2.1-rc.1 from the fix branch, let them pin it, ship the real patch, move them off the prerelease. Private forks living past the incident are debt.

"Thirty variables on one module: what now?" Split along the axis that changes independently. Thirty knobs almost always mean two boundaries pretending to be one.

"Registry or plain git sources?" Registry adds version discovery and search; tagged git works with zero infrastructure. Protect release tags against mutation or pin a reviewed commit digest; a tag alone is a movable Git reference.

Common mistakes

Extracting on day one, before any interface exists, then mutating the API weekly while every caller absorbs the churn.

Consuming branch refs in production stacks, so changes to the module are reviewed nowhere.

Calling a variable rename a patch because the diff was tiny, then answering for every pipeline that broke at plan time.

Sources: Refactoring modules.

That one was free, and so are 10 answers per topic without an account. Signing in doubles that to 20, keeps your bookmarks, and tracks which topics you keep getting wrong.one Google click · no card · nothing to cancel
HOW DID IT GO?
0
UP NEXT ON YOUR JOURNEY
DISCUSSION · 0

Nothing here yet. Say how you would answer it.