Entry / Feb 15, 2026

Replace Endless If/Else in TypeScript with Object Lookup

Why turning conditional chains into typed lookup objects makes TypeScript code easier to read, safer to extend, and far easier to maintain.

Replace Endless If/Else in TypeScript with Object Lookup

👉 Watch on YouTube

There is a point where an innocent if stops being a quick solution and starts becoming technical debt.

It begins with one condition. Then another requirement lands. Then another. A few weeks later, you are staring at a chain of if / else if branches that nobody wants to touch because removing one condition might break everything.

That is the problem this video tackles: how to move from fragile conditional logic to a cleaner, data-driven approach using plain objects and TypeScript.

The real problem with long conditional chains

The issue is not just aesthetics.

Long conditional trees tend to create code that is:

  • harder to scan
  • harder to extend safely
  • easier to break when requirements change
  • disconnected from TypeScript’s autocomplete and validation

When the input is just a string, TypeScript cannot help much. You lose the list of accepted values, invalid cases slip in more easily, and the compiler stops acting like a guardrail.

That is why these branches often feel dangerous to edit.

The object lookup pattern

The core refactor in the video is simple: stop asking the same question over and over with conditionals and replace that logic with a lookup object.

Instead of this:

function getRoleLabel(role: string) {
  if (role === "admin") return "Full access";
  if (role === "editor") return "Can edit content";
  if (role === "guest") return "Read only";
  return "Unknown role";
}

You move to something like this:

const roleLabels = {
  admin: "Full access",
  editor: "Can edit content",
  guest: "Read only",
};

function getRoleLabel(role: string) {
  return (
    roleLabels[role as keyof typeof roleLabels] ??
    "Unknown role"
  );
}

Conceptually, this is closer to a decision table than a control flow tree. The logic becomes declarative: values map to results.

That matters because the code stops being about branch management and starts being about relationships in data.

Why this feels cleaner immediately

A lookup object reduces noise.

You are no longer reading repeated comparisons. You are reading the allowed options directly.

That usually gives you:

  • fewer lines
  • clearer intent
  • simpler edits when new cases appear
  • a better separation between data and behavior

This is one of those refactors where the readability win shows up before any advanced TypeScript feature even enters the room.

Where TypeScript starts paying off

The video then improves the pattern by adding a union type for the accepted keys.

type UserRole = "admin" | "editor" | "guest";

const roleLabels: Record<UserRole, string> = {
  admin: "Full access",
  editor: "Can edit content",
  guest: "Read only",
};

function getRoleLabel(role: UserRole) {
  return roleLabels[role];
}

This is where the pattern goes from “cleaner JavaScript” to “serious TypeScript”.

Now you get:

  • autocomplete for valid roles
  • compile-time errors for invalid values
  • guarantees that every allowed role has a mapping
  • a function signature that documents the contract clearly

That last part is important.

The function is no longer accepting any random string and hoping for the best. It declares exactly what the system understands.

keyof and typeof make the mapping self-updating

One of the nicest expansions from the video is the keyof typeof version.

const roleLabels = {
  admin: "Full access",
  editor: "Can edit content",
  guest: "Read only",
};

type UserRole = keyof typeof roleLabels;

This works because:

  • typeof captures the type of the roleLabels value
  • keyof turns that object type into a union of its keys

So TypeScript derives "admin" | "editor" | "guest" for you.

That is powerful because the source of truth becomes the object itself. Add a new key, and the union updates automatically.

For small lookup tables, this is often the most ergonomic option because the data and the type stay in sync with almost no extra ceremony.

A useful production refinement: Record

The video focuses on the object mapping idea, but in production code it is often worth knowing the Record utility type too.

type UserRole = "admin" | "editor" | "guest";

const roleLabels: Record<UserRole, string> = {
  admin: "Full access",
  editor: "Can edit content",
  guest: "Read only",
};

Record<Keys, Type> is useful when you want to define the valid keys first and force the object to fully implement that contract.

That makes it a great fit when the domain model is already known and you want missing cases to fail fast during development.

About the O(1) argument

The video mentions reducing complexity from O(n) to O(1).

At a high level, that is directionally correct for direct object property access versus walking a sequence of comparisons. But in day-to-day application code, the bigger win is usually not raw performance.

It is maintainability.

The real improvement is that a lookup table is easier to extend, easier to audit, and easier for TypeScript to model.

So yes, the access pattern is efficient. But the design benefit is usually the reason this refactor matters.

When this pattern is the right tool

An object lookup works especially well when:

  • one input key maps directly to one value
  • one input key maps directly to one function
  • the allowed cases are finite and explicit
  • you want TypeScript to enforce coverage and autocomplete

This is perfect for labels, handlers, configuration, role-based messages, feature flags, and lightweight dispatch tables.

When you may need something else

Not every conditional should become a lookup object.

If each branch contains richer behavior, state transitions, or multiple related operations, a strategy or polymorphic design may be a better next step.

That matches a broader refactoring principle: when conditionals start spreading through a codebase, they often signal a design that wants stronger boundaries.

In other words, an object lookup is excellent for direct mapping. It is not automatically the final abstraction for every case.

The takeaway

The lesson here is bigger than replacing if statements.

It is about moving from imperative branching to explicit models.

When the valid options live in a typed object and TypeScript can derive or enforce the keys, the compiler starts working for you again.

You get code that is:

  • easier to read
  • safer to extend
  • better documented by its types
  • less likely to rot as requirements grow

That is not a hack.

That is better design.