diff.expert
TechniquesExercisesReference
© 2026 diff.expertAn Arbiter course
Home/Techniques/Configuration-Driven Behavior
Technique 11

Configuration-Driven Behavior

Make the system configurable first, then configure it.

←PreviousTest-First Decomposition

When to Use

You're changing business rules, thresholds, algorithms, or behavior that could be driven by external configuration rather than hard-coded values. The technique separates the "make it possible" step from the "make it happen" step.

The first PR makes the behavior configurable (with the default matching current behavior), which is a zero-risk change. The second PR changes the configuration value, which is a one-line change that's trivially reviewable and easily reversible.

You'll recognize this pattern when you find yourself about to change a literal value in code — that's your signal to extract it first.

The Pattern

  • PR 1 — Extract: Pull the hard-coded value into a configuration parameter (environment variable, config file, constant object). The default value matches the current behavior exactly. Zero behavior change. All existing tests pass unchanged.
  • PR 2+ — Configure: Change the configuration value to enable the new behavior. Each config change is its own tiny, trivially reviewable PR — often a single line.

The discipline is keeping these two steps separate. The extraction is zero-risk because nothing changes. The configuration change is trivially reviewable because there's nothing else to look at.

Commit Sequence

How this looks in your git history:

Worked Example

The team needs to update several business rules in an e-commerce checkout flow: increase the free shipping threshold from $50 to $75, change the maximum cart size from 20 items to 50 items, and increase the session timeout from 30 minutes to 2 hours.

Each of these is currently a magic number buried in the code. We'll extract them all into a typed config object first, then change the values in separate PRs — demonstrating how configuration extraction turns multi-file changes into single-line changes.

1

PR 1: Extract checkout business rules into configuration object

All magic numbers in the checkout flow are extracted into a single checkoutConfig object with a CheckoutConfig interface. The default values are identical to the current hard-coded values — this is a zero-behavior-change PR.

After this PR, every business rule is in one place. Reviewers can see the full shape of the system's configurable behavior at a glance. The extraction touches four files, but every change is mechanical: a reference to a literal becomes a reference to a config field.

src/checkout/checkoutConfig.ts
line numberline content
2

PR 2: Raise free shipping threshold and increase max cart size

Two configuration values change: the free shipping threshold rises from $50 to $75, and the maximum cart size increases from 20 to 50 items.

This is the entire PR. The business rule changes are isolated from the code that implements them — there's nothing here to misread, no control flow to trace, no side effects to consider. The diff speaks for itself.

src/checkout/checkoutConfig.ts
line numberline content
3

PR 3: Extend session timeout to 2 hours

The session timeout is updated from 30 minutes to 120 minutes (2 hours). This change is deployed separately from PR 2, because each config change is independently reviewable and independently reversible.

One line. One value. One concern.

src/checkout/checkoutConfig.ts
line numberline content

Common Mistakes

Over-configuring. Not every literal value needs to be a config parameter. Only extract values that represent business rules likely to change, or that you're actively planning to change. Math.PI doesn't need to be configurable. A 100ms debounce delay probably doesn't either. The signal is: is this a business decision or a technical constant? Business decisions belong in config. Technical constants belong in code.

Not validating configuration values. What happens if someone sets the free shipping threshold to -1 or the session timeout to 0? The validateConfig() function in PR 1 catches these at startup with a clear error message, long before a broken config value can corrupt production data. Always validate at the config layer: check types, enforce non-negative bounds, guard against nonsensical combinations. A startup crash with a useful message is far better than a silent runtime bug.

Mixing the extraction and the value change in a single PR. This defeats both benefits of the technique. The extraction is no longer obviously zero-risk (reviewers now have to mentally untangle "is this change structural or behavioral?"). And the value change is no longer trivially reviewable (it's buried in a larger diff). Keep them separate. The whole value of the pattern is that PR 1 earns trust by being verifiably safe, and PR 2 cashes in that trust by being verifiably simple.

←PreviousTest-First Decomposition
← All techniques