I'd read about the 4% rule the same way most people who lurk in FIRE forums have: withdraw 4% of your portfolio a year in retirement and, per the Trinity Study, you've got a 95% shot at not running out of money over 30 years. So when I sat down to build a small FIRE (Financial Independence, Retire Early) calculator, I assumed the withdrawal-rate slider would actually drive a 4%-of-portfolio withdrawal simulation once you hit retirement age. It doesn't. Writing the projection loop made it obvious that the withdrawal rate does exactly one job in the math, and it isn't "withdraw this much."
Your FIRE number is just annual expenses divided by a rate
The withdrawal rate's only job is turning a spending number into a net-worth target. Every year of the projection, the tool recomputes what your current (inflation-adjusted) expenses would require as a nest egg:
let currentTarget = currentAnnualExpense / swr;
targetData.push(Math.round(currentTarget));
if (simulatedAssets >= currentTarget && standardYears === 0) standardYears = i;
if (simulatedAssets >= currentTarget * 0.7 && leanYears === 0) leanYears = i;
if (simulatedAssets >= currentTarget * 1.5 && fatYears === 0) fatYears = i;
With the default 4% rate that's expenses / 0.04, i.e. expenses × 25 — the number everyone quotes without necessarily deriving it. Lean and Fat FIRE aren't independently computed; they're just 0.7× and 1.5× of that same standard number, on the theory that a leaner or richer lifestyle scales the same target proportionally rather than needing its own withdrawal-rate assumption.
Coast FIRE is really "can today's balance alone get there without any more contributions"
Coast FIRE gets its own check inside the same loop, and it's a genuinely different question from the other three: not "do I have enough right now," but "if I stopped contributing today, would today's balance alone compound to what I'll need by retirement age?"
let yearsLeftToRetire = inputs.retirementAge - ageThisYear;
let expenseAtRetirement =
initialAnnualExpense * Math.pow(1 + inf, inputs.retirementAge - inputs.age);
let requiredTargetAtRetirement = expenseAtRetirement / swr;
let projectedAssetsAtRetirement =
simulatedAssets * Math.pow(1 + r, yearsLeftToRetire);
if (projectedAssetsAtRetirement >= requiredTargetAtRetirement) {
coastYears = i;
coastAmountAtTargetYear = simulatedAssets;
hasReachedCoast = true;
}
projectedAssetsAtRetirement deliberately ignores any future contributions — it's pure compounding of the current balance at the expected return rate, r. The moment that number clears the inflation-adjusted target, you've "coasted": you can stop saving and let compounding finish the job. A separate, one-off calculation outside the loop (futureTargetAtRetire / Math.pow(1 + r, yearsToRetire)) discounts that same target back to today's dollars, purely so the UI can show an achievement percentage (assets / coastPresentValue) without waiting for the year-by-year loop to catch up.
The withdrawal rate never actually withdraws anything
Here's the part that surprised me. Once retirement hits, the simulation doesn't pull a fixed percentage out of the portfolio each year — it just stops income and lets full living expenses drain out of whatever's there:
let actualIncome = ageThisYear >= inputs.retirementAge ? 0 : currentAnnualIncome;
let currentAnnualSavings = actualIncome - currentAnnualExpense;
if (simulatedAssets > 0) {
simulatedAssets = simulatedAssets * (1 + r) + currentAnnualSavings;
} else {
simulatedAssets = simulatedAssets + currentAnnualSavings;
}
currentAnnualExpense = currentAnnualExpense * (1 + inf);
Growth is applied to the balance you started the year with, and that year's net cash flow — savings while working, a straight expense outflow after retirement — is added on top. That's ordinary-annuity timing (contribution/withdrawal at the end of the period), not annuity-due (contribute first, then grow the whole thing). It's a real, deliberate choice, and it means a dollar you save this year doesn't earn a return until next year.
The if (simulatedAssets > 0) branch is the other detail worth calling out: once a balance goes negative, the code stops compounding it at the market return rate and just adds the cash flow directly. Without that guard, a portfolio that's run dry would compound its deficit at 6-8% a year like it was still invested — which is backwards, since a negative balance in this model represents unmet spending, not a margin loan earning market returns.
So swr never appears in the growth loop at all after the target is set. A more conservative withdrawal-rate setting only makes the target bigger up front; it does nothing to stop the simulated you from spending faster than 4% a year once retired.
Limitations and a bug I found writing this up
-
The zero problem:
standardYears,leanYears, andfatYearsall default to0(meaning "not found yet") and are also set to0if the target is met on day one. The final result is computed asstandardYears || "99+"— and in JS,0is falsy. So if your current assets already clear your FIRE number at year zero, the tool reports "Unreachable" instead of "you can retire today." Coast FIRE dodges this same trap by tracking a separatehasReachedCoastboolean instead of trusting the counter's truthiness — which made it obvious, once I noticed, that the other three needed the same fix. -
Constant return, every year, forever.
ris one fixed number for the entire projection — no volatility, no down years, no sequence-of-returns risk. Retiring right before a crash and retiring right before a bull run look identical in this model, which in reality is one of the biggest risks to an early retirement plan. - No taxes, no Social Security/pension offsets, no healthcare cliff. Expenses inflate at a single flat rate for 60 years straight; nothing models a lump-sum expense (a new roof, a medical event) or income that isn't your salary.
- The projection loop is capped at 60 years. If none of the targets are hit in that window, everything falls back to the same "99+" sentinel as the zero-day bug above, just for the opposite reason.
None of that makes the arithmetic wrong — it's an honest, transparent compound-interest model, which is exactly what I wanted for sanity-checking my own numbers instead of trusting a black-box spreadsheet. I turned the version I built into a small free tool: FIRE Calculator. No sign-up, everything runs in the browser.
Available in other languages
- FIRE財富自由計算機 — 繁體中文
- FIRE财富自由计算器 — 简体中文
- FIRE Calculator — English
- FIRE経済的自由計算機 — 日本語
- FIRE 경제적 자유 계산기 — 한국어
- Calculateur FIRE — Français
- Калькулятор FIRE — Русский
- FIRE-Rechner — Deutsch
- Kalkulator FIRE — Bahasa Indonesia
- Calculadora FIRE — Español
- Máy Tính FIRE — Tiếng Việt
- เครื่องคำนวณ FIRE — ไทย
- Kalkulator FIRE — Polski
- FIRE Hesaplayıcı — Türkçe
- Calcolatore FIRE — Italiano
- Calculadora FIRE — Português
- FIRE Calculator — Nederlands
- Калькулятор FIRE — Українська
Top comments (0)