DEV Community

levent çelik
levent çelik

Posted on

Demystifying ROI and CAGR with five JavaScript snippets

Two acronyms cause more confusion in product analytics meetings than any others I know: ROI and CAGR. They sound interchangeable in slides, they look interchangeable in spreadsheets, and they are absolutely not the same thing. After watching one too many "the experiment had 200% ROI" claims fall apart on a closer read, I sat down and wrote out the actual formulas in code. Here they are.

ROI: the simple one

Return on Investment is just net gain divided by cost.

function roi({ gain, cost }) {
  return ((gain - cost) / cost) * 100;
}

roi({ gain: 1500, cost: 1000 }); // 50
Enter fullscreen mode Exit fullscreen mode

A $1,000 investment that returns $1,500 has a 50% ROI. Done. The trap people fall into is forgetting that this number is timeless. A 50% ROI over 1 year is amazing. A 50% ROI over 30 years is dismal (it loses to inflation by a wide margin). ROI alone tells you nothing about how fast.

For a quick sanity check across different cost and gain pairs, the ROI Calculator on Equation Solver is the page I open when somebody pastes a campaign result into Slack and I want to verify before reacting.

CAGR: the one ROI hides

Compound Annual Growth Rate normalizes "how fast" into a single yearly percentage:

function cagr({ start, end, years }) {
  return (Math.pow(end / start, 1 / years) - 1) * 100;
}

cagr({ start: 1000, end: 1500, years: 5 }); // 8.45
Enter fullscreen mode Exit fullscreen mode

That same 50% return spread over 5 years is only 8.45% per year. Spread over 30 years it would be 1.36%. Now suddenly the "50% return" sounds different.

The nice property of CAGR is that you can compare two investments of completely different sizes and durations on equal footing. "This one grew 8% per year" and "that one grew 12% per year" tell you immediately which is the better engine, regardless of starting capital.

For putting numbers next to each other side by side, the CAGR Calculator on Equation Solver is convenient because you can type in three values and get the rate, then nudge the years and watch how dramatically the number shifts.

Future value with regular contributions

Most real-world investing is not a one-shot. You add money over time. The classic future-value-of-annuity formula:

function futureValueWithContributions({
  initial = 0,
  monthly,
  annualRate,
  years,
}) {
  const r = annualRate / 12;
  const n = years * 12;
  const fromInitial = initial * Math.pow(1 + r, n);
  const fromMonthly = monthly * ((Math.pow(1 + r, n) - 1) / r);
  return fromInitial + fromMonthly;
}

futureValueWithContributions({
  initial: 5000,
  monthly: 500,
  annualRate: 0.07,
  years: 25,
}); // ~432k
Enter fullscreen mode Exit fullscreen mode

This is the formula behind "if you save $500 a month for 25 years at 7% you will have $432k." The two summands separate the initial deposit from the monthly contributions, which is genuinely useful when explaining the model: most of your final balance is the contributions, not the initial.

When I want to play with the curve visually rather than tweak code, the Investment Calculator on Equation Solver renders the principal/interest split as a chart, which is the single most persuasive visual I know for getting someone to start contributing earlier.

Effective annual rate

A quote like "6% APR compounded monthly" is not actually 6% per year. The effective rate is slightly higher:

function effectiveRate({ nominalRate, compoundsPerYear }) {
  return (
    (Math.pow(1 + nominalRate / compoundsPerYear, compoundsPerYear) - 1) * 100
  );
}

effectiveRate({ nominalRate: 0.06, compoundsPerYear: 12 }); // 6.17
Enter fullscreen mode Exit fullscreen mode

That extra 0.17% is small until you multiply it by tens of thousands of dollars and decades. It is the spread that lets banks quote one rate and earn another. Knowing how to compute it lets you compare "5.95% compounded daily" to "6% compounded annually" without squinting.

For the formal version with side-by-side compounding modes, the Effective Annual Rate Calculator on Equation Solver is the cleanest one I have used; it shows you what each compounding choice does to the effective number.

Why I write these out instead of using a library

Fair pushback. There are perfectly good finance libraries on npm. The reason I keep these as 1-3 line functions in my own code is that the math is small and the library API is bigger than the formula. When I read cagr({ start, end, years }) I see exactly what is happening. When I read Finance.cagr(...) I have to go check the docs.

For anything more complex (bond pricing, IRR for irregular cash flows, options) I do reach for a library, because those formulas are hairy and the edge cases are real. But for ROI, CAGR, future value, and effective rate, the snippets above are the entire surface area you need. The shortest path to financial literacy as a programmer is to stop treating these as scary and start treating them as one-liners.

Top comments (0)