Skip to main content
Back to blog
February 12, 20265 min read

Goodbye Spaghetti Code: Refactoring Infinite IF-ELSE with Object Mapping

As developers, we've all been there: a function that starts with two simple conditions and, six months later, turns into a 50-line "monster" full of nested `else if` and `switch` statements.
DevelopmentSoftwareFronten
Goodbye Spaghetti Code: Refactoring Infinite IF-ELSE with Object Mapping

Goodbye Spaghetti Code: Refactoring Infinite IF-ELSE with Object Mapping

This phenomenon is known as Spaghetti Code, and it's the number one enemy of scalability. Today, we'll explore how to transform that logic into clean, maintainable, and professional structures using Object Literals and Maps.


1. The Problem: The "Waterfall Effect"

When you use multiple if-else blocks, the JavaScript engine evaluates each line sequentially. If the condition you're looking for is at the bottom, the program wastes resources evaluating everything above it. Furthermore, readability drops to zero.

// ❌ Hard to maintain and scale
function processNotification(type) {
  if (type === 'email') {
    sendEmail();
  } else if (type === 'sms') {
    sendSMS();
  } else if (type === 'push') {
    sendPush();
  } else {
    logError();
  }
}

2. Level 1: The Lookup Pattern (Basics)

The simplest way to clean this up is to create an object where the keys are your conditions and the values are the results or functions to execute.

// ✅ Clean and direct code
const NOTIFICATIONS = {
  email: sendEmail,
  sms: sendSMS,
  push: sendPush
};

function processNotification(type) {
  // We use the || short-circuit to define a default case
  const action = NOTIFICATIONS[type] || logError;
  return action();
}

3. Level 2: Solving Complex Scenarios

This is where the technique becomes truly powerful for real-world applications, especially in modern Frontend development.

A. UI State Management (Perfect for React)

Instead of cluttering your component with ternary operators or if statements before the return, map your state components. This separates business logic from the view.

const UserProfileViews = {
  LOADING: () => <Skeleton />,
  ERROR: ({ message }) => <ErrorMessage text={message} />,
  SUCCESS: ({ data }) => <UserCard user={data} />,
  EMPTY: () => <EmptyState />
};

const UserProfile = ({ status, payload }) => {
  // We select the component based on the server status
  const Component = UserProfileViews[status] || UserProfileViews.ERROR;
  
  return (
    <div className="container">
      <Component {...payload} />
    </div>
  );
};

B. Dynamic Range Logic

Sometimes if statements compare ranges (e.g., price > 100). For this, we use an Array of Criteria with validation functions to keep the structure organized:

const DISCOUNT_RULES = [
  { test: (val) => val > 500,  apply: (val) => val * 0.80 }, // 20% OFF
  { test: (val) => val > 100,  apply: (val) => val * 0.90 }, // 10% OFF
  { test: (val) => true,       apply: (val) => val }         // No discount
];

const calculatePrice = (amount) => {
  // We search for the first matching rule declaratively
  const rule = DISCOUNT_RULES.find(r => r.test(amount));
  return rule.apply(amount);
};

4. Why is this approach superior?

  1. Constant Complexity $O(1)$: In simple key-value lookups, the speed is maximum. Regardless of whether you have 10 or 100 cases, there is no performance degradation.
  2. Single Responsibility Principle: You can extract configurations into external files (like config.rules.js), keeping your main components and functions extremely lean.
  3. Open/Closed Principle: If you need to add a new notification type or rule, you don't touch the core logic; you simply extend the configuration object or array.

Conclusion

Refactoring isn't just about "pretty code"; it's about reducing cognitive load for your "future self" and your team. Map-based code is more predictable, easier to test, and far more professional. Next time you're tempted to write that fourth else if, stop and ask yourself: "Could this be an object?"

Comments