I shipped a daily compound interest calculator last week. The math sounded like a solved problem: A = P(1 + r/n)^(nt), wire it to some inputs, done.
It wasn't, and the interesting bugs weren't in the formula.
The bug most calculators ship
The textbook formula covers a lump sum sitting untouched. The moment you add regular contributions, say $100 every month, you have to decide when each deposit starts earning.
The lazy implementation adds contributions at the end:
// wrong: contributions never compound
const balance = P * Math.pow(1 + r / n, n * t) + monthly * 12 * t;
The correct one compounds each deposit from the day it lands:
// each contribution compounds for its own remaining term
let balance = P * Math.pow(1 + dailyRate, totalDays);
for (let day = 1; day <= totalDays; day++) {
if (isDepositDay(day)) {
balance += monthly * Math.pow(1 + dailyRate, totalDays - day);
}
}
(In production I use the closed-form annuity formula instead of the loop, but the loop version is the one that makes the difference obvious.)
How much does it matter? Take $10,000 at 5%, daily compounding, plus $100/month for ten years:
- contributions compounded from deposit date: $32,022.28
- contributions bolted on at the end: $28,486.65
That's $3,535.63 of silent error. I checked several ranking calculators while building this, and more than one ships the second number.
The 360/365 toggle that does nothing
Banks quote "interest compounded on a 360-day basis," so I added a 365/360 day-count toggle, expecting drama.
At the same nominal rate, changing only the exponent basis moves $10,000 over ten years by one cent. The toggle is honest, but advertising it as a feature would be theater.
The thing that actually moves money is the bankers' method: divide the annual rate by 360, then charge interest for all 365 days. That multiplies the effective rate by 365/360, a 1.39% bump, which turns 5% into ~5.07% and $16,486.65 into roughly $16,601. Same label, three orders of magnitude more effect. If you build one of these, model the method, not the toggle.
Rounding will betray your own docs
I wrote the explainer copy with full-precision math: 5% × 365/360 = 5.069444…%, giving $16,601.52.
Then I typed the rounded rate a reader would actually enter (5.0694%) into my own calculator: $16,601.45.
Seven cents apart, and a reader who checks the worked example against the tool concludes one of them is broken. The fix was editorial, not numerical: the page now says "roughly $16,601" and explains why. If your copy quotes numbers your own tool can't reproduce keystroke-for-keystroke, you lose the only thing a calculator page has, which is trust.
URL state: only serialize the diff
First version wrote every input to the query string on every change. Share links looked like:
?p=10000&r=5&c=0&cf=12&n=daily&y=10&b=365
Two problems. Cosmetically it's noise. Practically, a shared link froze every setting, including ones the sender never touched, so recipients opened a calculator configured differently from the page's own documentation (monthly vs daily compounding, in my case, which contradicts the copy right below it).
Now only non-default values serialize:
const qs = Object.entries(state)
.filter(([k, v]) => v !== DEFAULTS[k])
.map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
.join("&");
Untouched calculator gives a clean URL. Change one field and you get ?c=100. Reset it, clean again.
Verify against a second implementation, not your own eyes
Every number above went through a separate closed-form script before shipping: annuity formula, no loops, different code path. The loop implementation and the closed form agreed to the cent ($6,977.00 of pure annuity interest in the test case) before anything went live.
It caught one real discrepancy (the rounding issue above) that eyeballing the UI never would have. For a page whose entire value is "these numbers are right," a reference implementation is the cheapest insurance there is.
The calculator is free and runs client-side: toolmate.io/daily-compound-interest-calculator
Top comments (0)