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:
- Pay the minimum on every debt.
- Put all extra money on one target debt.
- 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 };
}
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);
(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.0000001and 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)