DEV Community

zhihu wu
zhihu wu

Posted on

Your Amortization Loop Never Reaches Zero: 4 Bugs in Loan Math Code

If you've ever generated an amortization schedule by hand, you've probably hit this: you loop 360 times and the balance lands on... -$3.07. Or +$0.84. Never exactly zero.

Here are four bugs I keep running into, with numbers you can check yourself.

1. The loop's last step is a lie

$300,000 at 7% over 360 months. The exact payment is 1,995.9074..., which you round to 1,995.91. Run balance = balance + balance*r - payment 360 times and the balance ends at -$3.07 - you overpaid by three dollars because you rounded the payment up. Round down and you finish with a small balance the borrower still owes.

Switching to integer cents doesn't fix it; it just relocates the error (same loan, same 360 iterations: -355 cents).

The fix is to stop trusting the last iteration. After payment 359 the balance is $1,981.28, so the final payment must be balance * (1 + r) = $1,992.84. Lenders do exactly this: a fixed payment for every month except the last.

2. Accruing interest after subtracting the payment

balance = (balance - payment) * (1 + rate);  // wrong
balance = balance * (1 + rate) - payment;    // right
Enter fullscreen mode Exit fullscreen mode

Both look reasonable. They differ by one period of interest on the payment itself, and the first version "pays off" in 354 payments instead of 360 while reporting $406,552 of total interest instead of $418,527 - $11,975 short. Nobody notices, because the schedule still ends near zero.

3. setMonth() drifts

A schedule assumes "the same day each month". JavaScript disagrees:

new Date(2026, 0, 31).setMonth(1) // -> Mar 3, not Feb 28
Enter fullscreen mode Exit fullscreen mode

A 30-year schedule built month-by-month slides forward every time it crosses a short month. The dates in the table look plausible; they're just wrong by a few days that keep accumulating.

4. "Extra payment" is not principal by default

Many servicers apply extra money to your next installment, which only advances the due date. The loan still runs the full term and total interest is unchanged. You have to mark it principal-only, then verify on the next statement that the principal balance actually dropped.

Get it right and the payoff is real: $100/month extra on that $300k loan clears it in 310 payments - about 4.2 years early, roughly $69,300 of interest saved.

I keep a free loan calculator open for checking these by hand - it runs entirely in the browser, shows the full schedule and the year-end summaries, which is how I caught the rounding bug in my own script.

Top comments (0)