By Michael Nocito , data analyst · Published August 11, 2026 By the end of this page you can take a project that costs money now and pays money later, and produce one number that says whether it is worth doing. You will know the discount factor and where it comes from, the SQL that computes NPV in one line, the three ways that line silently returns the wrong answer, and how to present a result whose whole meaning depends on an assumption you chose. The fastest way in is to do it once on something real. Take any project with an upfront cost and a few years of expected returns. Divide each future year's cash by 1.10 raised to that year's number, add them all up, then subtract the upfront cost. If what is left is positive, the project beats a 10% required return. The whole idea in one line: a dollar arriving in five years is not a dollar. NPV shrinks every future amount down to what it is worth today, then adds the whole stream up, including the negative one at the start. The shrinking is the idea the rest of the page rests on, so it gets the picture. The original carries a diagram here. In words: Five vertical bars rising from a common baseline, one for each of years one to five. The full height of each bar is the cash the project expects to collect in that year, and the bars grow steadily taller from left to right, from one hundred and fifty thousand in year one to one hundred and ninety thousand in year five. Each bar is split into two parts. The solid accent-colored part at the bottom is what that cash is worth today after discounting at ten percent, and the pale hatched cap sitting on top of it is the part the waiting takes away. The solid parts shrink steadily from left to right, from about one hundred and thirty six thousand down to about one hundred and eighteen thousand, while the pale caps grow from a thin sliver in year one to a thick block in year five. A dashed line joins the tops of the five solid parts and slopes downward across the chart, against bars that are getting taller. That opposition is the whole point: a payment can be bigger and still be worth less, because each extra year of waiting divides it by another factor of one point one. A small legend at the top right pairs a pale swatch with the word waiting and a solid swatch with the word today. Every number on this page is verified, and you can check them. The worked project is six rows, shown in full below. Every discount factor, present value, NPV, rate table and error figure was computed in SQLite and cross-checked in pandas before it went on the page, so you can check any row on a calculator and it will agree. 1. Why a future dollar is worth less, in arithmetic rather than words You are offered 100 in a year. Beyond simple impatience, name one concrete reason today is worth more. Because 110. So 100 next year are not the same offer, and the size of the difference is whatever return you could have got. Run that backwards and you have the whole method. If 110 in a year, then 100 today. Growing forwards multiplies by 1.10. Coming backwards divides by 1.10. The dividing is called discounting, and the 10% is called the discount rate. Do it twice for two years. 110, then to 121 in two years is worth 500,000 today and pays back 500,000 to build, then five years of extra cash from the capacity it adds. The required return is 10%. Year Cash flow Factor at 10% Present value 0 -500,000 1.0000 -500,000 1 150,000 1.1000 136,364 2 160,000 1.2100 132,231 3 170,000 1.3310 127,724 4 180,000 1.4641 122,942 5 190,000 1.61051 117,975 NPV 350,000 137,236 Check one row by hand to trust the rest. Year 3: 170,000 divided by 1.331 is 127,723.5, which rounds to 127,724. Add the present-value column and you get 137,236. Look at the two totals on the bottom row, because they are the whole lesson. Undiscounted, the project nets 137,236. The 137,236 more than the 10% return we could get elsewhere on the same 350,000, and 350,000 is a believable answer for this project. The only way to catch it is to know it can happen. The working query, with the rate written as a decimal. SELECT ROUND(SUM(cash_flow / POWER(1 + 0.10, year)), 0) AS npv FROM project_cash_flows WHERE project = 'Line Expansion'; -- 137236 And the same idea one row at a time, which is what you actually want on screen, because a single NPV number is impossible to check. SELECT year, cash_flow, ROUND(POWER(1 + 0.10, year), 4) AS discount_factor, ROUND(cash_flow / POWER(1 + 0.10, year), 0) AS present_value FROM project_cash_flows WHERE project = 'Line Expansion' ORDER BY year; Three notes on the arithmetic, all worth checking once on your own database. What you write Returns Why 10/100 0 Whole divided by whole. The rate vanishes. 10/100.0 0.1 One decimal makes the whole expression decimal. POWER(1 + 10/100, 5) 1.0 Every factor is 1. No discounting happened. POWER(1 + 0.10, 5) 1.61051 Correct. POWER itself is safe, because it returns a decimal even when handed whole numbers, so cash_flow / POWER(...) stays decimal. The danger is entirely in how the rate reaches it. Write rates as 0.10 . If the rate has to come from a column, make that column a decimal type when the table is created, and check one value before you trust the output. The same trap in its percentage form is worked in gross vs operating vs net margin . One portability note. POWER exists in SQLite, PostgreSQL, SQL Server, MySQL, Oracle, Snowflake and BigQuery, though older SQLite builds may not have it compiled in. Where it is missing, EXP(year * LN(1 + 0.10)) is the same calculation and works anywhere that has logs. 5. Two off-by-one errors, and which one flips the decision Your cash-flow table was built with a row number instead of a year number, so the first inflow is labelled 0 instead of 1. Does NPV come out too high or too low? Too high, and by a lot. Every inflow is being discounted one year less than it should be, so each one is worth more today than it really is, while the cost at year 0 is untouched. On this project the correct answer is 200,960, an overstatement of 660,000 upfront cost instead of 137,236 to 137,236 divided by 1.10. Because everything moved together, the sign cannot change, so the accept-or-reject decision is always safe even though the value is 9.1% off. Only the partial shift is dangerous, and the partial shift is the one that happens. The third mistake in this family has nothing to do with years. The upfront cost simply is not in the table, because it lives in a different system, or because someone filtered on year > 0 to drop what looked like an empty row. SELECT ROUND(SUM(cash_flow / POWER(1 + 0.10, year)), 0) FROM project_cash_flows WHERE project = 'Line Expansion' AND year > 0; -- 637236 137,236. Overstated by exactly the 500,000 cost. Guess what rate would make this project not worth doing. Just under 20%. Here is the same project at seven different required returns, with nothing else changed. Discount rate NPV Decision 0% 350,000 Accept 5% 231,791 Accept 10% 137,236 Accept 15% 60,575 Accept 20% -2,347 Reject 25% -54,573 Reject 30% -98,367 Reject One project, one set of cash flows, and the verdict flips between 15% and 20%. Every row is arithmetically perfect. The rate is not something the data tells you; it is something you choose and then have to defend. An NPV presented without the rate beside it is not a result, it is half of one. Where the rate comes from, in practice. It is the return the company requires on money it puts at risk, usually its weighted average cost of capital, sometimes a hurdle rate set by the finance team, sometimes the return available on a comparable investment. Riskier projects get a higher rate, which is how risk enters the arithmetic: a higher rate punishes distant cash flows hardest, so speculative projects with far-off payoffs have to be much bigger to clear. The rate that makes NPV exactly zero has a name: the internal rate of return, or IRR. On this project it is 19.80%. Read it as the project's own return, the break-even required rate. Above it the project is worth doing; below it, not. IRR is not a SQL calculation. There is no closed-form solution, so it is found by trying rates until NPV lands on zero, which needs iteration that plain SQL does not do. That is why the practical division of labour is: compute NPV in SQL at the rate your company uses, and hand IRR to a spreadsheet or a Python script. If you want an approximate IRR without leaving SQL, run the NPV query at a handful of rates as above and read off where the sign changes. Between 15% and 20% here, and 19.80% when solved properly. Two more numbers worth having, both from the same project. Payback period, the point where the undiscounted cash adds back up to the cost, is 3.11 years. Discounted payback, the same question in today's dollars, is 3.84 years. Payback is easy to explain and it ignores everything that happens after it lands, which is why it is a useful second number and a poor first one. 7. The full before and after Same project, two ways of putting it in front of a decision maker. Before "The line expansion costs 850,000 over five years, a 70% return. Recommend approval." Nothing there is false. The reader has no way to know that the 137,236 has six digits of apparent precision built on estimates that could be 20% out either way. Presenting NPV to the dollar invites a confidence nobody has earned. Round to the nearest thousand in the summary, keep the full number in the working, and show at least one alternative rate so the range is visible. Years are not always the right period. If cash arrives monthly, discount monthly, and the rate has to be converted first. A 10% annual rate is not 10 divided by 12 per month; it is 1.10 raised to the power of one twelfth, minus 1, which is about 0.797% a month. Mixing an annual rate with monthly periods is a large error dressed as a small one. Sign conventions have to be consistent. Every model needs one rule: costs negative, inflows positive. A cost stored as a positive number in a column the query then adds up will not fail, it will just tell you the project is wonderful. Why this works Discounting is not a modern convenience. Fisher set out the theory that the value of an asset is its future income discounted to the present, and that the rate connecting them is the market rate of interest (Fisher, 1930, The Theory of Interest , Macmillan). Hirshleifer's later paper is the cleaner statement of why NPV rather than IRR should decide, showing that the present-value rule follows directly from optimal investment choice while rate-of-return rules can rank projects wrongly (Hirshleifer, 1958, Journal of Political Economy , 66(4), 329–352). That is the formal version of section six's point: IRR is a useful second number and a poor rule. The stronger practical reason to keep the year-by-year table on screen, rather than a single NPV, is that people are bad at future money in a specific and measurable way. Thaler found the implied discount rate people apply falls sharply as the delay lengthens, so a single constant rate does not describe how anyone actually feels about it (Thaler, 1981, Economics Letters , 8(3), 201–207). Frederick, Loewenstein and O'Donoghue's review of the field found implied annual rates in the published literature ranging from below zero to many thousand percent, depending only on how the question was asked (Frederick, Loewenstein, & O'Donoghue, 2002, Journal of Economic Literature , 40(2), 351–401). A reader shown one NPV has to trust your rate. A reader shown the schedule and three rates can find their own. The question at the top of each section is deliberate. Attempting an answer before receiving one improves learning of that specific material across sixty-four studies (Bisra, Liu, Nesbit, Salimi, & Winne, 2018, Educational Psychology Review , 30(3), 703–725). Guessing what 10/100 returns, before section four told you, is why that one will still be with you the next time you write a rate into a query. Using this on your own numbers Rebuilding every capital model in the company is not a job anyone will thank you for. Do this instead, in order. Show the schedule, not just the NPV. Year, cash flow, factor, present value. Four columns, and it makes every other check on this list possible. Verify the year-0 row. It should be negative, and its present value should equal its cash flow exactly. If it does not, your factors or your labels are wrong. Check the rate reached the query as a decimal. Run SELECT POWER(1 + your_rate, 5) on its own. If it returns 1, the rate vanished, and section four has the fix. Put the rate in the output. A column or a header, not a comment in the code. An NPV without its rate cannot be checked by the person reading it. Run it at two more rates, one above and one below. This is one more query and it converts a number into a range, which is what the estimate always was. Find where the sign changes and report that as the approximate IRR. If the decision is clos

Net Present Value (NPV): How to Discount Cash Flows and Read the Answer
Michael Nocito
