Compound Interest Calculator

Model how savings or investments grow with compounding, recurring contributions, and inflation adjustments. Compare nominal vs. effective returns and plan for long-term goals.

Growth assumptions

$
%
$
%

Used to convert results into today’s dollars.

%

Assumes tax is applied annually to interest earned.

How to Use This Calculator

Enter your starting balance, choose the compounding frequency, and add the rate that reflects your expected growth. Include a recurring contribution and select whether the deposit happens at the beginning or end of each period.

The calculator reports the final balance, total contributions, and interest earned. Use the Calculate button to refresh the numbers after editing the inputs or the Reset button to restore the sample defaults.

Methodology

Results follow standard compound interest formulas. The timeline simulation steps through fractional periods, applies contributions before or after interest as selected, and applies optional tax and inflation adjustments so you can compare nominal vs. real outcomes. Figures are estimates; consult a licensed advisor for critical decisions.

How this compound interest calculator works

The calculator computes two pieces: the growth of the initial lump sum and the growth of each recurring contribution (ordinary annuity or annuity due depending on timing). Contributions are scaled to the selected payment frequency so monthly deposits can be combined with weekly compounding.

For discrete compounding, it divides the horizon into short uniform steps, compounds interest within each step, and optionally taxes annual interest. When you choose continuous compounding, it uses \( e^{r t} \) for the principal growth while still adding contributions at the selected cadence.

The effective annual rate (EAR) is reported alongside your nominal APR so you can see the true annualized gain after compounding. Entering an inflation rate lets the calculator show what the ending balance and earned interest are worth in today’s dollars.

Tips for using the compound interest calculator

  • Experiment with time: Extending the horizon from 20 to 30 years often has a bigger impact than increasing your rate by 1–2%.
  • Increase contributions gradually: Try adding a small annual increase to your savings plan even though the calculator assumes fixed amounts.
  • Compare compounding frequencies: Switch between annual, monthly, and daily compounding to see how much difference it makes for your scenario.
  • Account for taxes: Use the tax input to approximate the drag from annual tax on interest for taxable accounts.

Frequently asked questions

Can compound interest be negative?

Yes. If the average return is negative (such as a -3% annual return), the compounding math still applies and your balance shrinks over time. Use this calculator to model those scenarios by entering a negative rate.

How accurate is this calculator?

The math is exact for the assumptions you enter: constant rate, fixed contributions, and the selected compounding and tax schedule. Real markets fluctuate and taxes can be more complex, so treat the results as a planning guide rather than a guarantee.

Full original guide (expanded)

Growth over time

Balance Contributions

The chart above tracks the portfolio balance and cumulative contributions over time so you can see acceleration from compounding.

Year-by-year breakdown

Open the yearly breakdown panel above to review start balance, contributions, interest, and end balance for each year. Use the “Show every” dropdown while the panel is visible to skip rows.

Inputs used by this calculator

  • Initial amount $
  • Time horizon (years)
  • Annual interest rate %
  • Compounding frequency (Annually, Semi-annually, Quarterly, Monthly, Bi-weekly, Weekly, Daily, Continuous)
  • Regular contribution $
  • Contribution frequency (Annually, Monthly, Bi-weekly, Weekly, Daily)
  • Inflation rate (optional) % — used to show results in today’s dollars.
  • Tax on interest (optional) % — assumes tax is applied annually on interest earned.
  • Contribution timing (end of period or beginning)

Consistency checks

Checks: non-negative values, plausible ranges, coherent outputs.

Operational notes

Fill in realistic values and keep units and timeframes consistent.

About the author

Ugo Candido builds financial tools and educational resources to help readers make better money decisions. He focuses on practical, transparent models that reflect how lenders calculate payments and total cost of ownership.

Contact: info@calcdomain.com

Editorial policy

CalcDomain content is created for educational purposes and is reviewed for clarity, accuracy, and transparency. We do not accept paid placements that influence calculator outputs. Inputs and assumptions are shown directly in the interface so you can verify how results are produced.

Formulas

Lump-sum growth with discrete compounding:

\( FV = P \left(1 + \frac{r}{m}\right)^{m \cdot t} \)

Adding fixed contributions:

\( FV = P \left(1 + \frac{r}{m}\right)^{m t} + PMT \cdot \frac{\left(1 + \frac{r}{k}\right)^{k t} - 1}{\frac{r}{k}} \)

Continuous compounding (theoretical limit):

\( FV = P \cdot e^{r t} \)

Effective annual rate:

\( EAR = \left(1 + \frac{r}{m}\right)^m - 1 \)

Inflation-adjusted real return:

\( 1 + r_{\text{real}} = \frac{1 + r_{\text{nominal}}}{1 + i} \)
Citations
Changelog
  • v2.1.0 — Refreshed to the canonical hero/layout, added chart + yearly schedule controls, and introduced structured parse/validate/compute flow.
  • v1.0.0 — Initial compound interest calculator content and methodology.
Verified by Ugo Candido Last Updated: 2026-02-08 Version 2.1.0
Version 1.5.0
; return symbol + round2(safe).toFixed(2); } }; const formatPercent = (value, digits = 2) => { if (!Number.isFinite(value)) return '0%'; return `${(value * 100).toFixed(digits)}%`; }; const debounce = (fn, delay = 100) => { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), delay); }; }; const els = { initialAmount: document.getElementById('initialAmount'), years: document.getElementById('years'), annualRate: document.getElementById('annualRate'), compoundingFrequency: document.getElementById('compoundingFrequency'), contributionAmount: document.getElementById('contributionAmount'), contributionFrequency: document.getElementById('contributionFrequency'), inflationRate: document.getElementById('inflationRate'), taxRate: document.getElementById('taxRate'), contributionTiming: document.getElementById('contributionTiming'), currency: document.getElementById('currency'), finalBalance: document.getElementById('finalBalance'), finalReal: document.getElementById('finalReal'), totalContributions: document.getElementById('totalContributions'), totalInterestEarned: document.getElementById('totalInterestEarned'), interestReal: document.getElementById('interestReal'), resultEar: document.getElementById('resultEar'), resultNominal: document.getElementById('resultNominal'), errorBox: document.getElementById('errorBox'), scheduleWrap: document.getElementById('scheduleWrap'), scheduleBody: document.getElementById('scheduleBody'), toggleSchedule: document.getElementById('toggleSchedule'), downloadCsv: document.getElementById('downloadCsv'), calcBtn: document.getElementById('calcBtn'), resetBtn: document.getElementById('resetBtn'), tableInterval: document.getElementById('tableInterval') }; const chartCanvas = document.getElementById('growthChart'); const chartCtx = chartCanvas ? chartCanvas.getContext('2d') : null; let growthChart; let currentYearlyRows = []; const allowedCompounding = new Set([0, 1, 2, 4, 12, 26, 52, 365]); const allowedContributionFreq = new Set([1, 12, 26, 52, 365]); function parseInputs() { return { principal: parseFloat(els.initialAmount.value), years: parseFloat(els.years.value), annualRatePct: parseFloat(els.annualRate.value), compFreq: parseInt(els.compoundingFrequency.value, 10), contribution: parseFloat(els.contributionAmount.value), contributionFreq: parseInt(els.contributionFrequency.value, 10), inflationPct: parseFloat(els.inflationRate.value), taxPct: parseFloat(els.taxRate.value), timing: els.contributionTiming.value, currency: els.currency.value }; } function validate(inputs) { const errors = []; if (!Number.isFinite(inputs.principal) || inputs.principal <= 0) { errors.push('Initial amount must be greater than 0.'); } if (!Number.isFinite(inputs.years) || inputs.years <= 0) { errors.push('Time horizon must be greater than 0.'); } if (!Number.isFinite(inputs.annualRatePct)) { errors.push('Annual interest rate must be a number.'); } if (!allowedCompounding.has(inputs.compFreq)) { errors.push('Compounding frequency is invalid.'); } if (!allowedContributionFreq.has(inputs.contributionFreq)) { errors.push('Contribution frequency is invalid.'); } if (!Number.isFinite(inputs.contribution) || inputs.contribution < 0) { errors.push('Contribution must be 0 or greater.'); } if (!Number.isFinite(inputs.taxPct) || inputs.taxPct < 0) { errors.push('Tax rate must be 0 or greater.'); } if (!Number.isFinite(inputs.inflationPct)) { errors.push('Inflation rate must be a number.'); } if (inputs.timing !== 'begin' && inputs.timing !== 'end') { errors.push('Contribution timing is invalid.'); } return { ok: errors.length === 0, errors }; } function computeSchedule(inputs) { const { principal, years, annualRatePct, compFreq, contribution, contributionFreq, taxPct, timing } = inputs; const rNominal = annualRatePct / 100; const totalYears = years; const totalPeriods = Math.round(totalYears * 12); const dt = totalYears / (totalPeriods || 1); let balance = principal; let totalContrib = 0; let totalInterest = 0; const yearlyRows = []; const chartLabels = []; const chartBalance = []; const chartContrib = []; const rPerStep = compFreq === 0 ? null : (rNominal / compFreq) * (compFreq * dt); const rContPerStep = rNominal * dt; let yearStartBalance = principal; let yearContrib = 0; let yearInterest = 0; let currentYear = 0; for (let i = 0; i <= totalPeriods; i++) { const t = i * dt; const yearIndex = Math.floor(t + 1e-9); if (i > 0) { if (timing === 'begin') { const contribThisStep = contribution * (contributionFreq * dt); balance += contribThisStep; totalContrib += contribThisStep; yearContrib += contribThisStep; } let interestThisStep; if (compFreq === 0) { interestThisStep = balance * (Math.exp(rContPerStep) - 1); } else { interestThisStep = balance * rPerStep; } if (taxPct > 0 && Math.abs(t - Math.round(t)) < dt / 2 && t > 0) { const tax = yearInterest * (taxPct / 100); balance -= tax; yearInterest -= tax; totalInterest -= tax; } balance += interestThisStep; totalInterest += interestThisStep; yearInterest += interestThisStep; if (timing === 'end') { const contribThisStep = contribution * (contributionFreq * dt); balance += contribThisStep; totalContrib += contribThisStep; yearContrib += contribThisStep; } } chartLabels.push(t.toFixed(1)); chartBalance.push(balance); chartContrib.push(principal + totalContrib); if ((yearIndex > currentYear && yearIndex <= totalYears) || i === totalPeriods) { yearlyRows.push({ year: currentYear + 1, startBalance: yearStartBalance, contributions: yearContrib, interest: yearInterest, endBalance: balance }); currentYear = yearIndex; yearStartBalance = balance; yearContrib = 0; yearInterest = 0; } } return { finalBalance: balance, totalContrib, totalInterest, yearlyRows, chartLabels, chartBalance, chartContrib }; } function compute(inputs) { const schedule = computeSchedule(inputs); if (!schedule) return null; const totalInvested = schedule.totalContrib + inputs.principal; const interestEarned = schedule.finalBalance - totalInvested; const inflationFactor = inputs.inflationPct !== 0 ? Math.pow(1 + inputs.inflationPct / 100, inputs.years) : null; const finalReal = inflationFactor ? schedule.finalBalance / inflationFactor : null; const interestReal = inflationFactor ? interestEarned / inflationFactor : null; let ear; if (inputs.compFreq === 0) { ear = Math.exp(inputs.annualRatePct / 100) - 1; } else { ear = Math.pow(1 + (inputs.annualRatePct / 100) / inputs.compFreq, inputs.compFreq) - 1; } return { finalBalance: schedule.finalBalance, contributions: totalInvested, totalInterest: interestEarned, ear, nominalRate: inputs.annualRatePct / 100, finalReal, interestReal, chartData: { labels: schedule.chartLabels, balance: schedule.chartBalance, contributions: schedule.chartContrib }, yearlyRows: schedule.yearlyRows }; } function formatResults(data, inputs) { return { finalBalance: formatCurrency(data.finalBalance, inputs.currency), totalContributions: formatCurrency(data.contributions, inputs.currency), totalInterest: formatCurrency(data.totalInterest, inputs.currency), resultEar: formatPercent(data.ear), resultNominal: `${(data.nominalRate * 100).toFixed(2)}%`, finalReal: data.finalReal != null ? `≈ ${formatCurrency(data.finalReal, inputs.currency)} in today's dollars` : '', interestReal: data.interestReal != null ? `≈ ${formatCurrency(data.interestReal, inputs.currency)} real interest` : '' }; } function parseInterval() { const val = parseInt(els.tableInterval.value, 10); return Number.isFinite(val) && val > 0 ? val : 1; } function renderTable(rows, interval, currency) { els.scheduleBody.innerHTML = ''; const fragment = document.createDocumentFragment(); const lastYear = rows[rows.length - 1]?.year; for (const row of rows) { if (row.year % interval !== 0 && row.year !== lastYear) continue; const tr = document.createElement('tr'); tr.innerHTML = ` ${row.year} ${formatCurrency(row.startBalance, currency)} ${formatCurrency(row.contributions, currency)} ${formatCurrency(row.interest, currency)} ${formatCurrency(row.endBalance, currency)} `; fragment.appendChild(tr); } els.scheduleBody.appendChild(fragment); } function renderChart(chartData, currency) { if (!chartCtx) return; if (growthChart) growthChart.destroy(); growthChart = new Chart(chartCtx, { type: 'line', data: { labels: chartData.labels, datasets: [ { label: 'Balance', data: chartData.balance, borderColor: '#2563eb', backgroundColor: 'rgba(37, 99, 235, 0.08)', tension: 0.15, fill: true, pointRadius: 0 }, { label: 'Contributions', data: chartData.contributions, borderColor: '#16a34a', backgroundColor: 'rgba(22, 163, 74, 0.05)', tension: 0.15, fill: false, borderDash: [4, 4], pointRadius: 0 } ] }, options: { responsive: true, maintainAspectRatio: false, interaction: { mode: 'index', intersect: false }, plugins: { legend: { display: false }, tooltip: { callbacks: { label: (context) => { const label = context.dataset.label || ''; return `${label}: ${formatCurrency(context.parsed.y, currency)}`; } } } }, scales: { x: { title: { display: true, text: 'Years' }, ticks: { callback: function(value) { const label = Number(this.getLabelForValue(value)); return Number.isInteger(label) ? label : ''; } } }, y: { title: { display: true, text: `Balance (${currency})` }, ticks: { callback: function(value) { return value >= 1000 ? `${value / 1000}k` : value; } } } } } }); } function render(formatted, data, inputs, errors) { if (errors && errors.length) { els.errorBox.style.display = 'block'; els.errorBox.textContent = errors.join(' '); els.downloadCsv.disabled = true; return; } els.errorBox.style.display = 'none'; els.errorBox.textContent = ''; els.finalBalance.textContent = formatted.finalBalance; els.totalContributions.textContent = formatted.totalContributions; els.totalInterestEarned.textContent = formatted.totalInterest; els.resultEar.textContent = formatted.resultEar; els.resultNominal.textContent = formatted.resultNominal; if (formatted.finalReal) { els.finalReal.textContent = formatted.finalReal; els.finalReal.classList.remove('hidden'); } else { els.finalReal.classList.add('hidden'); } if (formatted.interestReal) { els.interestReal.textContent = formatted.interestReal; els.interestReal.classList.remove('hidden'); } else { els.interestReal.classList.add('hidden'); } currentYearlyRows = data.yearlyRows; renderChart(data.chartData, inputs.currency); renderTable(currentYearlyRows, parseInterval(), inputs.currency); els.downloadCsv.disabled = currentYearlyRows.length === 0; } function update() { const inputs = parseInputs(); const validation = validate(inputs); if (!validation.ok) { render(null, null, null, validation.errors); return; } const computed = compute(inputs); if (!computed) { render(null, null, null, ['Calculation failed for the current inputs. Please revise values.']); return; } const formatted = formatResults(computed, inputs); render(formatted, computed, inputs, []); } const interactiveEls = [ els.initialAmount, els.years, els.annualRate, els.compoundingFrequency, els.contributionAmount, els.contributionFrequency, els.inflationRate, els.taxRate, els.contributionTiming, els.currency, els.tableInterval ]; interactiveEls.forEach((el) => { el.addEventListener('change', debouncedUpdate); el.addEventListener('input', debouncedUpdate); }); els.calcBtn.addEventListener('click', update); els.resetBtn.addEventListener('click', () => { els.initialAmount.value = '10000'; els.years.value = '20'; els.annualRate.value = '7'; els.compoundingFrequency.value = '12'; els.contributionAmount.value = '200'; els.contributionFrequency.value = '12'; els.inflationRate.value = '2'; els.taxRate.value = '0'; els.contributionTiming.value = 'end'; els.currency.value = 'USD'; els.tableInterval.value = '1'; els.scheduleWrap.style.display = 'none'; els.toggleSchedule.textContent = 'View yearly breakdown'; currentYearlyRows = []; const debouncedUpdate = debounce(update, 100); document.querySelectorAll('#inputsCard input, #inputsCard select, #inputsCard textarea') .forEach((el) => { el.addEventListener('input', debouncedUpdate); el.addEventListener('change', debouncedUpdate); }); update(); }); els.tableInterval.addEventListener('change', () => { if (currentYearlyRows.length) { renderTable(currentYearlyRows, parseInterval(), els.currency.value); } }); els.toggleSchedule.addEventListener('click', () => { const isHidden = els.scheduleWrap.style.display !== 'block'; els.scheduleWrap.style.display = isHidden ? 'block' : 'none'; els.toggleSchedule.textContent = isHidden ? 'Hide yearly breakdown' : 'View yearly breakdown'; if (isHidden && currentYearlyRows.length) { renderTable(currentYearlyRows, parseInterval(), els.currency.value); } }); els.downloadCsv.addEventListener('click', () => { if (!currentYearlyRows.length) return; let csv = 'Year,Start balance,Contributions,Interest,End balance\\n'; for (const row of currentYearlyRows) { csv += `${row.year},${round2(row.startBalance)},${round2(row.contributions)},${round2(row.interest)},${round2(row.endBalance)}\\n`; } const blob = new Blob([csv], { type: 'text/csv' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'compound-interest-yearly-breakdown.csv'; a.click(); URL.revokeObjectURL(url); }); update(); })();