How the Decision tools rent vs buy calculator is verified
Inputs
| Input | Field | Type |
|---|---|---|
| House price | homePrice | number |
| Deposit (%) | depositPct | number |
| Mortgage rate (%) | mortgageRatePct | number |
| Mortgage term (years) | mortgageTermYears | integer |
| Buying costs (% of price) | closingCostsPct | number |
| Selling costs (% of value) | sellingCostsPct | number |
| Property tax (% of value / yr) | propertyTaxPct | number |
| Buildings insurance / yr | insurancePerYear | number |
| Maintenance (% of value / yr) | maintenancePct | number |
| Service charge / month | hoaPerMonth | number |
| House price growth per year (%) | appreciationPct | number |
| Mortgage interest tax relief (%) | taxReliefPct | number |
| Rent / month | monthlyRent | number |
| Rent rises per year (%) | rentInflationPct | number |
| Contents insurance / yr | rentersInsurancePerYear | number |
| Investment return the renter earns (%) | investmentReturnPct | number |
| Years you stay | years | integer |
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 value | Field | Formula (JSONata) |
|---|---|---|
| Deposit | deposit | $round(homePrice * depositPct / 100, 2) |
| Loan Amount | loanAmount | $round(homePrice - deposit, 2) |
| Closing Costs | closingCosts | $round(homePrice * closingCostsPct / 100, 2) |
| Monthly Rate | monthlyRate | mortgageRatePct / 1200 |
| Compound Factor | compoundFactor | $power(1 + monthlyRate, mortgageTermYears * 12) |
| Monthly Payment | monthlyPayment | loanAmount <= 0 ? 0 : (monthlyRate = 0 ? $round(loanAmount / (mortgageTermYears * 12), 2) : $round(loanAmount * monthlyRate * compoundFactor / (compoundFactor - 1), 2)) |
| Net worth on each path | comparison | ( $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 year | breakevenYear | ( $hits := $map(comparison, function($c) { $c.buyNet >= $c.rentNet ? $c.year : 0 })[$ > 0]; $count($hits) = 0 ? 0 : $min($hits) ) |
| Total cost of owning | totalBuyCost | $round($sum($map(comparison, function($c) { $c.buyCost })), 2) |
| Total rent paid | totalRentCost | $round($sum($map(comparison, function($c) { $c.rentCost })), 2) |
| Net worth difference | netAdvantage | ( $d := $map(comparison, function($c) { $round($c.buyNet - $c.rentNet, 2) }); $i := $count($d) - 1; $d[$i] ) |
| Favours Buying | favoursBuying | netAdvantage >= 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 homePrice | 400000 |
|---|---|
given depositPct | 20 |
given mortgageRatePct | 6.5 |
given monthlyRent | 2200 |
given appreciationPct | 3 |
given investmentReturnPct | 7 |
given years | 20 |
expect deposit | 80000 |
expect closingCosts | 12000 |
expect loanAmount | 320000 |
expect monthlyPayment | 2022.62 |
expect comparison[0].homeValue | 412000 |
expect comparison[0].mortgageBalance | 316423.28 |
expect comparison[0].buyNet | 70856.72 |
expect comparison[0].rentNet | 106220.44 |
expect comparison[19].buyNet | 500968.92 |
expect comparison[19].rentNet | 470936.57 |
expect totalBuyCost | 593268.83 |
expect totalRentCost | 713377.88 |
expect breakevenYear | 16 |
expect netAdvantage | 30032.35 |
expect favoursBuying | true |
Case 2 — the same move over only 8 years — renting and investing the difference wins, and there is no breakeven
given years | 8 |
|---|---|
expect comparison[7].buyNet | 192604.18 |
expect comparison[7].rentNet | 218145.84 |
expect breakevenYear | 0 |
expect netAdvantage | -25541.66 |
expect favoursBuying | false |
Case 3 — no appreciation, no investment return, no rent rises — the pure cash comparison you can check by hand
given appreciationPct | 0 |
|---|---|
given investmentReturnPct | 0 |
given rentInflationPct | 0 |
given years | 5 |
expect comparison[0].homeValue | 400000 |
expect comparison[0].buyNet | 59576.72 |
expect comparison[0].rentNet | 99271.44 |
expect comparison[4].homeValue | 400000 |
expect totalRentCost | 133000 |
expect totalBuyCost | 148912.33 |
expect breakevenYear | 0 |
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.