tion processor that routes logic based on amount tiers and payment methods.
Step 1: Define the Execution Contract
Start with explicit types to enforce predictable condition evaluation. TypeScript interfaces prevent implicit coercion traps and make branching boundaries explicit.
type PaymentMethod = "credit_card" | "bank_transfer" | "crypto";
type TransactionStatus = "pending" | "approved" | "declined" | "flagged";
interface TransactionPayload {
amount: number;
method: PaymentMethod;
riskScore: number;
}
Step 2: Implement Range-Based Logic with if-else if
Use if-else if when conditions involve ranges, inequalities, or compound boolean expressions. The engine evaluates each condition sequentially and short-circuits at the first match, making order critical.
function evaluateRiskTier(payload: TransactionPayload): TransactionStatus {
const { amount, riskScore } = payload;
if (riskScore > 85 || amount > 10000) {
return "flagged";
} else if (riskScore > 60 || amount > 5000) {
return "pending";
} else if (riskScore <= 30 && amount < 1000) {
return "approved";
} else {
return "declined";
}
}
Architecture Rationale: Conditions are ordered from most restrictive to least restrictive. This prevents broader conditions from shadowing specific ones. The else block acts as a deterministic fallback, ensuring no execution path escapes handling. Early returns eliminate nested indentation and keep the function's cognitive footprint flat.
Step 3: Implement Exact-Value Routing with switch
When dispatching based on a single variable against discrete values, switch provides a cleaner execution model. JavaScript uses strict equality (===) for case matching, eliminating type coercion surprises.
function applyMethodFees(method: PaymentMethod): number {
switch (method) {
case "credit_card":
return 0.029;
case "bank_transfer":
return 0.015;
case "crypto":
return 0.005;
default:
throw new Error(`Unsupported payment method: ${method}`);
}
}
Architecture Rationale: Each case terminates with an explicit return, which inherently prevents fall-through without requiring break. The default block throws a descriptive error, failing fast rather than silently proceeding with undefined behavior. This pattern is ideal for configuration routing, state machines, and API response handlers.
Step 4: Optimize Hot Paths with Lookup Maps
For static routing where conditions map directly to functions or values, replace branching with object lookups. This approach reduces cyclomatic complexity to zero and enables better tree-shaking.
const taxCalculator: Record<string, (base: number) => number> = {
us: (base) => base * 0.08,
eu: (base) => base * 0.21,
uk: (base) => base * 0.20,
};
function computeTax(region: string, baseAmount: number): number {
const calculator = taxCalculator[region];
if (!calculator) {
throw new Error(`Tax region not configured: ${region}`);
}
return calculator(baseAmount);
}
Architecture Rationale: Property access is O(1) and bypasses the conditional evaluation pipeline entirely. Guard clauses validate configuration existence before execution. This pattern scales cleanly as new regions are added without modifying control flow logic.
Pitfall Guide
1. Implicit Coersion in Conditions
Explanation: Using loose equality (==) or relying on truthy/falsy coercion in if statements causes unexpected branch selection. 0, "", null, and undefined all coerce to false, but [] and {} coerce to true.
Fix: Always use strict equality (===) and explicit type checks. Replace if (value) with if (value !== undefined && value !== null) when dealing with nullable types.
2. Unintended Switch Fall-Through
Explanation: Omitting break or return in a switch case causes execution to cascade into subsequent cases. This is rarely intentional and introduces silent logic corruption.
Fix: Structure switch blocks to return early or throw errors. If fall-through is required for grouped cases, add explicit comments and ensure the final case terminates execution.
3. Deep Nesting / Arrow Anti-Pattern
Explanation: Chaining conditions with nested if blocks creates the "arrow anti-pattern," drastically increasing cognitive load and making error handling fragmented.
Fix: Apply guard clauses and early returns. Validate prerequisites at the top of the function and exit immediately on failure. Keep the happy path left-aligned.
4. Redundant Condition Evaluation
Explanation: Placing expensive computations or API calls inside else if conditions causes unnecessary execution when earlier branches already matched.
Fix: Extract complex expressions into named variables before branching. Evaluate once, reference multiple times. This also improves debugging and testability.
5. Using switch for Range or Compound Logic
Explanation: switch relies on strict equality against a single expression. Attempting to use it for ranges (case x > 50:) or compound conditions (case a && b:) requires hacky workarounds like switch(true) that obscure intent.
Fix: Reserve switch for exact-value dispatch. Use if-else chains for ranges, inequalities, or multi-variable boolean logic.
6. Missing Fallback Handling
Explanation: Omitting else or default blocks leaves execution paths unhandled. In production, this results in undefined returns, silent failures, or uncaught exceptions downstream.
Fix: Always include a deterministic fallback. Either return a safe default value, throw a descriptive error, or log a warning with telemetry context.
7. Over-Engineering Simple Booleans
Explanation: Wrapping straightforward boolean checks in complex ternary chains or nested conditionals reduces readability without adding value.
Fix: Use direct boolean assignment or early returns. Replace if (isValid) return true; else return false; with return isValid;.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
Range checks (>, <, <=) | if-else chain | Native support for inequality operators | Low |
Compound boolean logic (&&, ||) | if-else chain | Clear expression grouping and short-circuiting | Low |
| Single variable, exact values | switch statement | Flat structure, explicit case boundaries | Medium |
| Static routing to functions/values | Object lookup map | O(1) access, zero branching overhead | Low |
| Dynamic condition evaluation | if-else with extracted predicates | Maintains readability while enabling reuse | Medium |
| State machine transitions | switch or lookup map | Predictable dispatch, easy to test | Low |
Configuration Template
// conditional-router.ts
export type RouteHandler<T> = (payload: T) => void;
export class ConditionalRouter<T> {
private routes: Map<string, RouteHandler<T>> = new Map();
private fallback: RouteHandler<T> | null = null;
addRoute(key: string, handler: RouteHandler<T>): this {
this.routes.set(key, handler);
return this;
}
setFallback(handler: RouteHandler<T>): this {
this.fallback = handler;
return this;
}
execute(key: string, payload: T): void {
const handler = this.routes.get(key);
if (handler) {
handler(payload);
return;
}
if (this.fallback) {
this.fallback(payload);
return;
}
throw new Error(`No route configured for key: ${key}`);
}
}
// Usage example
const router = new ConditionalRouter<{ userId: string; action: string }>();
router
.addRoute("login", (payload) => console.log(`Authenticating ${payload.userId}`))
.addRoute("logout", (payload) => console.log(`Terminating session ${payload.userId}`))
.setFallback((payload) => console.warn(`Unknown action: ${payload.action}`));
router.execute("login", { userId: "usr_8821", action: "login" });
Quick Start Guide
- Identify the evaluation pattern: Determine if your logic requires range checks, exact-value matching, or static dispatch.
- Select the construct: Use
if-else for ranges/complex logic, switch for exact matches, or lookup maps for static routing.
- Implement guard clauses: Extract validation logic to the top of the function and return early on failure.
- Add deterministic fallbacks: Ensure every branch has an explicit
else, default, or error throw to prevent silent failures.
- Benchmark hot paths: If the conditional executes in a tight loop or high-frequency handler, replace branching with lookup maps or precomputed predicates.