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.
👉 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:
typeofcaptures the type of theroleLabelsvaluekeyofturns 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.
