DEV Community

Elay osetrov
Elay osetrov

Posted on

I built a debt snowball vs avalanche calculator in 3 KB of vanilla JS (and its default example shows no difference)

I wanted a debt payoff calculator that loads instantly, needs no signup and sends nothing to a server. The result is one HTML file, about 3 KB, hosted free on GitHub Pages:

Live: https://elaysuu.github.io/debt-snowball-calculator/
Source: https://github.com/elaysuu/debt-snowball-calculator

This post walks through the simulation loop, plus one thing that surprised me when I tested it.

The two strategies

  • Snowball: pay minimums on everything, put every extra dollar on the smallest balance first.
  • Avalanche: same, but put the extra on the highest APR first.

When a debt is paid off, its minimum "rolls" into the next one in line. So the whole thing is: a fixed monthly budget, an ordering rule, and a loop.

The simulation, month by month

The core is a single function. ds is the list of debts (b balance, a APR %, m minimum), extra is the extra monthly amount, and av picks the strategy:

function sim(ds, extra, av) {
  ds = ds.map(d => ({ ...d }));
  // order: avalanche = highest APR first, snowball = smallest balance first
  let o = ds.map((d, i) => i).sort((i, j) =>
    av ? (ds[j].a - ds[i].a || i - j) : (ds[i].b - ds[j].b || i - j));
  let B = ds.reduce((s, d) => s + d.m, 0) + extra, mo = 0, int = 0;
  while (ds.some(d => d.b > .005) && mo < 600) {
    mo++;
    // 1. accrue one month of interest (APR / 12)
    let ow = o.map(i => { let d = ds[i], v = d.b * (1 + d.a / 1200); int += v - d.b; return v; });
    // 2. reserve each debt's minimum (or its full balance if smaller)
    let need = ow.map((v, k) => Math.min(v, ds[o[k]].m)), paid = 0;
    // 3. walk the priority list: each debt gets whatever is left
    //    after reserving minimums for the debts behind it
    o.forEach((i, k) => {
      let p = Math.max(0, Math.min(ow[k],
        B - paid - need.slice(k + 1).reduce((s, x) => s + x, 0)));
      paid += p;
      ds[i].b = Math.max(0, ow[k] - p);
    });
  }
  return [mo, int];
}
Enter fullscreen mode Exit fullscreen mode

A few choices worth calling out:

  1. The budget B is constant. It's the sum of all original minimums plus the extra. That constant budget is what makes the "rollover" happen automatically: once a debt hits zero, its share simply flows to the next one in the loop.
  2. Minimums for later debts are reserved first. The debt at the front of the line gets budget - already paid - minimums still owed behind it. That keeps every account current while the target gets everything else.
  3. A 600-month cap. If the payments don't cover the interest, the loop would never end. At 600 months the page shows a warning to raise the extra amount instead.
  4. Ties break by original order (|| i - j), so the result is deterministic.

No framework, no build step, no dependencies. go() just reads the table inputs, runs sim twice and prints both results.

The surprise: the default example shows no difference

The page loads with three sample debts (sample data, not anyone's real finances):

Debt Balance APR Min/month
Credit card 3,200 24.9% 95
Car loan 8,700 7.5% 240
Store card 650 29.9% 35

With 200 extra per month, both strategies come out identical: 25 months and about 1,479 in interest.

That looked like a bug at first. It isn't. In this sample, the smallest balance (store card) also has the highest APR, and the credit card is second on both lists. Snowball and avalanche produce the same order, so they produce the same plan.

Here is a second sample (again, example data) where the orders differ, computed with the same sim function:

Debt Balance APR Min/month
Personal loan 2,500 6.9% 75
Credit card 9,000 26.9% 270
Medical bill 1,200 0% 50

With 200 extra per month:

  • Snowball: 29 months, about 4,051 interest
  • Avalanche: 27 months, about 3,147 interest

Snowball spends its early extra on the 0% medical bill and the 6.9% loan while the 26.9% card keeps compounding. The gap here is about 904 and two months.

The takeaway for anyone writing a comparison tool: the two strategies only diverge when "smallest balance" and "highest rate" point at different debts. If your test case doesn't have that, your test can't tell the strategies apart.

Caveats

  • Interest is monthly APR/12 on the balance, with no fees, promo rates or variable rates.
  • It's an estimate, not financial advice.
  • Everything runs in the browser; nothing is stored or sent.

Try it or fork it

Disclosure: I also sell a spreadsheet version with a month-by-month schedule and a payoff date per debt, linked from the calculator page (Debt Payoff Planner). The calculator itself is free and stays free.

Top comments (0)