Quick Answer

The coin change problem is really two different problems that share a name. The first asks for the minimum number of coins to reach an amount, solved with a DP recurrence that takes a min() over coin choices. The second asks how many distinct ways there are to reach an amount, and it needs coins in the outer loop and amount in the inner loop — swap that order and you silently count ordered permutations instead of combinations, inflating the answer. Both are demonstrated and verified below against brute force, with the exact wrong number the bug produces.

Two Problems, One Name

"The coin change problem" gets used for two different questions that happen to share coins and an amount as inputs, and nothing else structurally:

  • Minimum coins: given coin denominations and a target amount, what's the fewest coins that sum to it?
  • Number of ways: given the same inputs, how many distinct combinations of coins sum to it (order doesn't matter — three 1s and a 2 is one combination, not several)?

Both are solved with dynamic programming, and both build a 1D array indexed by amount. That surface similarity is exactly what causes bugs: it's easy to assume the loop structure that works for one works for the other. It doesn't. The first uses min() and is loop-order-independent. The second sums counts, and gets the loop order wrong in exactly the way this article demonstrates, on real numbers, below.

Variant 1: Minimum Coins

The recurrence: to make amount a with the fewest coins, try every coin ca, and take 1 plus whatever the fewest coins is for the remaining amount a - c. Take the best option across all coins.

function minCoinsDP(coins, amount) {
  const dp = new Array(amount + 1).fill(Infinity);
  dp[0] = 0;
  for (let a = 1; a <= amount; a++) {
    for (const c of coins) {
      if (c <= a && dp[a - c] + 1 < dp[a]) {
        dp[a] = dp[a - c] + 1;
      }
    }
  }
  return dp[amount] === Infinity ? -1 : dp[amount];
}

Run against a memoized brute-force recursive version on real inputs, printed directly from the executed script:

coins=[1,5,10,25] amount=63: DP=6 brute=6 [MATCH]
coins=[1,3,4] amount=6: DP=2 brute=2 [MATCH]
coins=[2,5] amount=11: DP=4 brute=4 [MATCH]
coins=[3,7] amount=5: DP=-1 brute=-1 [MATCH]

63 cents with US coins takes 6: two quarters, one dime, three pennies. The last row is worth noting — with only 3-cent and 7-cent coins, amount 5 is genuinely impossible, and both implementations correctly return -1 instead of a wrong number.

Variant 2: Counting the Ways

Counting distinct combinations needs a different recurrence and, critically, a different loop order. The correct version puts coins in the outer loop, amount in the inner loop:

function countWaysCorrect(coins, amount) {
  const dp = new Array(amount + 1).fill(0);
  dp[0] = 1;
  for (const c of coins) {
    for (let a = c; a <= amount; a++) {
      dp[a] += dp[a - c];
    }
  }
  return dp[amount];
}

Why coins-outer matters: fully processing one denomination before moving to the next means each combination gets built in a fixed, canonical coin order — you can never reach the same combination through two different orderings, so it's counted exactly once. This is the part that's easy to get backwards, and the next section shows exactly what happens when you do.

The Bug: Swapping the Loop Order Overcounts

Swap the two loops — amount outer, coins inner — and the code still runs and still returns a number. That number is wrong:

function countWaysBuggy(coins, amount) {
  const dp = new Array(amount + 1).fill(0);
  dp[0] = 1;
  for (let a = 1; a <= amount; a++) {
    for (const c of coins) {
      if (c <= a) dp[a] += dp[a - c];
    }
  }
  return dp[amount];
}

Run both against a brute-force count of actual combinations, for coins [1, 2, 5] and amount 5, printed directly from the executed script:

countWaysCorrect:                 4
countWaysBuggy (amount-outer):    9
brute force combinations:         4
brute force ORDERED sequences:    9

The buggy version returns 9, and 9 is not a random wrong number — it's the exact count of ordered sequences of coins summing to 5 (1+1+1+1+1, 1+1+1+2, 1+1+2+1, 1+2+1+1, 2+1+1+1, 1+2+2, 2+1+2, 2+2+1, 5). The amount-outer loop lets every ordering of the same coins get counted separately, because by the time it reaches amount a it has no memory of which coin was used last. It silently answers a different, related question instead of the one you asked.

Verifying Both Variants Against Brute Force

Running all three implementations across multiple coin sets confirms the pattern holds, not just for one lucky example:

coins=[1,2,5] amount=5: CORRECT=4  BUGGY=9  brute-combinations=4
coins=[2,3,5] amount=8: CORRECT=3  BUGGY=6  brute-combinations=3
coins=[1,5,10] amount=12: CORRECT=4 BUGGY=18 brute-combinations=4

The correct, coins-outer version matches brute-force combinations every time. The buggy, amount-outer version overcounts every time, and the size of the overcount grows with how many orderings each combination has — for [1,5,10] and amount 12, it's off by more than 4x. This is the kind of bug that passes a single hand-checked test case and then fails silently in production on any input with more than one way to make change. It's also worth testing this way rather than eyeballing the code, because both versions look equally plausible on a read-through — the bug is invisible until you check the actual returned number against something independently correct.

When to Use Which

Use minimum coins when the real question is "what's the fewest units to reach a target" — vending machine change, resource allocation with discrete units, or any interview question phrased around minimizing count.

Use the counting-ways variant when the question is about how many distinct combinations exist — this comes up in combinatorics problems and any "how many ways can you partition this" question with a fixed set of parts.

If you only remember one thing: whenever a DP problem involves both a set of items (coins) and a target (amount), ask whether the order of picking items should matter to the answer. If it shouldn't, put items in the outer loop. Getting that backwards doesn't crash — it just quietly returns a larger, wrong number.

Frequently Asked Questions

What's the actual difference between the two coin change problems? One asks for the minimum number of coins to reach an amount and uses a min() recurrence. The other asks how many distinct combinations of coins sum to the amount, and needs coins in the outer loop of the DP so each combination is counted only once.
Why does loop order matter for counting ways but not minimum coins? Minimum coins takes the best of several options regardless of order, so swapping loops doesn't change the answer. Counting ways sums counts, so an amount-outer loop lets the same combination be reached through multiple coin orderings and counts each one separately.
How big is the overcount bug in practice? For coins [1,2,5] and amount 5, the correct count is 4 but the buggy amount-outer loop returns 9 — it's actually counting ordered sequences, not combinations. The gap grows with the number of coins and the amount.
Can I tell if my counting-ways code has this bug without a reference answer? Yes — check whether the code treats {1,2} and {2,1} as the same combination or two. If your nested loop has amount as the outer loop, it's almost certainly counting them separately, which is the bug.
Does the minimum-coins version ever fail the same way? No — since it takes a min() instead of summing, loop order doesn't affect its correctness. Its only special case is returning -1 (or infinity) when the amount can't be made from the given coins at all.