Beyond If-Else: Pattern Matching as the Next Evolution in Control Flow
Karan

Karan @karanjamadar

Joined:
Nov 2, 2023

Beyond If-Else: Pattern Matching as the Next Evolution in Control Flow

Publish Date: Mar 18
1 0

If you've been coding for any significant amount of time, you've likely found yourself deep in nested if-else statements or switch-case blocks that stretch far beyond what's comfortable to read or maintain. While these traditional control flow structures have served us well, modern languages are embracing a more powerful paradigm: pattern matching. This approach is transforming how we handle complex conditional logic, making code more readable, maintainable, and expressive.

The Problem with Traditional Control Flow

Let's face a harsh truth: traditional control flow structures don't scale well with complexity. Consider this JavaScript example:

function processPayment(payment) {
  if (payment.status === 'pending') {
    if (payment.method === 'credit') {
      if (payment.amount > 1000) {
        return handleLargeCredit(payment);
      } else {
        return handleSmallCredit(payment);
      }
    } else if (payment.method === 'bank_transfer') {
      return handleBankTransfer(payment);
    }
  } else if (payment.status === 'failed') {
    if (payment.attempts < 3) {
      return retryPayment(payment);
    } else {
      return notifyCustomerFailed(payment);
    }
  } else if (payment.status === 'completed') {
    return issueReceipt(payment);
  }
  return handleUnknownPayment(payment);
}
Enter fullscreen mode Exit fullscreen mode

As the number of conditions grows, these structures become unwieldy. They're hard to read, difficult to modify, and prone to subtle bugs when edge cases are overlooked.

Enter Pattern Matching

Pattern matching allows developers to express complex conditional logic in a more declarative, structured way. It combines three powerful concepts:

  1. Structural decomposition - Breaking down complex data structures
  2. Condition testing - Evaluating predicates against the data
  3. Binding - Capturing values for use in the handling code

Pattern Matching in JavaScript

JavaScript is evolving to include pattern matching capabilities through TC39 proposals. While not yet standard, the proposed syntax gives us a glimpse of how our code could look in the near future:

// Using the proposed pattern matching syntax
function processPayment(payment) {
  return match (payment) {
    when { status: 'pending', method: 'credit', amount } if amount > 1000 => 
      handleLargeCredit(payment),
    when { status: 'pending', method: 'credit' } => 
      handleSmallCredit(payment),
    when { status: 'pending', method: 'bank_transfer' } => 
      handleBankTransfer(payment),
    when { status: 'failed', attempts } if attempts < 3 => 
      retryPayment(payment),
    when { status: 'failed' } => 
      notifyCustomerFailed(payment),
    when { status: 'completed' } => 
      issueReceipt(payment),
    default => 
      handleUnknownPayment(payment)
  };
}
Enter fullscreen mode Exit fullscreen mode

Even without native pattern matching, we can approximate some of its benefits using JavaScript's object destructuring and custom utilities:

// Using object destructuring and simple matchers
function processPayment(payment) {
  const { status, method, amount, attempts } = payment;

  // Pattern matching simulation
  if (status === 'pending' && method === 'credit' && amount > 1000) {
    return handleLargeCredit(payment);
  }

  if (status === 'pending' && method === 'credit') {
    return handleSmallCredit(payment);
  }

  if (status === 'pending' && method === 'bank_transfer') {
    return handleBankTransfer(payment);
  }

  if (status === 'failed' && attempts < 3) {
    return retryPayment(payment);
  }

  if (status === 'failed') {
    return notifyCustomerFailed(payment);
  }

  if (status === 'completed') {
    return issueReceipt(payment);
  }

  return handleUnknownPayment(payment);
}
Enter fullscreen mode Exit fullscreen mode

Real-World Applications of Pattern Matching in JavaScript

Data Validation

function validateUserInput(input) {
  return match (input) {
    when { email, password } if isValidEmail(email) && password.length >= 8 =>
      { valid: true, user: { email, passwordHash: hashPassword(password) } },
    when { email } if !isValidEmail(email) =>
      { valid: false, error: 'Invalid email format' },
    when { password } if password.length < 8 =>
      { valid: false, error: 'Password too short' },
    default =>
      { valid: false, error: 'Missing required fields' }
  };
}
Enter fullscreen mode Exit fullscreen mode

API Response Handling

function handleAPIResponse(response) {
  return match (response) {
    when { status: 200, data: { user: { name, role: "admin" } } } =>
      renderAdminDashboard(name),
    when { status: 200, data: { user: { name } } } =>
      renderUserDashboard(name),
    when { status: 401 } =>
      redirectToLogin(),
    when { status: 404 } =>
      showNotFound(),
    when { status } if status >= 500 =>
      showServerError(),
    default =>
      showUnexpectedError()
  };
}
Enter fullscreen mode Exit fullscreen mode

State Management

function reducer(state, action) {
  return match (action) {
    when { type: 'INCREMENT', amount } =>
      { ...state, count: state.count + (amount || 1) },
    when { type: 'DECREMENT', amount } =>
      { ...state, count: state.count - (amount || 1) },
    when { type: 'RESET' } =>
      { ...state, count: 0 },
    when { type: 'SET_USER', user: { name, email } } =>
      { ...state, user: { name, email } },
    default =>
      state
  };
}
Enter fullscreen mode Exit fullscreen mode

Bridging the Gap Today

While we wait for native pattern matching in JavaScript, several libraries offer similar functionality:

Using ts-pattern Library

import { match, P } from 'ts-pattern';

const result = match(payment)
  .with({ status: 'pending', method: 'credit', amount: P.when(n => n > 1000) }, 
    () => handleLargeCredit(payment))
  .with({ status: 'pending', method: 'credit' }, 
    () => handleSmallCredit(payment))
  .with({ status: 'pending', method: 'bank_transfer' }, 
    () => handleBankTransfer(payment))
  .with({ status: 'failed', attempts: P.when(n => n < 3) }, 
    () => retryPayment(payment))
  .with({ status: 'failed' }, 
    () => notifyCustomerFailed(payment))
  .with({ status: 'completed' }, 
    () => issueReceipt(payment))
  .otherwise(() => handleUnknownPayment(payment));
Enter fullscreen mode Exit fullscreen mode

Using a Custom Matcher

const matcher = (value) => {
  const matches = [];

  return {
    when: (predicate, handler) => {
      matches.push({ predicate, handler });
      return matcher(value);
    },
    otherwise: (handler) => {
      for (const { predicate, handler: matchHandler } of matches) {
        if (predicate(value)) {
          return matchHandler(value);
        }
      }
      return handler(value);
    }
  };
};

// Usage
const result = matcher(payment)
  .when(p => p.status === 'pending' && p.method === 'credit' && p.amount > 1000, 
    p => handleLargeCredit(p))
  .when(p => p.status === 'pending' && p.method === 'credit', 
    p => handleSmallCredit(p))
  // ... other cases
  .otherwise(p => handleUnknownPayment(p));
Enter fullscreen mode Exit fullscreen mode

Looking Ahead

As pattern matching becomes more integrated into JavaScript, we can expect to see several benefits:

  1. Cleaner code with fewer nested conditions
  2. Better error handling through exhaustive pattern checking
  3. More declarative programming focusing on what to match rather than how to check
  4. Enhanced readability making complex logic more approachable
  5. Reduced bugs from overlooking edge cases

The future of control flow in JavaScript is moving toward more expressive, declarative patterns that handle complexity with grace. By understanding and adopting pattern matching concepts today, you'll be well-positioned as the language evolves.

Conclusion

Pattern matching represents a significant upgrade to how we manage complex conditional logic. While JavaScript's native implementation is still in progress, understanding these concepts and leveraging existing libraries can already improve your code quality. As the language continues to evolve, those who embrace these patterns early will find themselves writing more maintainable, expressive, and robust code.

Are you already using pattern matching-like approaches in your JavaScript projects? Share your techniques and experiences in the comments below!

Comments 0 total

    Add comment