---
title: "Architecting Low-Latency Decision Engines: One-Way vs. Two-Way Door Metrics in Software Architecture"
description: "How to design low-latency software decision engines using Feature Flags, State Machines, and Bezos' reversible decision framework in TypeScript."
tags: ["architecture", "typescript", "systemdesign", "devops"]
canonical_url: "https://entscheidomat.com/ratgeber/entscheidung-treffen-wenn-zwei-optionen-gleich-gut-sind"
target_keywords: ["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](https://entscheidomat.com/entweder-oder), 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).

```text
                  ┌────────────────────────┐
                  │ 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.

```typescript
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
```typescript
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](https://entscheidomat.com/entweder-oder)), representing state transitions as a **Finite State Machine** ensures strict type safety and zero invalid transitions.

```typescript
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](https://entscheidomat.com/entweder-oder).

---

## FAQ (Schema Structured Data)

```json
{
  "@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."
      }
    }
  ]
}
```
