Methodology · Open Math
FIRE & Coast FIRE
The FIRE number is annual spending divided by a safe withdrawal rate (the 4% rule from the Trinity Study). Coast FIRE discounts that target back to today at an expected return.
Authoritative reference
Our implementation is validated against Trinity Study — Cooley, Hubbard & Walz (1998), sustainable withdrawal rates.
Reference test cases
These exact cases are asserted in our unit test suite, so a regression would fail the build.
| Input | Expected |
|---|---|
| $40,000 annual spending at a 4% withdrawal rate | $1,000,000 FIRE number |
| $1,000,000 target, 7% return, 20 years out | $258,419 Coast FIRE number |
Source code
This is the exact, unmodified source that powers the FIRE Calculator.
FIRE & Coast FIRE engine (fire.ts)
// src/lib/finance/fire.ts
//
// FIRE (Financial Independence, Retire Early) calculations.
// FIRE Number = annual spending / safe withdrawal rate
// Coast FIRE = FIRE Number / (1 + return)^years_to_retirement
//
// Once you hit Coast FIRE you can stop contributing and your portfolio still
// grows to the FIRE number by retirement.
/** Lump-sum portfolio size that supports `annualSpending` at `swr`. */
export function fireNumber(annualSpending: number, swr: number): number {
if (swr <= 0) throw new Error(`fireNumber: swr must be > 0, got ${swr}`);
return annualSpending / swr;
}
/**
* Coast FIRE number: how much you'd need invested today, with no further
* contributions, to grow to {@link target} in {@link yearsToRetirement} years
* at the given return rate.
*/
export function coastFireNumber(target: number, returnRate: number, yearsToRetirement: number): number {
if (yearsToRetirement <= 0) return target;
return target / Math.pow(1 + returnRate, yearsToRetirement);
}
/**
* Iterate year by year and find the first year where balance ≥ target.
* Returns Infinity if the target isn't reached within maxYears (default 100).
*/
export function yearsToReach(
startBalance: number,
annualContribution: number,
returnRate: number,
target: number,
maxYears = 100
): number {
let bal = startBalance;
for (let y = 1; y <= maxYears; y++) {
bal = bal * (1 + returnRate) + annualContribution;
if (bal >= target) return y;
if (!Number.isFinite(bal)) return Infinity;
}
return Infinity;
}