Why is infrastructure-as-code usually split into reusable components or modules instead of describing the whole estate in one large configuration?
answer
- a function over infrastructure
- inputs, outputs, hidden implementation
- write the pattern once, call it many times
- smaller reviews, faster runs, contained mistakes
- one-resource wrappers buy nothing
basics
~20 sA component packages a pattern once behind a small input interface, so the tenth service is a few lines instead of a hundred. It also shrinks what each change touches: smaller reviews, faster runs, and a mistake that stops at one component instead of the whole estate.
solid answer
~50 sThree reasons, in the order I would argue them. **Reuse**: the standard shape of a service — network wiring, identity, logging, alarms — is written and reviewed once, and every consumer gets the reviewed version rather than a fresh copy-paste with its own mistakes. **Comprehension and review**: a reviewer reads a change to one named component with a declared interface, not a diff inside a thousand-line file. **Blast radius and speed**: when components are applied as separate units, a change to the service layer computes a diff over service resources only, so it is faster, and a bad change cannot enumerate the network or the database for destruction. The counterweight is that too many tiny components create wiring and ordering work of their own — decomposition is a judgement call, not a rule to maximise.
go deeper
Be ready to name reuse and readability, and to describe a component as something with inputs and outputs that hides its implementation. A concrete example — a service component called once per service — lands better than an abstract definition.
Explain that the interface is where standards get enforced: what varies becomes an input, what should not vary becomes a baked-in default. Note that code decomposition and apply-unit decomposition are different decisions.
Show the counterweight. Argue when not to decompose, describe the maintenance cost of an interface you can never break, and connect apply-unit boundaries to run time, lock contention and containment of a bad change.
Own the estate-level version: how a component library is governed, versioned and adopted across teams, who owns breaking changes, and how you avoid a shared library that becomes a bottleneck every team routes around.
## What a component actually is In every infrastructure-as-code tool the same idea appears under a different name — module, construct, component resource, role, stack template. Strip the naming and it is the same thing: **a named unit of infrastructure with a declared set of inputs, a declared set of outputs, and an implementation the caller does not need to read.** It is a function over infrastructure. Everything below follows from that. ## Reason one: reuse of a reviewed pattern An estate contains the same shape many times. "A service" means a compute unit, a load balancer target, an identity with a scoped policy, log retention, a couple of alarms, and consistent tags. Written inline, that is roughly a hundred lines per service, and the twelfth copy will quietly omit the log retention because someone was in a hurry. Written as a component, it is one reviewed implementation and twelve short call sites, each supplying a name, a size and a couple of flags. The subtler benefit is that improvements propagate. When you add a required tag or fix an over-broad permission in the component, every consumer picks it up the next time they take a new version. With copy-paste, you have twelve places to find and a grep that misses two of them. ## Reason two: the interface is where the thinking happens A component forces you to answer "what legitimately varies here?" The answer becomes the input list. What does *not* vary becomes an opinion baked into the implementation — encryption on, public access off, retention at ninety days. That is how organisational standards actually get enforced in practice: not by a document, but by being the default inside the component that everyone calls. A good interface is small and value-shaped: sizes, counts, names, CIDRs, retention. A poor one exposes every underlying knob, at which point the component is not an abstraction, it is a passthrough with extra indirection. ## Reason three: review scope A reviewer's attention is finite. A change described as "bump the service component from 2.3.0 to 2.4.0 in the payments environment" is reviewable: they read the component's changelog and the preview of what will change. A forty-line diff buried in the middle of a monolithic configuration file is reviewed by scrolling, which is not review. ## Reason four: blast radius and run time This reason applies to *separately applied* units rather than merely separately written ones, and the distinction matters. Writing components does not by itself split the recorded state; you also have to apply them as independent units. Once you do, three things improve: each run reads and diffs fewer resources, so it finishes in seconds instead of many minutes; a mistake in one unit produces a plan that cannot even name the resources in another; and two teams can change their own areas concurrently instead of queueing behind one lock. ``` monolith: one run, 900 resources, 11 minutes, everyone waits composed: network | platform | service-a | service-b four runs, minutes each, independent owners ``` ## The honest counterweight Decomposition is not free, and an interviewer will respect you more for saying so. Every boundary you introduce becomes an interface you must version and keep stable, and every cross-boundary dependency becomes wiring — component A's output has to reach component B somehow, and now there is an ordering relationship a human has to know about. A component wrapping a single resource with a one-to-one input list adds indirection and buys nothing; you now read two files to learn what one resource does. The practical heuristic: create a component when the pattern is used more than once, or when it encodes a decision you want enforced. Create a separately applied unit when the pieces have different change rates, different owners, or different consequences of failure. Otherwise leave it inline and split later — merging code is easy, and the split can be done when the pain is real. ## What a weak answer sounds like "Modules keep the files shorter." File length is a symptom, not the reason. Reuse of a *reviewed* pattern, an interface that encodes standards, reviewable change units and contained blast radius are the reasons; shorter files are what you notice on the way past.
- When is wrapping something in a component the wrong call?When it is used once, or when the component's inputs map one-to-one onto the resource it wraps. That is indirection without abstraction — a reader now opens two files to learn what one resource does, and the wrapper's interface has to be maintained forever. Write it inline and extract it the second time you need it.
- Does splitting code into components automatically reduce blast radius?No. Components are a code-organisation move; blast radius follows the unit you apply and the record it diffs against. If ten components are still applied together from one composition against one recorded state, a bad change still computes over everything. Reducing blast radius means applying them as independent units with independent records.
saying these in an interview costs you the question
- Modules exist mainly to make files shorter.
- Wrap every single resource in its own module.
- Expose every underlying option as a module input, just in case.
- Splitting code into modules automatically shrinks blast radius.
- One big configuration is fine because the tool works out the order.