How the Decision tools rent vs buy calculator is verified

Inputs

InputFieldType
House pricehomePricenumber
Deposit (%)depositPctnumber
Mortgage rate (%)mortgageRatePctnumber
Mortgage term (years)mortgageTermYearsinteger
Buying costs (% of price)closingCostsPctnumber
Selling costs (% of value)sellingCostsPctnumber
Property tax (% of value / yr)propertyTaxPctnumber
Buildings insurance / yrinsurancePerYearnumber
Maintenance (% of value / yr)maintenancePctnumber
Service charge / monthhoaPerMonthnumber
House price growth per year (%)appreciationPctnumber
Mortgage interest tax relief (%)taxReliefPctnumber
Rent / monthmonthlyRentnumber
Rent rises per year (%)rentInflationPctnumber
Contents insurance / yrrentersInsurancePerYearnumber
Investment return the renter earns (%)investmentReturnPctnumber
Years you stayyearsinteger

The formula

Each derived value below is computed by the Valem engine from the expression shown, in this order. Nothing else runs — there is no hidden code path.

Derived valueFieldFormula (JSONata)
Depositdeposit$round(homePrice * depositPct / 100, 2)
Loan AmountloanAmount$round(homePrice - deposit, 2)
Closing CostsclosingCosts$round(homePrice * closingCostsPct / 100, 2)
Monthly RatemonthlyRatemortgageRatePct / 1200
Compound FactorcompoundFactor$power(1 + monthlyRate, mortgageTermYears * 12)
Monthly PaymentmonthlyPaymentloanAmount <= 0 ? 0 : (monthlyRate = 0 ? $round(loanAmount / (mortgageTermYears * 12), 2) : $round(loanAmount * monthlyRate * compoundFactor / (compoundFactor - 1), 2))
Net worth on each pathcomparison( $r := $reduce([1..years], function($a, $y) { ( $term := mortgageTermYears * 12; $hv := $round(homePrice * $power(1 + appreciationPct / 100, $y), 2); $hvPrev := $round(homePrice * $power(1 + appreciationPct / 100, $y - 1), 2); $m1 := $min([($y - 1) * 12, $term]); $m2 := $min([$y * 12, $term]); $b1 := $balanceAt($m1, loanAmount, monthlyRate, compoundFactor, $term); $b2 := $balanceAt($m2, loanAmount, monthlyRate, compoundFactor, $term); $interest := $max([0, $round(monthlyPayment * ($m2 - $m1) - ($b1 - $b2), 2)]); $relief := $round($interest * taxReliefPct / 100, 2); $principal := $round($b1 - $b2, 2); $carry := $round($interest - $relief + $hvPrev * propertyTaxPct / 100 + $hvPrev * maintenancePct / 100 + insurancePerYear + hoaPerMonth * 12, 2); $outlay := $round($carry + $principal, 2); $rentYear := $round(monthlyRent * 12 * $power(1 + rentInflationPct / 100, $y - 1) + rentersInsurancePerYear, 2); $invested := $round($outlay - $rentYear, 2); $port := $round(($a.portfolio + $invested) * (1 + investmentReturnPct / 100), 2); { "portfolio": $port, "rows": $append($a.rows, [{ "year": $y, "homeValue": $hv, "mortgageBalance": $b2, "buyCost": $carry, "rentCost": $rentYear, "buyNet": $round($hv - $b2 - $hv * sellingCostsPct / 100, 2), "rentNet": $port }]) } ) }, { "portfolio": $round(deposit + closingCosts, 2), "rows": [] }); [$r.rows] )
Buying pulls ahead in yearbreakevenYear( $hits := $map(comparison, function($c) { $c.buyNet >= $c.rentNet ? $c.year : 0 })[$ > 0]; $count($hits) = 0 ? 0 : $min($hits) )
Total cost of owningtotalBuyCost$round($sum($map(comparison, function($c) { $c.buyCost })), 2)
Total rent paidtotalRentCost$round($sum($map(comparison, function($c) { $c.rentCost })), 2)
Net worth differencenetAdvantage( $d := $map(comparison, function($c) { $round($c.buyNet - $c.rentNet, 2) }); $i := $count($d) - 1; $d[$i] )
Favours BuyingfavoursBuyingnetAdvantage >= 0

Test vectors (3)

Every case is executed against this model on each build, by the Java engine and independently by a JavaScript one. A mismatch of a single cent fails the build, so the page cannot ship advertising a figure the model no longer produces.

Case 1 — 400,000 home at 6.5% with 20% down vs 2,200 rent, 3% appreciation, 7% return, staying 20 years

given homePrice400000
given depositPct20
given mortgageRatePct6.5
given monthlyRent2200
given appreciationPct3
given investmentReturnPct7
given years20
expect deposit80000
expect closingCosts12000
expect loanAmount320000
expect monthlyPayment2022.62
expect comparison[0].homeValue412000
expect comparison[0].mortgageBalance316423.28
expect comparison[0].buyNet70856.72
expect comparison[0].rentNet106220.44
expect comparison[19].buyNet500968.92
expect comparison[19].rentNet470936.57
expect totalBuyCost593268.83
expect totalRentCost713377.88
expect breakevenYear16
expect netAdvantage30032.35
expect favoursBuyingtrue

Case 2 — the same move over only 8 years — renting and investing the difference wins, and there is no breakeven

given years8
expect comparison[7].buyNet192604.18
expect comparison[7].rentNet218145.84
expect breakevenYear0
expect netAdvantage-25541.66
expect favoursBuyingfalse

Case 3 — no appreciation, no investment return, no rent rises — the pure cash comparison you can check by hand

given appreciationPct0
given investmentReturnPct0
given rentInflationPct0
given years5
expect comparison[0].homeValue400000
expect comparison[0].buyNet59576.72
expect comparison[0].rentNet99271.44
expect comparison[4].homeValue400000
expect totalRentCost133000
expect totalBuyCost148912.33
expect breakevenYear0
expect netAdvantage-51912.33

Sources

Check it yourself — and reuse it

The whole model as JSON — inputs, outputs, formulas, vectors and sources in one re-runnable document.

This model is published under CC BY 4.0: use it, adapt it, build on it, with attribution. The licence covers the model — the expression of each rule and the arrangement of the catalog. The rates and thresholds are law, and nobody licenses those; the sources above are where they come from.

← Back to the Decision tools rent vs buy calculator

Built with Valem — a live, verifiable calculator you can fork and embed.