DEV Community

Marc
Marc

Posted on

We Shipped a Mortgage Calculator Bug Where Every Test Was Green and the Answer Was Still Wrong

A review pass on our credit calculator flagged something that should have been impossible: a
finding tagged HIGH, on a panel with a full test suite, where every individual test passed and
the answer was still wrong. Not "wrong in an edge case" wrong. Wrong for every single user who had
ever configured a Sondertilgung (a lump-sum extra payment on their mortgage) and then looked at
the "what if I invest the difference instead" comparison next to it.

The bug: the comparison panel always ran the loan projection with an empty additionals list. The
headline result above it, the number the user actually configured and trusted, included their
real Sondertilgungen. So if you had a 20k lump-sum payment scheduled for month 6, the headline
said "your loan is basically gone by year 8" and the panel right next to it, presented as
directly comparable, was quietly running a different loan. Not a rounding difference. A different
payoff date, a different remaining balance, a different verdict on whether investing instead
would have won.

Every test was green because every test checked one function against one input. Nothing checked
that the two numbers on the same screen were talking about the same loan.

Why "pay down debt faster vs. invest the difference" is rigged by default

That specific bug is a symptom of a more general trap. Almost every calculator online that lets
you compare "pay down debt faster" against "invest the difference instead" gets the comparison
itself wrong, independent of any coding bug — because it quietly changes the monthly budget
between the two scenarios and presents the result as a fair fight.

Say you have a 300k loan at 3.5%, paying 3% annual amortization. Someone suggests: "drop to 1%
amortization, invest the rest." The naive calculator compares:

  • Scenario A: 3% amortization, no investing, ~1200/month total outflow
  • Scenario B: 1% amortization, invest a fixed 200/month, ~950/month total outflow

Of course B looks better over ten years. You're comparing spending 1200/month against spending
950/month and marveling that the cheaper option built more wealth. It didn't win on insight. It
won because you fed it a smaller number.

The fix is a structural invariant, not a footnote:

credit installment (this month) + ETF contribution (this month) = fixed monthly budget
Enter fullscreen mode Exit fullscreen mode

Every month, for both scenarios, and once a loan is paid off in one scenario, its full budget
rolls into the ETF contribution from that point on. You don't get to just stop spending because
the mortgage happened to end early in the model. Once that holds, "total interest paid" stops
being a meaningful metric (it no longer means the same thing across scenarios that pay
differently), and the only honest number left is:

net worth(t) = ETF portfolio(t) - remaining loan balance(t)
Enter fullscreen mode Exit fullscreen mode

at a fixed horizon, for both scenarios, with identical cash outflow throughout.

The invariant was never the bug. Something else was.

Here's the part that made this finding interesting: our budget invariant was correctly
implemented from day one. Every month's ETF contribution is computed as monthlyBudget -
loanRate
, not hardcoded, not assumed. If you'd audited that one property in isolation, you'd have
signed off.

The actual failure mode was a level up from that: getting the fair-comparison math right and then
silently comparing two different loans anyway. The panel and the headline agreed on the rule
("budget minus installment goes to the ETF") but disagreed on the input ("what installment,
exactly, on what loan"). A structurally correct comparison of the wrong pair of scenarios is still
wrong, and none of the unit tests around the fair-comparison logic could have caught it, because
that logic was never the part that broke.

The fix threads the same additionals the headline uses through the comparison panel, so both
numbers on the screen are now guaranteed to describe the same loan.

The actual lesson, and something to go check right now

Budget-neutrality gets cited (correctly) as the thing naive pay-down-vs-invest calculators get
wrong. It's necessary. It is not sufficient. You also have to pin every other input, identically,
across both scenarios you're comparing, or the comparison quietly starts answering a different
question than the one on the label, and a green test suite will not tell you.

If you've got a mortgage calculator, a rent-vs-buy tool, or a pay-down-vs-invest comparison
bookmarked somewhere: go feed it a scenario with something extra configured (a lump-sum payment,
an irregular income month, whatever the tool supports), and check whether the comparison panel
still uses it. Most won't tell you either way. Check anyway.


This is the Tilgung-vs-ETF panel in cashcolt, a free
no-signup mortgage calculator hosted in Germany. No tracking, no login, poke at it with your own
numbers.

Top comments (0)