Two-Way Doors: Don’t Code Yourself into a Corner

This article was adapted from a Google Tech on the Toilet (TotT) episode. You can download a printer-friendly version of this TotT episode and post it in your office.

By Nimit Khandelwal and Chris Kennelly

On large software projects, even seemingly trivial choices can set assumptions that the codebase naturally evolves around, making them incredibly expensive to fix later.

To help achieve decision velocity without accumulating technical debt, leverage the “one-way vs. two-way doors” mental model:

  • One-way doors: Decisions that are rare, highly consequential, and hard to reverse (e.g., picking a database backend).

  • Two-way doors: Decisions that are low-risk and easy to walk back (e.g., choosing a local variable name).

Unless designing for flexibility, even trivial choices can quickly harden into painful one-way doors. To maintain high decision velocity safely, use deliberate architectural patterns to engineer reversibility into your code. Instead of coding yourself into a corner, actively design your code with built-in escape hatches. By consciously building flexibility into your systems up front, you can eliminate analysis paralysis and keep your team executing both fast and safely.

Here are some patterns you can use to build two-way-doors into your code:

Pattern

Description

How It Creates a Two-Way Door

Feature flags

Use conditional switches within the code to safely roll out a new feature in production, decoupling deployment from release.

Dynamically isolates experimental behavior from the baseline. If a new logic path fails in production, the flag update can be rolled back and the system restored to the previous, good known state.

Limiting API exposure

Limit access to APIs that are under development; only expand access once the design is highly mature.

Restricts access to your module or package, allowing you to iterate and refactor the interface without breaking downstream clients. Reversing a public API is a painful one-way door.

Decoupling  data formats

Keep transient in-memory parsing and active RPC payload layouts separate from your persistent disk structures.

Isolates your changes from disk storage. Changing memory layouts is cheap, but writing new data formats to disk is a one-way door since you must support reading that format forever.

Dark launches

Run new experimental logic in parallel with the status quo, discarding the experimental output but recording telemetry.

Allows you to validate performance and correctness at production scale with zero downstream impact, making it incredibly easy to walk back or modify before committing.


This content was adapted from a Google Performance Tip of the Week: abseil.io/fast/87.