DEV Community

Timevolt
Timevolt

Posted on

The Matrix of Problem Solving: Breaking Down Complex Problems into Solvable Pieces

The Quest Begins (The "Why")

Honestly, I stared at my screen for what felt like an eternity, jaw clenched, coffee gone cold. The ticket read: “Generate a monthly loyalty‑statement for every active user, applying tier‑based discounts, tax rules, and shipping fees, but only for users who made ≥ 3 transactions in the month.”

It was a beast. My first attempt was a single 150‑line function that looped over transactions, built nested maps, applied conditionals inside conditionals, and somehow managed to mutate the original data while I was trying to be “efficient”. I ran the tests, watched them fail in spectacular ways, and felt like Neo dodging bullets—except I kept getting hit.

That frustration sparked the question: What if I stopped trying to slay the whole dragon at once and instead chopped it into bite‑size pieces?

The Revelation (The Insight)

The breakthrough came when I remembered a mantra I’d heard from a senior engineer during a code‑review: “Solve the smallest thing that could possibly work, then compose.”

It sounded simple, but the real magic was in the mental shift:

  1. Identify the independent actions – What does the problem actually require, step by step?
  2. Give each action a pure, testable function – No side effects, no hidden state.
  3. Compose the functions like LEGO bricks – Feed the output of one into the input of the next.

Suddenly the monster wasn’t a monolith; it was a pipeline:

raw transactions → filter → group → sum → apply discounts → add taxes/shipping → format
Enter fullscreen mode Exit fullscreen mode

Each stage could be unit‑tested in isolation, reasoned about without scrolling through hundreds of lines, and swapped out if the business rules changed. The “aha!” moment was realizing that complexity lives in the connections, not in the individual steps. If each step is trivial, the whole thing becomes trivial to understand and maintain.

Wielding the Power (Code & Examples)

Before: The “Monolithic Spell”

function generateLoyaltyStatement(transactions, users) {
  const statement = {};

  for (const tx of transactions) {
    const user = users.find(u => u.id === tx.userId);
    if (!user || !user.isActive) continue;

    // month key like "2024-09"
    const month = tx.date.slice(0, 7);
    if (!statement[user.id]) statement[user.id] = {};
    if (!statement[user.id][month]) statement[user.id][month] = {
      rawTotal: 0,
      txCount: 0,
      tier: user.loyaltyTier,
    };

    statement[user.id][month].rawTotal += tx.amount * (1 - tx.discount);
    statement[user.id][month].txCount += 1;
  }

  const result = {};
  for (const [uid, months] of Object.entries(statement)) {
    for (const [month, data] of Object.entries(months)) {
      if (data.txCount < 3) continue; // rule: need ≥3 tx

      let subtotal = data.rawTotal;
      // tier‑based discount
      if (data.tier === 'gold') subtotal *= 0.9;
      else if (data.tier === 'silver') subtotal *= 0.95;

      // tax (8.25%)
      const withTax = subtotal * 1.0825;

      // shipping: free if > $100, else $5
      const shipping = withTax > 100 ? 0 : 5;
      const total = withTax + shipping;

      if (!result[uid]) result[uid] = {};
      result[uid][month] = {
        rawTotal: data.rawTotal,
        discountApplied: data.rawTotal - subtotal,
        tax: withTax - subtotal,
        shipping,
        total,
      };
    }
  }
  return result;
}
Enter fullscreen mode Exit fullscreen mode

What’s wrong?

  • The function does five different jobs at once (filtering, grouping, summing, discounting, tax/shipping).
  • Mutating objects inside loops makes reasoning painful.
  • Changing a single rule means digging through nested conditionals.

After: The “Pipeline of Pure Functions”

// 1️⃣ Keep only active users' transactions
const filterActive = (txs, users) =>
  txs.filter(tx => {
    const user = users.find(u => u.id === tx.userId);
    return user && user.isActive;
  });

// 2️⃣ Group by userId & month, counting transactions and summing amounts
const groupByUserMonth = (txs) =>
  txs.reduce((acc, tx) => {
    const month = tx.date.slice(0, 7);
    const key = `${tx.userId}|${month}`;
    if (!acc[key]) {
      acc[key] = { userId: tx.userId, month, txCount: 0, rawTotal: 0 };
    }
    acc[key].txCount += 1;
    acc[key].rawTotal += tx.amount * (1 - tx.discount);
    return acc;
  }, {});

// 3️⃣ Apply the “≥3 transactions” filter
const enforceMinTx = (grouped) =>
  Object.values(grouped).filter(data => data.txCount >= 3);

// 4️⃣ Compute tier‑based discount
const applyTierDiscount = (data, users) => {
  const user = users.find(u => u.id === data.userId);
  let subtotal = data.rawTotal;
  if (user?.loyaltyTier === 'gold') subtotal *= 0.9;
  else if (user?.loyaltyTier === 'silver') subtotal *= 0.95;
  return { ...data, subtotal };
};

// 5️⃣ Add tax (8.25%)
const addTax = (data) => ({
  ...data,
  withTax: data.subtotal * 1.0825,
});

// 6️⃣ Add shipping (free > $100, else $5)
const addShipping = (data) => ({
  ...data,
  shipping: data.withTax > 100 ? 0 : 5,
  total: data.withTax + (data.withTax > 100 ? 0 : 5),
});

// 7️⃣ Shape final output
const shapeOutput = (processed, users) =>
  processed.reduce((acc, p) => {
    if (!acc[p.userId]) acc[p.userId] = {};
    acc[p.userId][p.month] = {
      rawTotal: p.rawTotal,
      discountApplied: p.rawTotal - p.subtotal,
      tax: p.subtotal * 0.0825,
      shipping: p.shipping,
      total: p.total,
    };
    return acc;
  }, {});

// 🎯 The pipeline – each step is tiny, testable, and composable
function generateLoyaltyStatement(transactions, users) {
  return pipe(
    filterActive,
    groupByUserMonth,
    enforceMinTx,
    (data) => data.map(d => applyTierDiscount(d, users)),
    (data) => data.map(addTax),
    (data) => data.map(addShipping),
    shapeOutput
  )(transactions, users);
}

// tiny utility for readability
const pipe = (...fns) => (x) => fns.reduce((v, f) => f(v), x);
Enter fullscreen mode Exit fullscreen mode

What changed?

  • Each function does one thing and does it well.
  • No hidden state; everything is passed explicitly.
  • Unit tests can target filterActive, applyTierDiscount, etc., without building a massive mock dataset.
  • If the tax rate changes, you edit only addTax.
  • The pipeline reads like a story: “Take the transactions, keep the active ones, bucket them, enforce the minimum, discount, tax, ship, format.”

Common Traps to Avoid

Trap Why it hurts How to dodge it
“God function” – trying to do everything in one place Makes reasoning impossible; a tiny change requires massive retesting. Split early; ask “What is the smallest independent piece I can extract?”
Mutating shared objects inside loops Leads to subtle bugs when the same object is reused elsewhere. Treat data as immutable; return new objects instead of modifying the input.
Skipping the composition step – leaving functions isolated You end up with a bunch of utilities but no clear flow. Write a thin orchestrator (the pipe) that makes the intent obvious.

Why This New Power Matters

Now I can look at a gnarly feature and instantly see the seams where I can cut. The mental model isn’t just “write smaller functions”; it’s a framework for problem solving:

  1. Decompose – list the distinct actions the solution must perform.
  2. Isolate – turn each action into a pure function with a clear contract.
  3. Compose – stitch those functions together in a readable pipeline.

The payoff? Faster debugging, fearless refactoring, and the delight of watching a test suite turn green after you swap out a single piece. It’s like discovering a hidden shortcut in a video game that lets you bypass the final boss—except the shortcut is better code, and the boss is technical debt.

Give it a try on your next ticket: write down the steps, turn each into a function, and watch the complexity melt away.

Your quest: Pick a recent chunk of code that made you sigh, break it into three pure functions, and compose them. Drop a link to your refactor in the comments—I’d love to see your pipeline in action!


Happy coding, and may your bugs be few and your commits be many!

Top comments (0)