skip to content

A shared infrastructure component has grown a boolean or environment-name input for nearly every per-environment difference. Why is that a problem, and what would you do instead?

level: middleimportance: should knowfreq 42%

answer

  1. flags multiply, tests do not
  2. the prod-only path is the least exercised
  3. parameterise values, not resource existence
  4. push structural differences up to the caller
  5. wrong abstraction costs more than duplication

basics

~20 s

Every flag doubles the number of code paths, and only the combinations your environments actually use are ever exercised — so the production path is the least tested one. Prefer parameterising values, pushing structural differences up into the caller, or accepting two explicit definitions.

solid answer

~50 s

Two problems. First, combinatorics: ten booleans are a thousand possible shapes, of which you exercise three, and the one that only production uses is the one nobody has ever run. Second, the component has stopped being a reusable building block and become a container for environment policy — it now knows what production means, so it cannot be reused anywhere new without teaching it a new environment. My rule of thumb is *parameterise values, not the existence of resources*: sizes, counts, retention and names are legitimate inputs, but a flag that toggles whether a resource exists at all is a fork hiding inside a shared file. Push that difference up so the caller composes an extra piece, split the component if the two shapes genuinely diverged, or accept the duplication — two clear definitions often cost less than one abstraction bent to cover both.

go deeper

for a junior

Know that a shared component should take simple values like size and count rather than a long list of on/off switches, and that passing the environment name in and branching on it is a smell.

for a middle

Explain the combinatorics — n booleans means 2^n possible shapes and only a handful ever built — and articulate the value-versus-structure line that decides whether something belongs as an input at all.

for a senior

Show judgement about when to stop abstracting. Argue duplication as a deliberate choice, explain how you would refactor a flag-heavy component without recreating live resources, and name the review cost of conditional-heavy code.

for a principal

Own the library policy: what a shared component is allowed to know, how environment policy is expressed and reviewed where it is visible, and how you keep a central component library from becoming a bottleneck teams route around by forking.

## How it happens Nobody designs this. It accretes. Production needs a replica; add `enable_replica`. Dev should not pay for backups; add `enable_backups`. Staging needs a public endpoint for a partner test; add `public_endpoint`. Each addition is individually reasonable and takes five minutes. Two years later the component takes forty inputs, half of them booleans, and reading it means holding a truth table in your head. ## Problem 1: untested combinations With *n* independent booleans there are 2^n possible shapes. You use three of them — one per environment. The rest have never been instantiated, which would be fine if they were unreachable, but they are not: they are one review away, and the day someone flips a flag in a hurry they instantiate a combination that has never existed anywhere. Worse is the asymmetry. The flags that are *on* in production and *off* everywhere else describe exactly the code path that has never run in a safe environment. The abstraction is thinnest where the consequences are highest. ## Problem 2: the component now encodes policy A reusable component should describe *how to build a thing*. Once it contains `if environment == "prod"`, it also describes *what your organisation does in production*, which is a completely different kind of knowledge with a completely different rate of change. Two consequences follow. The component cannot be reused by a team whose environments are named differently without editing it. And the policy is now invisible to anyone reading the environment's configuration — the call site says `environment = "prod"` and gives no hint that this quietly turns on cross-region replication and seven-year retention. An environment-name input is the worst version of this, because it is a flag that fans out to many behaviours at once and hides every one of them. ## Problem 3: reviewing a change becomes simulation To review a change to a heavily conditional component, you cannot just read it — you have to mentally execute it under each environment's flag set. Reviewers stop doing that quickly, and then the component is effectively unreviewed. ## What to do instead **Parameterise values, not the presence of resources.** This is the single most useful line to draw. `instance_size`, `replica_count`, `retention_days`, `cidr_block` are values; every environment takes the same code path and supplies different numbers. `enable_replica` is not a value — it selects between two structures. When `replica_count` can be zero, you have often converted a structural flag into a scalar, and that is a real improvement. **Push structural differences up to the caller.** If production needs an extra piece of infrastructure, let production's configuration compose that extra piece explicitly. The shared component stays one shape; the difference is visible at the site where the decision was actually made. ``` before: service_component(env = "prod") # component decides, invisibly after: service_component(size = large, replicas = 3) plus, only in the prod composition: replica_component(...) # the difference is on screen ``` **Split the component.** If two callers use disjoint halves of the inputs, they were never one component. Two smaller, coherent components — each with a small honest interface — beat one that covers both by branching. **Accept duplication.** The least popular and often the correct answer: two explicit definitions can be cheaper than one abstraction contorted to cover both. Duplication costs you a change applied twice, which is visible and mechanical. The wrong abstraction costs you every future reader's comprehension and every future change fighting a shape that does not fit — a much larger bill, paid silently. Extract the abstraction later, once the two cases have stopped diverging and you can see the real shared shape. **Presets over flags.** When several inputs move together, a small named set of input bundles — one per environment tier, defined at the caller — beats individual flags. The bundle lives in the environment's configuration where it is readable, rather than inside the component where it is not. ## The honest caveat Some flags are legitimate. A component supporting two genuinely different but equally supported modes — public or private endpoint, say — with both modes exercised by real consumers and documented is fine. The defect is not conditionals; it is *unexercised* conditionals encoding environment policy inside a unit that is supposed to be environment-agnostic. The test question is simple: **is this code path instantiated somewhere other than production?** If not, it is not an abstraction, it is a production-only branch that nobody has tested.

  • Which inputs are legitimate on a shared component, then?
    Values that describe the same structure at a different scale or with a different name: size, count, retention, CIDR, tags, identifiers. They keep every environment on one code path. The line to watch is any input that changes whether a resource exists or which of two structures gets built — that selects a shape, and shapes are the caller's business.
  • Is duplicating a component ever the better answer than parameterising it?
    Yes, when the two cases have genuinely diverged or you cannot yet see the shared shape. Duplication has a visible, mechanical cost — apply the change twice — while a bad abstraction taxes every reader and every future change invisibly. Duplicate, wait until the common shape is obvious, then extract it deliberately rather than guessing early.
  • Why is passing the environment name into a shared component worse than passing individual flags?
    Because one input silently selects many behaviours, and none of them are visible at the call site. It also welds the component to your naming, so a new environment or another team's tier cannot use it without editing the component. Pass the behaviours you want as explicit inputs and let the caller decide what its environment means.

saying these in an interview costs you the question

  • Just add another flag; the component already has twenty.
  • Pass the environment name in and let the component decide everything.
  • Duplication is always worse than an abstraction, whatever the cost.
  • Untested flag combinations are fine because nobody sets them.
  • The component is reusable because it takes lots of inputs.

context