Files
entscheidomat/posts/06-bezos-decision-matrix-architecture-code.md
2026-08-05 19:33:11 +02:00

9.9 KiB

title, description, tags, canonical_url, target_keywords
title description tags canonical_url target_keywords
Architecting Low-Latency Decision Engines: One-Way vs. Two-Way Door Metrics in Software Architecture How to design low-latency software decision engines using Feature Flags, State Machines, and Bezos' reversible decision framework in TypeScript.
architecture
typescript
systemdesign
devops
https://entscheidomat.com/ratgeber/entscheidung-treffen-wenn-zwei-optionen-gleich-gut-sind
entscheidungsfinder
entscheidungsgenerator
entweder oder generator
feature flag architecture
state machine typescript

Architecting Low-Latency Decision Engines: One-Way vs. Two-Way Door Metrics in Software Architecture

In high-throughput distributed systems, decision-making happens millions of times per second. Whether an application is routing API traffic between microservices, executing A/B testing variations, evaluating user entitlements, or powering a lightweight decision tool like an Entscheidungsgenerator, how decision engines are architected directly impacts latency, system resilience, and deployment velocity.

Amazon founder Jeff Bezos famously divided all decisions into two distinct categories in his 1997 Shareholder Letter:

  • Type 1 Decisions (One-Way Doors): Irreversible, high-consequence architectural bets (e.g. primary database migration, multi-region cloud strategy).
  • Type 2 Decisions (Two-Way Doors): Reversible, low-risk operational choices (e.g. UI micro-interactions, algorithm tweaks, feature toggles).

In this article, we will translate this decision framework into concrete software architecture patterns. We will build a production-grade, zero-dependency Type 2 Decision Engine with Feature Flag Rollouts and Deterministic Finite State Machines (FSM) in TypeScript.


1. Architectural Taxonomy: Type 1 vs. Type 2 Decisions in Code

Software architecture mistakes occur when engineering organizations treat Type 2 decisions with Type 1 rigor (paralyzing release cycles) or treat Type 1 decisions as Type 2 shortcuts (causing catastrophic outage vulnerabilities).

                  ┌────────────────────────┐
                  │ Software Decision Flow │
                  └───────────┬────────────┘
                              │
              Is the change easily reversible?
                              │
               ┌──────────────┴──────────────┐
               ▼                             ▼
        YES (Type 2 / Two-Way)        NO (Type 1 / One-Way)
    ┌───────────────────────────┐ ┌──────────────────────────┐
    │ • Feature Toggles         │ │ • DB Schema Migrations   │
    │ • Dynamic API Routing     │ │ • Monolith to Serverless │
    │ • Progressive Rollouts    │ │ • Protocol Format Shifts │
    └───────────────────────────┘ └──────────────────────────┘
Decision Metric Type 1 (One-Way Door) Type 2 (Two-Way Door)
Reversibility Cost Extremely High (O(\text{Weeks/Months})) Near Zero (O(\text{Milliseconds}))
Evaluation Speed Target Weeks (RFCs, Architecture Reviews) < 1 \text{ms} (Runtime Evaluator)
Verification Strategy Formal proofs, load testing Feature flags, canary rollouts, A/B metrics
Code Implementation Immutable schema contracts Dynamic state machine / Config evaluation

2. Designing a Sub-Millisecond Type 2 Decision Engine

Let's build a sub-millisecond, zero-dependency Type 2 Feature Flag & Routing Engine in TypeScript. This engine allows teams to make instant, reversible runtime decisions without re-deploying code.

export interface UserContext {
  id: string;
  email: string;
  country: string;
  isBetaTester: boolean;
}

export interface FeatureFlagRule {
  id: string;
  enabled: boolean;
  rolloutPercentage: number; // 0 to 100
  allowedCountries?: string[];
  requiresBeta?: boolean;
}

export class DecisionEngine {
  private rules: Map<string, FeatureFlagRule> = new Map();

  public registerRule(rule: FeatureFlagRule): void {
    this.rules.set(rule.id, rule);
  }

  /**
   * Deterministic hash calculation (MurmurHash3 variant) to ensure 
   * a user consistently receives the same feature bucket.
   */
  private hashUser(userId: string, flagId: string): number {
    const key = `${userId}:${flagId}`;
    let hash = 0x811c9dc5; // FNV-1a offset basis

    for (let i = 0; i < key.length; i++) {
      hash ^= key.charCodeAt(i);
      hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
    }

    return (hash >>> 0) % 100; // Returns consistent integer 0..99
  }

  /**
   * Evaluates a decision rule in under 0.1 milliseconds.
   */
  public evaluate(flagId: string, context: UserContext): boolean {
    const rule = this.rules.get(flagId);

    if (!rule || !rule.enabled) return false;

    // Rule 1: Beta Check
    if (rule.requiresBeta && !context.isBetaTester) return false;

    // Rule 2: Geo-location Filter
    if (rule.allowedCountries && !rule.allowedCountries.includes(context.country)) {
      return false;
    }

    // Rule 3: Deterministic Percentage Rollout
    const userBucket = this.hashUser(context.id, flagId);
    return userBucket < rule.rolloutPercentage;
  }
}

Performance & Determinism Test

const engine = new DecisionEngine();
engine.registerRule({
  id: "new_checkout_flow",
  enabled: true,
  rolloutPercentage: 25, // 25% rollout
  allowedCountries: ["DE", "AT", "CH"]
});

const user: UserContext = {
  id: "usr_94821",
  email: "dev@example.com",
  country: "DE",
  isBetaTester: false
};

const startTime = performance.now();
const isEnabled = engine.evaluate("new_checkout_flow", user);
const duration = performance.now() - startTime;

console.log(`Decision Evaluation Result: ${isEnabled} (Executed in ${duration.toFixed(4)} ms)`);

3. Reversible Decision Workflows via Finite State Machines (FSM)

For complex multi-step application states (such as checkout flows, loan applications, or interactive decision tools like Entscheidomat Entweder-Oder), representing state transitions as a Finite State Machine ensures strict type safety and zero invalid transitions.

export type DecisionState = "IDLE" | "EVALUATING" | "RESOLVED" | "CANCELLED";
export type DecisionEvent = "SUBMIT" | "APPROVE" | "REJECT" | "RESET";

export class ReversibleDecisionFSM {
  private currentState: DecisionState = "IDLE";
  private history: DecisionState[] = [];

  private readonly transitions: Record<DecisionState, Partial<Record<DecisionEvent, DecisionState>>> = {
    IDLE: { SUBMIT: "EVALUATING" },
    EVALUATING: { APPROVE: "RESOLVED", REJECT: "CANCELLED", RESET: "IDLE" },
    RESOLVED: { RESET: "IDLE" },
    CANCELLED: { RESET: "IDLE" }
  };

  public transition(event: DecisionEvent): DecisionState {
    const allowedNextState = this.transitions[this.currentState][event];

    if (!allowedNextState) {
      throw new Error(`Invalid FSM transition: Cannot trigger '${event}' from state '${this.currentState}'`);
    }

    this.history.push(this.currentState);
    this.currentState = allowedNextState;
    return this.currentState;
  }

  /**
   * Reverts to previous state (Two-Way Door behavior)
   */
  public rollback(): DecisionState {
    const previous = this.history.pop();
    if (!previous) {
      throw new Error("No previous state to rollback to");
    }

    this.currentState = previous;
    return this.currentState;
  }

  public getState(): DecisionState {
    return this.currentState;
  }
}

4. Benchmark & Architectural Checklist

When designing modern web applications and decision utilities:

Principle Technical Implementation
Sub-Millisecond Evaluation Use in-memory FNV-1a hashing instead of remote DB network calls on every request.
Zero Deployment Rollbacks Wrap all Type 2 changes in feature flag evaluation blocks.
Reversibility (Two-Way) Implement FSM history stacks to allow single-click state rollbacks.
Stateless Scalability Derive user buckets deterministically using hash(userId + flagId) % 100.

Conclusion & Practical Takeaway

  1. Software teams must explicitly tag changes as Type 1 (One-Way) or Type 2 (Two-Way) in RFCs and PR reviews.
  2. Type 2 decisions should never require code deployments; evaluate them using in-memory feature flags and state machines.
  3. For interactive consumer tools, use clean TypeScript state engines to keep execution fast, predictable, and reversible.

Test an interactive decision engine live on Entscheidomat Entweder-Oder.


FAQ (Schema Structured Data)

{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "name": "What is Jeff Bezos' One-Way vs. Two-Way Door decision framework?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "One-Way Door (Type 1) decisions are irreversible and high-stakes, requiring slow evaluation. Two-Way Door (Type 2) decisions are easily reversible and should be executed rapidly using feature flags and runtime evaluators."
      }
    },
    {
      "@type": "Question",
      "name": "How fast should a feature flag decision engine evaluate?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "A well-architected in-memory decision engine using deterministic hashing should evaluate in under 0.1 milliseconds per request without hitting external network databases."
      }
    }
  ]
}