Try it live: plug your own numbers into the free 401(k) calculator on FreeKalc — no sign-up, no backend, just the maths in your browser.
Every 401(k) projection you've ever seen — in a finance app, a retirement planner, or a blog post — is built from two ideas: compound growth on what you already have, and the future value of a stream of regular contributions. That's it. Two formulas, a handful of inputs, and suddenly you can answer the question every developer eventually asks: if I keep contributing this much, what will I actually have at 65?
Let's build the whole thing in JavaScript, with the edge cases that most online calculators quietly skip.
The two formulas that run your retirement
Growth on your current balance is plain compound interest:
FV = P × (1 + r)^n Growth on your contributions is the future value of a series — each monthly contribution compounds for a different length of time:
FV = C × (((1 + r)^n − 1) / r) Where P is your current balance, C is the monthly contribution, r is the monthly return, and n is the number of months. Add them together and you have a projection. The catch is that real life keeps moving the inputs: your salary rises, your employer matches part of what you put in, and the IRS caps what you're allowed to contribute.
Building the projector
function project401k({ currentBalance = 0, annualSalary, contributionPercent, // e.g. 10 for 10% of salary employerMatchPercent = 0, // e.g. 50 for "50% of your contribution" employerMatchCap = 6, // employer matches up to 6% of salary annualReturn = 7, // expected average annual return, % annualRaise = 3, // expected salary growth, % yearsUntilRetirement, }) { const months = yearsUntilRetirement * 12; const monthlyReturn = annualReturn / 100 / 12; let balance = currentBalance; let salary = annualSalary; let totalContributed = 0; let totalEmployer = 0; for (let month = 1; month <= months; month++) { // Salary rises once a year if (month > 1 && (month - 1) % 12 === 0) { salary *= 1 + annualRaise / 100; } const myMonthly = (salary * (contributionPercent / 100)) / 12; const matchable = Math.min(contributionPercent, employerMatchCap); const employerMonthly = (salary * (matchable / 100) * (employerMatchPercent / 100)) / 12; balance = balance * (1 + monthlyReturn) + myMonthly + employerMonthly; totalContributed += myMonthly; totalEmployer += employerMonthly; } return { finalBalance: Math.round(balance), youContributed: Math.round(totalContributed), employerContributed: Math.round(totalEmployer), growth: Math.round(balance - totalContributed - totalEmployer - currentBalance), }; } // 30 years old, $80,000 salary, 10% contribution, 50% match up to 6%, retiring at 65 console.log(project401k({ currentBalance: 15000, annualSalary: 80000, contributionPercent: 10, employerMatchPercent: 50, employerMatchCap: 6, yearsUntilRetirement: 35, })); // { finalBalance: 1422978, youContributed: 399842, employerContributed: 119953, growth: 887183 } Read that output carefully: you put in about $400k, your employer adds about $120k, and growth contributes roughly $887k — more than double everything you paid in. That's the whole argument for starting early, expressed as arithmetic. Time in the market does the heavy lifting; contributions just feed it.
Why monthly compounding matters
A tempting shortcut is to compound annually and add one lump contribution per year. It looks cleaner, but it understates the result: real 401(k) contributions land every payday, so each one starts compounding weeks or months earlier than a year-end lump sum would. Over 35 years, monthly vs annual compounding on the same inputs diverges by tens of thousands of dollars. If your projection is meant to be honest, loop month by month. Computers are fast; there's no reason to approximate.
The employer match: free money with a cap
The match is the highest guaranteed return in personal finance — a 50% instant return on every matched dollar. But the details matter in code:
- The cap applies to your contribution rate, not your salary. A "50% match up to 6%" means the employer matches half of the first 6% you contribute. Contributing 10% doesn't get you a match on all 10%.
- The match is usually capped by salary percentage, not dollars. That's why
matchable = Math.min(contributionPercent, employerMatchCap)is in the loop. - Vesting schedules delay ownership. Many employers vest their match over 3–6 years. If you leave early, you keep your contributions but forfeit some or all of the match. A projection for someone planning a job change should discount the employer column accordingly.
The 2026 IRS limits (the guard rails)
Your code should never let a projection assume contributions the law doesn't allow. For 2026, the IRS limits (Notice 2025-67) are:
| 2026 limit | |
|---|---|
| Employee elective deferral (under 50) | $24,500 |
| Catch-up contribution (age 50+) | +$8,000 ($32,500 total) |
| Super catch-up (ages 60–63) | +$11,250 ($35,750 total) |
| Total additions (employee + employer) | $72,000 |
Add a cap check to the projector so a high earner's 15% contribution doesn't silently project past the legal limit:
function cappedMonthlyContribution(salary, contributionPercent, age) { const annualLimit = age >= 60 && age <= 63 ? 35750 : age >= 50 ? 32500 : 24500; const desired = (salary * contributionPercent) / 100; return Math.min(desired, annualLimit) / 12; } Note the limit applies to your deferrals across all plans, not per employer — changing jobs mid-year doesn't reset it.
Edge cases most calculators skip
- Fees eat returns silently. A 1% annual expense ratio on a fund doesn't sound like much, but it compounds against you exactly like returns compound for you. Subtract the fee from the expected return before projecting: a 7% return with a 1% fee is a 6% projection, and over 35 years that's a six-figure difference.
- Inflation turns the headline number honest. $1.4 million in 35 years isn't $1.4 million of today's spending power. At 3% inflation it's worth roughly $500k in today's dollars. Show both numbers, or users will plan around a mirage.
- Returns aren't smooth. The loop above uses a flat 7% every month. Real markets fall 20% some years and rise 30% in others. A single average hides sequence-of-returns risk: two people with the same average return can retire with very different balances depending on when the bad years hit. If you want a serious tool, run a Monte Carlo simulation — same loop, randomised monthly returns, a thousand runs — and show the range, not a point.
- Roth vs traditional changes the tax, not the maths. The projection is identical either way; what differs is whether you pay tax now (Roth) or at withdrawal (traditional). Don't bake a tax rate into the balance — show the pre-tax figure and let the user apply their expected retirement bracket.
The takeaway
A 401(k) projection is two formulas, a monthly loop, and a handful of real-world constraints: the employer match cap, the IRS limits, fees, and inflation. Get those right and the model is honest; skip them and it becomes a marketing number. The uncomfortable truth the maths reveals is that the contribution rate matters less than the start date — the developer who starts at 25 with 6% ends up ahead of the one who starts at 35 with 12%, because compounding rewards time more than effort.
Run your own numbers — including the match, the fees, and the inflation adjustment — with the free 401(k) calculator on FreeKalc. The earlier the projection starts, the better it looks, and that's not a bug in the calculator. It's the point.
