Coin Change

Dynamic Programming
avg O(n·a)

Coin change asks a simple question: what is the fewest number of coins that add up to an amount? The obvious answer - always grab the biggest coin - happens to work for US coins, so almost everyone trusts it. But for many coin systems greedy silently returns a wrong count, with no error and no warning. Dynamic programming fixes this by proving the best answer for every amount from 0 up, so the final answer is built on facts, not habits.

Watch the table beat greedy

Press play - or drag the timeline and step through it yourself.
Coin 1Cells filled 0Choices 0Step 1 / 35
012345678910
coins {1}···········
coins {1,3}···········
coins {1,3,4}···········

Make amount 10 with coins {1, 3, 4} - fewest coins wins. Each row adds one coin type. Each cell: the fewest coins for that amount.

being filled skip / take options fewest coins

Complexity

Best
O(n·a)
Input already in order
Average
O(n·a)
Normal mixed input
Worst
O(n·a)
Worst possible input
Space
O(a)
Extra memory used

The snack machine with odd coins

Imagine a country whose coins are 1, 3 and 4 cents, and a snack that costs 10. Your instinct says: grab the biggest coin. So you pay 4, then another 4, and now you are stuck at 2 - two clumsy 1-cent coins finish the job. Four coins total. Your friend pays 4 + 3 + 3: three coins. Your instinct was not just slower, it was wrong - and it never told you. The careful way is to work out the cheapest way to pay 1 cent, then 2, then 3, and so on up to 10, each answer reusing the smaller ones. That way nothing is a guess.

How it works, step by step

  1. Build a table: one column per amount from 0 to the target, one row per coin type you allow.

  2. The first cell is free: amount 0 needs 0 coins. Every other cell builds on it.

  3. Each cell asks two questions. Skip the row's newest coin: copy the answer from the row above. Take it once: look left by the coin's value in the same row, and add 1.

  4. Keep the smaller of the two. That cell is now the proven best for its amount and its coin set - no guess involved.

  5. Fill left to right, row by row. The bottom-right cell is the fewest coins for the full target.

  6. To learn which coins were used, walk backwards from the answer: if the cell equals the one above, the coin was skipped; otherwise take the coin and jump left by its value.

The code, in JavaScript

The standard version: one dp array over amounts, reused for every coin. dp[a] means "fewest coins that make amount a". Infinity marks amounts we cannot make yet.

javascript
function minCoins(coins, amount) {
  // dp[a] = fewest coins that make amount a.
  // Infinity = "cannot be made yet" - NOT 0!
  const dp = new Array(amount + 1).fill(Infinity);
  dp[0] = 0;                        // amount 0: take nothing

  for (const coin of coins) {
    for (let a = coin; a <= amount; a++) {
      // Best without this coin (dp[a]) vs
      // take one coin + best for what remains (dp[a - coin] + 1).
      dp[a] = Math.min(dp[a], dp[a - coin] + 1);
    }
  }

  return dp[amount] === Infinity ? -1 : dp[amount];
}

minCoins([1, 3, 4], 10);   // → 3   (4 + 3 + 3)
minCoins([25, 10, 5, 1], 30); // → 2  (25 + 5)
minCoins([2, 5], 3);       // → -1  (3 cannot be made)

Dry run: making 10 with coins {1, 3, 4}

The same steps the visualizer plays, written as a table. Generated by running the real algorithm - not written by hand.

StepCoinWhat happened
12Amount 3: skip coin 3 (3) or take one (0 + 1 = 1). Take it - 1 beats 3.
22Amount 4: skip coin 3 (4) or take one (1 + 1 = 2). Take it - 2 beats 4.
32Amount 5: skip coin 3 (5) or take one (2 + 1 = 3). Take it - 3 beats 5.
42Amount 6: skip coin 3 (6) or take one (1 + 1 = 2). Take it - 2 beats 6.
52Amount 7: skip coin 3 (7) or take one (2 + 1 = 3). Take it - 3 beats 7.
62Amount 8: skip coin 3 (8) or take one (3 + 1 = 4). Take it - 4 beats 8.
72Amount 9: skip coin 3 (9) or take one (2 + 1 = 3). Take it - 3 beats 9.
82Amount 10: skip coin 3 (10) or take one (3 + 1 = 4). Take it - 4 beats 10.
93Amount 4: skip coin 4 (2) or take one (0 + 1 = 1). Take it - 1 beats 2.
103Amount 5: skip coin 4 (3) or take one (1 + 1 = 2). Take it - 2 beats 3.
113Amount 6: taking coin 4 costs 3, but the row above already has 2. Skip the coin.
123Amount 7: skip coin 4 (3) or take one (1 + 1 = 2). Take it - 2 beats 3.
133Amount 8: skip coin 4 (4) or take one (1 + 1 = 2). Take it - 2 beats 4.
143Amount 9: skipping gives 3, taking coin 4 also gives 3. A tie - keep 3.
153Amount 10: skip coin 4 (4) or take one (2 + 1 = 3). Take it - 3 beats 4.

Good choice when…

  • The question is "fewest pieces to hit an exact total": coin change, fewest perfect squares that sum to n, fewest steps with fixed step sizes.
  • The coin values are arbitrary or user-defined. That is exactly when greedy cannot be trusted - DP is the safe default.
  • You also need to report which coins. A small `lastCoin` array replays the answer for free.
  • Many amounts share one coin set: one dp array answers every amount up to the target at once.

Bad choice when…

  • The coin system is a standard currency (US, euro). Greedy is proven correct for those systems, and it is simpler and faster.
  • The amount is huge and the coins are few - the table has one column per amount, so O(n·a) time and O(a) space can blow up. Look at BFS over remainders or math shortcuts.
  • Each coin may be used at most once. That is 0/1 knapsack - same idea, but the inner loop must run backwards.

Common mistakes

  • Greedy really is correct for US coins - and that success is the trap. Swap in {1, 3, 4} and greedy pays 4 coins for amount 10 when 3 is enough. No exception, no warning, just a wrong number in production.
  • Initialize dp with Infinity, not 0. Zeros make every amount look already solved, so `Math.min` never updates anything. Only dp[0] starts at 0. Bonus: in JavaScript `Infinity + 1` is still `Infinity`, so unreachable amounts stay unreachable through the math.
  • dp[amount] can still be Infinity at the end ({2, 5} can never make 3). Return -1 or null explicitly - otherwise Infinity leaks into the caller's arithmetic.
  • Counting the number of ways to make change looks like a one-line edit (`+=` instead of `Math.min`) - but suddenly loop order matters. Coins outside, amounts inside counts each combination once. Amounts outside counts orderings too, so 1+3 and 3+1 both count. For fewest coins either order works, which is exactly why the trap goes unnoticed.

Coin Change vs. its closest relatives

AlgorithmBestAverageWorstSpaceStable
Coin Changethis pageO(n·a)O(n·a)O(n·a)O(a)-
0/1 KnapsackO(n·W)O(n·W)O(n·W)O(W)-
Fibonacci: Memo vs TabulationO(n)O(n)O(n)O(n)-
Longest Common SubsequenceO(n·m)O(n·m)O(n·m)O(n·m)-