Coding 101

Mastering Conditional Logic: Guard Clauses, Lookup Tables, and Eliminating Else

DD
Ankur Ishwar
9 min read Updated Sep 7, 2026
Mastering Conditional Logic Guard Clauses and Lookup Tables

In my first year of college, our lab instructors taught us conditional logic by nesting five if statements inside each other.

By the time your cursor reached column 60, the code looked like an arrowhead pointing to the right. Everyone in the lab nodded along. We thought that was what "advanced programming" looked like.

Then I reviewed real production pull requests at my first engineering job. A senior engineer left a comment on my code that I never forgot: "If your function requires horizontal scrolling to read the conditions, your logic is upside down. Invert the condition, return early, and delete the else branch."

That one habit eliminated half the bugs in my code. Here is how professional software engineers write clean, maintainable conditional logic.

The Pyramid of Doom (Arrow Anti-Pattern)

Look at this typical authentication and checkout controller written by a junior developer:

// Bad: Nested Arrow Anti-pattern
function processCheckout(user: User | null, cart: Cart, paymentMethod: PaymentMethod) {
  if (user) {
    if (user.isEmailVerified) {
      if (cart.items.length > 0) {
        if (paymentMethod.isValid) {
          const total = calculateTotal(cart);
          return chargeCustomer(user, total);
        } else {
          return { error: "Invalid payment method" };
        }
      } else {
        return { error: "Cart is empty" };
      }
    } else {
      return { error: "Email not verified" };
    }
  } else {
    return { error: "User not authenticated" };
  }
}

Why is this dangerous in production?

  • High Cognitive Load: The reader must hold four mental assumptions in their head simultaneously before reaching line 7.
  • Far-Flung Error Handlers: The error message for user not authenticated is 15 lines away from the check at the top.
  • Indentation Creep: Every new requirement (KYC check, coupon validation, stock check) pushes your core business logic further to the right.

Technique 1: Guard Clauses and Early Returns

A guard clause checks for invalid or terminating conditions at the start of a function and exits immediately. This keeps the "happy path" perfectly flat and aligned to the left margin.

// Good: Clean Guard Clauses
function processCheckout(user: User | null, cart: Cart, paymentMethod: PaymentMethod) {
  if (!user) {
    return { error: "User not authenticated" };
  }

  if (!user.isEmailVerified) {
    return { error: "Email not verified" };
  }

  if (cart.items.length === 0) {
    return { error: "Cart is empty" };
  }

  if (!paymentMethod.isValid) {
    return { error: "Invalid payment method" };
  }

  // Happy path runs flat without mental overhead
  const total = calculateTotal(cart);
  return chargeCustomer(user, total);
}

Every failure mode is caught upfront. The core logic executes peacefully at root indentation.

Technique 2: Lookup Tables Over Else-If Ladders

When you have branching logic based on an action, state, or event type, an else if ladder quickly grows into an unreadable 100-line monolith.

Replace it with an object or dictionary lookup map:

// Bad: Ladder of Else-Ifs
function calculateShippingFee(zone: string): number {
  if (zone === "METRO_DELHI" || zone === "METRO_BLR") {
    return 40;
  } else if (zone === "TIER_2") {
    return 80;
  } else if (zone === "NORTH_EAST") {
    return 140;
  } else if (zone === "INTERNATIONAL") {
    return 650;
  } else {
    return 100;
  }
}

// Good: O(1) Constant-Time Lookup Table
const SHIPPING_RATES: Record<string, number> = {
  METRO_DELHI: 40,
  METRO_BLR: 40,
  TIER_2: 80,
  NORTH_EAST: 140,
  INTERNATIONAL: 650,
};

function getShippingFee(zone: string): number {
  const DEFAULT_RATE = 100;
  return SHIPPING_RATES[zone] ?? DEFAULT_RATE;
}

This approach gives you three distinct advantages:

  1. Constant O(1) Lookup: Instant hash evaluation without executing 5 comparison operations.
  2. Separation of Data and Logic: The rates can be moved to a JSON file or database table without altering the function.
  3. Easy Unit Testing: You can export and test the map directly.

Technique 3: Exhaustive Type Checking with Discriminated Unions

In TypeScript, conditional checks on discriminated union types can be verified at build time. If someone adds a new notification channel tomorrow and forgets to handle it, the compiler breaks the build before it ever reaches production.

type NotificationEvent =
  | { type: "EMAIL"; email: string; subject: string }
  | { type: "SMS"; phoneNumber: string; message: string }
  | { type: "WHATSAPP"; phone: string; templateId: string };

function sendNotification(event: NotificationEvent) {
  switch (event.type) {
    case "EMAIL":
      return dispatchEmail(event.email, event.subject);
    case "SMS":
      return dispatchSms(event.phoneNumber, event.message);
    case "WHATSAPP":
      return dispatchWhatsApp(event.phone, event.templateId);
    default: {
      // Compile-time exhaustion check
      const _exhaustiveCheck: never = event;
      throw new Error(`Unhandled event type: ${JSON.stringify(_exhaustiveCheck)}`);
    }
  }
}

Technique 4: Nullish Coalescing vs Logical OR

One of the most common production bugs in JavaScript and TypeScript is using || when you meant to use ??.

In JavaScript, 0, "", and false are falsy values. If your user enters an item quantity of 0, || will overwrite it with your default fallback:

const userDiscount = 0; // 0% discount entered intentionally

// Bug: 0 evaluates to false, setting discount to 10!
const appliedDiscount = userDiscount || 10; // Result: 10 (Wrong)

// Fix: Nullish coalescing only catches null or undefined
const correctDiscount = userDiscount ?? 10; // Result: 0 (Correct)

Summary: The Clean Logic Checklist

Before submitting your next pull request, run through these rules:

  • Validate inputs at the start and return early.
  • If you wrote an else block right after a return, delete the else.
  • Replace string-matching else-if chains with dictionary lookup objects.
  • Use nullish coalescing (??) when handling numeric or boolean defaults.
  • Use TypeScript discriminated unions to guarantee exhaustive case handling.

Next Steps

To continue writing clean, production-level code:

Found this useful?
View all articles
Free Technical Interview Prep

Practicing for Engineering Interviews?

Skip the expensive coaching bootcamps and dry LeetCode memorization. Practice real production scenarios with instant turn-by-turn AI feedback on Frontend, Backend, System Design, and DSA.

Free Utilities

Recommended Developer Tools for this Topic

Explore all 25+ tools

Keep Reading

Related Articles

Learn with Dropout Developer

Build real software with AI

Step-by-step learning paths, vibe coding tutorials, and certified developer programs designed for the modern engineer.