The Quest Begins (The "Why")
I was staring at a pull request that felt like a boss level in a retro arcade game—except there were no extra lives. The code was a massive if/else if/else chain that decided how to handle different JSON payloads coming from a third‑party API. Each branch did almost the same thing: validate a few fields, map them to our internal model, then call a service. The only thing that changed was the shape of the incoming object.
Every time a new endpoint was added, a developer had to copy‑paste the whole block, tweak a few field names, and pray they didn’t miss a comma. Reviewing it felt like watching someone try to solve a Rubik’s cube by rotating random faces—you could get lucky, but most of the time you just made a bigger mess.
I kept asking myself: Why are we writing the same logic over and over? The answer was hiding in plain sight: we weren’t seeing the pattern.
The Revelation (The Insight)
The breakthrough hit me while I was refactoring a tiny utility that turned a list of user IDs into a set. I realized I wasn’t writing a new algorithm each time—I was applying the same shape of solution: take an input, transform it, then feed it to a consistent consumer.
In other words, the problem wasn’t “how do I handle payload X?” It was “how do I dispatch the right transformation based on a key?” That’s a classic dispatch table (or strategy pattern) problem.
The “aha!” moment was when I looked at the chain and saw that each branch could be expressed as a function:
function handleOrder(payload) { /* … */ }
function handleRefund(payload) { /* … */ }
function handleShipment(payload) { /* … */ }
All of them shared the same signature: (payload) => Result. If I could map a discriminator (like payload.type) to the correct function, the whole if/else monster would collapse into a single lookup.
That’s the pattern top coders spot instantly: repetitive conditional logic → a table of behaviors. Once you see it, the code writes itself.
Wielding the Power (Code & Examples)
The Struggle (Before)
Here’s a simplified version of the original nightmare (JavaScript/Node.js, but the idea translates to any language):
function processApiEvent(event) {
if (event.type === 'order') {
// validate order‑specific fields
if (!event.id || !event.amount) throw new Error('Bad order');
const model = {
id: event.id,
amount: event.amount,
createdAt: new Date(event.timestamp)
};
return orderService.create(model);
} else if (event.type === 'refund') {
// validate refund‑specific fields
if (!event.refundId || !event.originalOrderId) throw new Error('Bad refund');
const model = {
refundId: event.refundId,
orderId: event.originalOrderId,
amount: event.amount,
reason: event.reason || 'unspecified'
};
return refundService.process(model);
} else if (event.type === 'shipment') {
// validate shipment‑specific fields
if (!event.trackingNumber || !event.carrier) throw new Error('Bad shipment');
const model = {
tracking: event.trackingNumber,
carrier: event.carrier,
estimatedDelivery: new Date(event.eta)
};
return shipmentService.register(model);
} else {
throw new Error(`Unknown event type: ${event.type}`);
}
}
Problems:
- Adding a new event type meant copying the whole block.
- Validation logic was scattered, making it easy to forget a field.
- The function grew linearly with the number of event types—hard to test, hard to read.
The Victory (After)
Now we extract the common shape: each handler validates, builds a model, then calls a service. We store them in a plain object keyed by the discriminator (event.type).
// ---- Handler definitions (pure, easy to test) ----
function handleOrder(event) {
if (!event.id || !event.amount) throw new Error('Bad order');
const model = {
id: event.id,
amount: event.amount,
createdAt: new Date(event.timestamp)
};
return orderService.create(model);
}
function handleRefund(event) {
if (!event.refundId || !event.originalOrderId) throw new Error('Bad refund');
const model = {
refundId: event.refundId,
orderId: event.originalOrderId,
amount: event.amount,
reason: event.reason || 'unspecified'
};
return refundService.process(model);
}
function handleShipment(event) {
if (!event.trackingNumber || !event.carrier) throw new Error('Bad shipment');
const model = {
tracking: event.trackingNumber,
carrier: event.carrier,
estimatedDelivery: new Date(event.eta)
};
return shipmentService.register(model);
}
// ---- Dispatch table ----
const HANDLERS = {
order: handleOrder,
refund: handleRefund,
shipment: handleShipment
};
function processApiEvent(event) {
const handler = HANDLERS[event.type];
if (!handler) throw new Error(`Unknown event type: ${event.type}`);
return handler(event);
}
What changed?
- The conditional chain is gone; we have a single lookup.
- Each handler is isolated—easy to unit test in isolation.
- Adding a new event type is as simple as writing a new handler and adding one line to
HANDLERS.
Common Traps (the “boss‑level” pitfalls)
- Mutating shared state inside handlers – If a handler modifies a variable that lives outside its scope, you re‑introduce hidden coupling. Keep handlers pure or pass in explicit dependencies.
-
Forgetting a fallback – Leaving out the
if (!handler)check will letundefinedbe called as a function, throwing a cryptic “handler is not a function” error. Always validate the lookup.
Why This New Power Matters
Seeing the pattern turns what used to be a dreaded maintenance chore into a snap‑fit LEGO build. You can now:
- Scale – Add dozens of event types without blowing up the file size.
- Test – Each handler is a tiny, pure function; test suites run fast and are easy to reason about.
- Onboard – New teammates glance at the dispatch table and instantly grasp how to extend the system.
More broadly, this mindset spreads beyond API dispatchers. Whenever you see repetitive if/switch blocks, think: “What’s the varying part? Can I turn it into a key?” That’s the secret weapon of top coders: they don’t just write code, they recognize the shape of the problem and reuse a proven pattern.
Your Turn
Take a look at a recent piece of code you’ve written that contains a long conditional chain. Ask yourself: What’s the one thing that changes between each branch? Try extracting those varying pieces into a map or a dictionary and see how the flow simplifies.
If you give it a shot, drop a link to your before/after in the comments—I’d love to see your pattern‑recognition victories! Happy coding! 🚀
Top comments (0)