DEV Community

Black
Black

Posted on

Debt snowball vs avalanche: settling it with a 40-line JavaScript simulation

Personal finance arguments about the debt snowball and the debt avalanche usually stay abstract. They are easy to settle with a month-by-month simulation. Here is the one behind my free snowball vs avalanche calculator, which runs entirely in the browser.

The two rules

Both methods do the same three things every month:

  1. Pay the minimum on every debt.
  2. Put all extra money on one target debt.
  3. When a debt is paid off, roll its whole payment into the next target.

The only difference is the order. Snowball targets the smallest balance first. Avalanche targets the highest interest rate first.

The simulation

// debts: [{name, balance, apr, min}], order: indexes in target order
function simulate(debts, order, extra) {
  const bal = debts.map(d => d.balance);
  const rate = debts.map(d => d.apr / 1200);           // monthly rate
  const budget = debts.reduce((s, d) => s + d.min, 0) + extra;
  let month = 0, interest = 0;
  const paidOff = [];

  while (bal.some(b => b > 0.005) && month < 600) {
    month++;
    let left = budget;

    // 1. interest accrues
    bal.forEach((b, i) => { if (b > 0) { const k = b * rate[i]; bal[i] += k; interest += k; } });

    // 2. minimums
    bal.forEach((b, i) => {
      if (b > 0) { const p = Math.min(b, debts[i].min, left); bal[i] -= p; left -= p; }
    });

    // 3. everything left goes to the target, then the next one
    for (const i of order) {
      if (left <= 0) break;
      if (bal[i] > 0) { const p = Math.min(bal[i], left); bal[i] -= p; left -= p; }
    }

    bal.forEach((b, i) => {
      if (b <= 0.005 && !paidOff.some(p => p.i === i)) paidOff.push({ i, month });
    });
  }
  return { month, interest, paidOff, stuck: month >= 600 };
}
Enter fullscreen mode Exit fullscreen mode

The "rollover" in step 3 needs no special code. The monthly budget stays fixed, so when a debt reaches zero, the money that used to pay its minimum is still in left, and the loop sends it to the next target.

Order is just a sort:

const snowball  = idx.sort((a, b) => debts[a].balance - debts[b].balance);
const avalanche = idx.sort((a, b) => debts[b].apr - debts[a].apr);
Enter fullscreen mode Exit fullscreen mode

(Sort a copy for each one. sort changes the array in place.)

Edge cases that matter

  • Payments that never catch up. If the minimums don't cover the monthly interest, the loop would run forever. The 600-month cap turns that into a "stuck" result, and the UI tells the user to raise the extra amount.
  • Floating-point dust. Treat any balance under half a cent as paid. Otherwise a debt can sit at 0.0000001 and stay "open" for an extra month.
  • The final month. Math.min(balance, payment) keeps the last payment from going negative. The leftover rolls on in the same month, as it would in real life.

What the numbers say

With four sample debts (a $4,200 card at 24.9%, a $900 store card at 29.9%, an $11,500 car loan at 7.5% and a $3,000 personal loan at 13%) and $200 extra a month:

Debt-free Interest
Snowball 30 months $3,098
Avalanche 29 months $2,860
Minimums only 44 months $5,552

Avalanche saves $238 here. Paying the extra $200 at all saves far more than the choice of method. That is the usual pattern: the gap between the methods is often small, and the gap between paying extra and not paying extra is large. So pick the method you will stick with.

For people who would rather track it on paper, I made a printable Monthly Budget and Debt Payoff Planner. It has a debt list, a month-by-month payoff tracker and milestones to tick off.

Interest is computed monthly at APR / 12, with no fees or promo rates. Real statements will differ slightly.

Top comments (0)