Quality Ratchet
also called Boy Scout Rule, Improvement Ratchet
An automated rule ensuring a codebase can only improve on a chosen dimension - new code meets the standard, existing code improves when touched, and regression is blocked.
A large refactoring project has no business outcome to defend, so it competes with features and loses. A ratchet works differently: it makes improvement a side effect of ordinary work and makes regression impossible.
The mechanism is an automated check with two rules:
- New code must meet the standard.
- The overall measure may not get worse.
Existing code is not required to conform immediately; it improves when it is touched. The measure can only move in one direction, hence the name.
What it applies to
- Test coverage — new code covered, overall coverage non-decreasing.
- Dependency direction — no new violations of module rules, with existing violations recorded as a shrinking allowlist.
- Type coverage in gradually-typed languages.
- Complexity or file size thresholds for new and modified code.
- Lint and static analysis rules, introduced with existing violations grandfathered and prevented from growing.
- Performance budgets — bundle size, query count, latency — where the budget may not be exceeded and raising it is an explicit, owned decision.
Why it works where projects do not
It concentrates improvement where change frequency is highest. Code that is touched often is improved often; code nobody touches is left alone, which is correct because its structure is costing nothing.
It also removes the funding problem: the work is not a project requiring prioritisation, it is a condition of doing the work already prioritised.
And it is durable. A heroic refactor with no ratchet re-muddies within two quarters; a ratchet holds indefinitely because regression fails the build.
Implementation patterns
- Grandfather existing violations explicitly, as a list that may shrink and not grow. Trying to fix everything before enabling the check is what prevents adoption.
- Fail the build, not a report. A dashboard nobody must look at changes nothing.
- Make the exception path explicit — raising a budget is a reviewed decision with an owner, so it is a decision rather than an erosion.
- Pick few dimensions. A ratchet on ten measures is friction; on two or three that matter it is discipline.
Industry example
The most valuable single ratchet in most systems is a dependency-direction check on the critical path — no module on the ordering or checkout path may import from analytics, recommendation or marketing modules.
It catches the day someone adds a personalisation call inside checkout "just for a banner". It works perfectly in staging and becomes a checkout outage the first time the recommendation service is slow. Critical paths acquire non-critical dependencies by accident, never by decision, and no functional test fails when they do.
The second most valuable is usually a performance budget on client bundle size, for the same structural reason: every individual addition is defensible and the aggregate is a product that takes twice as long to load, with no responsible party and no signal.
Failure scenarios
- Enabled without grandfathering, so it cannot be adopted on an existing codebase.
- Too many dimensions, so it is experienced as obstruction and gets disabled.
- A measure that is easy to game — coverage satisfied by tests that assert nothing.
- No exception path, so a legitimate need to exceed a budget forces the check to be bypassed entirely.
- Reporting rather than blocking, which changes nothing.
Trade-offs
A ratchet slows individual changes slightly and occasionally blocks something legitimate. It also encodes a judgement about what matters, which may be wrong — a coverage ratchet on a codebase where coverage is not the constraint adds friction without benefit.
Choosing the dimension deliberately, from what is actually costing the team, is what separates a ratchet that holds from one that gets disabled after a frustrating month.
Interview question
"You inherit a codebase with poor structure and no capacity for a refactoring project. What automated check would you add first, how would you make it adoptable, and what would you expect to change in six months?"