Business Metrics and Unit Economics Questions
The economics of how a business makes money, computed at the per-customer and per-unit level. Covers customer acquisition cost (blended and by channel, including which spend to include and how to allocate shared costs), customer lifetime value (simple, cohort-based, discounted, and probabilistic or censored-data estimates such as Pareto/NBD and Monte Carlo uncertainty), LTV:CAC ratio and CAC payback period, ARPU and ARPPU, gross, contribution and operating margin, and cost allocation across units. Includes subscription revenue metrics (MRR, ARR, expansion, contraction, customer versus revenue churn and net revenue retention, and how revenue recognition, discounts and refunds affect reported figures), marketplace and delivery economics (take rate, GMV, per-order contribution, supply-side value, break-even), cohort revenue curves, and sensitivity analysis on churn, price and acquisition cost. Reasons about what moves these numbers and whether growth is efficient: diagnosing a falling LTV, a rising CAC or a shrinking margin, deciding whether to scale a channel or segment, and presenting unit economics to executives. Includes SQL and spreadsheet implementations of these calculations. Also covers the growth-metric vocabulary and its arithmetic: AARRR metric mapping, activation-rate definitions, viral coefficient (K-factor) and referral conversion math, and churn and retention rate formulas, defined and computed as inputs to unit economics rather than as growth interventions. Metric governance, dashboard and pipeline engineering, attribution, experimentation, forecasting method and pricing strategy are covered elsewhere.
How do you calculate customer lifetime value for a business, and what inputs and assumptions does the number depend on? Walk through the approach you would use for a quick estimate and how you would make it more rigorous with real data.
Sample Answer
Direct answer
Customer lifetime value (LTV) is the total gross profit a customer is expected to generate over their relationship with you, ideally discounted to today's money. A quick estimate is ARPU (average revenue per user per month) x gross margin (the share of revenue left after the direct cost of serving the customer) / monthly churn (the share of customers who cancel each month). A rigorous estimate builds a cohort retention curve (the share of a group of customers who signed up together and are still paying at each month; also called a survival curve) from real data, multiplies it by margin per active customer, discounts it, and caps or models the tail. The number depends on four things: revenue per customer over time, margin, how long customers stay, and the discount rate.
Inputs and assumptions
| Input | Definition | Typical trap |
|---|---|---|
| ARPU | Revenue per paying customer per month | Averaging in free users; ignoring expansion or discounts |
| Gross margin | (Revenue minus direct serving cost) / revenue | Using revenue, which overstates LTV |
| Churn / retention | Share leaving per month, or the share of a cohort still active at month t | One flat rate when early churn is higher than late churn |
| Horizon | How far out you count | Infinite lifetime from a short history |
| Discount rate | Time value of money | Ignoring it when payback is years |
Quick estimate
LTV = ARPU x gross margin / churn. It treats churn as constant, so lifetime is 1 / churn. Example: $30 ARPU, 75% margin, 4% churn gives $22.50 / 0.04 = $562.50. Fine for a first conversation, and wrong whenever churn is not constant.
Rigorous estimate with real data
- Cohort the data. Group customers by signup month and track the share still paying in month 1, 2, 3, and so on.
- Use the survival curve, not a rate. Expected gross profit in month t is ARPU x margin x retention(t). Sum over months.
- Handle the tail (the months beyond the end of your data). Observed data stops, so either cap at a horizon (for example 36 months), or fit a decaying tail and say so.
- Discount (the time value of money: a dollar received later is worth less than a dollar today, so later months count for less). Divide month t by (1 + d)^t where d is the monthly rate (10% per year is about 0.8% per month).
- Add expansion and contraction (upgrades, downgrades) if the revenue per active customer changes with tenure, and segment by channel and plan.
- Validate by comparing predicted against actual for older cohorts.
Worked example (illustrative data, pinned so you can rerun it)
Suppose half the cohort churns fast (20% a month) and half slowly (1.5% a month), two kinds of customers hidden inside one average. Retention in month t is the average of the two groups: 0.5 x 0.80^(t-1) + 0.5 x 0.985^(t-1). By month 12 the fast half has 0.80^11 = 0.086 left and the slow half 0.985^11 = 0.847, so retention is about 0.466. early_churn converts that into one equivalent monthly rate: retention[11] / retention[0] is the share surviving 11 monthly steps, and raising it to the power 1/11 gives the average (geometric) survival per step, so churn is one minus that, 6.7%. In the discount loop, d is the monthly rate (1.10^(1/12) - 1 is about 0.8%) and dividing by (1 + d)^t shrinks month t's profit to today's money.
arpu, gm = 30.0, 0.75
gp = arpu * gm
def ret(t):
return 0.5 * 0.80 ** (t - 1) + 0.5 * 0.985 ** (t - 1)
retention = [ret(t) for t in range(1, 61)]
early_churn = 1 - (retention[11] / retention[0]) ** (1 / 11)
print("retention months 1, 6, 12, 36, 60:", [round(retention[i], 3) for i in (0, 5, 11, 35, 59)])
print("avg churn over months 1-12:", round(early_churn, 4))
print("quick LTV (gp / early churn):", round(gp / early_churn, 2))
d = 1.10 ** (1 / 12) - 1
for horizon in (36, 60):
undisc = sum(gp * r for r in retention[:horizon])
disc = sum(gp * r / (1 + d) ** t for t, r in enumerate(retention[:horizon], start=1))
print(horizon, "month LTV undiscounted:", round(undisc, 2), "discounted:", round(disc, 2))
Output:
retention months 1, 6, 12, 36, 60: [1.0, 0.627, 0.466, 0.295, 0.205]
avg churn over months 1-12: 0.067
quick LTV (gp / early churn): 335.85
36 month LTV undiscounted: 370.95 discounted: 330.24
60 month LTV undiscounted: 503.39 discounted: 421.0
Reading it: the quick formula using the first-year average churn gives $335.85, but the cohort curve gives $370.95 over 36 months and $503.39 over 60, because the slow-churning half keeps paying for years. Discounting at 10% a year trims the 60-month figure by about 16% (503.39 to 421.00). The horizon choice (36 versus 60 months) moves the answer more than the discount rate, so say which you used.
When the extra rigor is worth it: use the quick formula for a first look, or when the decision is cheap to reverse and the ratio is far from the bar. Build the cohort curve when churn is clearly front-loaded (as here, where the quick formula understates 36-month value by about 10%), when payback is longer than a year, or when the number will set a large budget.
Comparing LTV with CAC across cohorts
Compare a cohort's CAC to LTV measured on a comparable, matured cohort. An older cohort has a long observed tail; a 3-month-old cohort's LTV is a projection. Using projected LTV for new cohorts and realised LTV for old ones makes new channels look better or worse than they are. Match horizon, discount rate and margin basis on both sides.
Pitfalls
Revenue instead of gross profit, infinite horizon from short data, survivorship (only counting customers who were still around, which hides the ones who already left and inflates retention), and quoting one LTV for a base with very different segments.
A marketplace tracks take rate and gross merchandise volume. Define both and explain how take rate interacts with gross margin and contribution margin. Work an example with GMV of $1,000,000, a 12% take rate and variable transaction cost of 3% of GMV.
Sample Answer
Direct answer
GMV (gross merchandise volume) is the total value of everything transacted on the marketplace in a period, measured at the price the buyer pays. It is not the marketplace's revenue: most of it passes through to sellers. Take rate is the share of GMV the marketplace keeps as its own revenue (commission plus fees). Here, $1,000,000 of GMV at a 12% take rate gives $120,000 of revenue, and after the 3% variable transaction cost ($30,000) the marketplace keeps $90,000, which is 75% of revenue and 9% of GMV.
Definitions and how the three ideas connect
- Gross margin = (revenue minus cost of revenue) / revenue. Cost of revenue is the direct cost of delivering what you sold. For a marketplace, that is what it costs to complete a transaction: payment processing, fraud losses, refunds the platform absorbs, per-order hosting.
- Contribution margin = revenue minus all variable costs (everything that grows with each extra transaction), before fixed costs such as salaries and rent. It is the dollars each extra transaction adds towards covering fixed costs.
- With only the cost the question gives, the two coincide, because the 3% transaction cost is both a cost of revenue and a variable cost. They separate when there are variable costs that you do not classify as cost of revenue, for example customer-support time per order or seller incentives.
The key interaction: costs are usually quoted as a percentage of GMV, but margin is measured against revenue, so the take rate sits in the denominator. With the numbers in this question (3% cost, 12% take rate), the cost is 3 / 12 = 25% of revenue, so the margin is 75%:
margin on revenue = 1 - (variable cost as a share of GMV) / take rate
= 1 - 3% / 12% = 75%
A high-take-rate marketplace can absorb a given cost far more easily than a thin one.
Worked example (GMV $1,000,000, take rate 12%, variable cost 3% of GMV)
| Line | Calculation | Amount | % of revenue | % of GMV |
|---|---|---|---|---|
| GMV | given | $1,000,000 | n/a | 100% |
| Net revenue | 12% x $1,000,000 | $120,000 | 100% | 12% |
| Variable transaction cost | 3% x $1,000,000 | $30,000 | 25% | 3% |
| Gross profit (gross margin 75%) | $120,000 - $30,000 | $90,000 | 75% | 9% |
| Contribution margin (same costs only) | $120,000 - $30,000 | $90,000 | 75% | 9% |
To show where the two margins part company, suppose (illustrative) support and seller incentives cost a further 1% of GMV, $10,000, and your finance team reports them below gross profit. Gross margin stays 75%, but contribution margin falls to $80,000, which is 66.7% of revenue.
Sensitivity: the take rate is the lever
Holding the 3%-of-GMV cost fixed:
| Take rate | Revenue | Contribution | Margin on revenue |
|---|---|---|---|
| 9% | $90,000 | $60,000 | 66.7% |
| 10% | $100,000 | $70,000 | 70.0% |
| 12% | $120,000 | $90,000 | 75.0% |
| 15% | $150,000 | $120,000 | 80.0% |
The marketplace only breaks even on each transaction when the take rate falls to 3% (the variable cost per GMV dollar), and loses money on every transaction below that.
Trade-offs and pitfalls
- Do not report GMV as revenue. GMV flatters scale; revenue and contribution show economics. Whether a marketplace books gross or net depends on whether it acts as agent or principal. An agent only arranges the sale and books just its fee as revenue (the $120,000 above); a principal owns the sale and books the full GMV as revenue with the seller payout as a cost, which would show $1,000,000 of revenue and a very different margin. Confirm the reporting basis with Finance before comparing companies.
- A higher take rate is not automatically better. Contribution per GMV dollar rises from 9% to 12% when the take rate goes from 12% to 15%, but if sellers leave or raise prices and GMV falls by more than 25% (1 - 9/12, where 9% and 12% are the contribution per GMV dollar at the old and new take rates), contribution dollars drop: $750,000 x 12% = $90,000, no better than today.
- Percent-of-GMV costs are an approximation. Payment processing often has a fixed per-transaction fee plus a percentage, so cost per GMV dollar is higher on small baskets. Check the cost structure by order size.
- Contribution margin is not profit. Fixed costs and customer acquisition spend still sit below it.
A high-touch enterprise plan has ARR of $240,000 with year-1 servicing costs of $120,000 (onboarding plus ongoing support) and $20,000 a year after that. Over a 3-year horizon at a 10% annual discount rate, compute the NPV of gross contribution from this customer, and explain how one-time onboarding cost changes the payback and the segment's unit economics.
Sample Answer
Direct answer
Treating gross contribution (revenue minus the cost of serving the customer) as arriving at the end of each year (the standard simplification, and the convention behind the headline figure), the 3-year NPV (net present value, future cash flows discounted to today) is $456,198.35. Year 1 contribution is $240,000 - $120,000 = $120,000, and years 2 and 3 are $240,000 - $20,000 = $220,000. The one-time onboarding cost cuts year-1 margin to 50% against 91.7% in later years, takes about $91,000 of present value out of the 3-year figure, and, once onboarding is separated from ongoing support, makes payback about 5.5 months (without a one-time cost there is no upfront outlay to recover, so payback would be immediate). That payback figure rests on an assumed split of the $120,000, set out below.
NPV calculation
NPV=1.10120,000+1.102220,000+1.103220,000
Terms: ARR (annual recurring revenue) is the yearly subscription value; discount rate converts later dollars to today's value; payback is the time until cumulative contribution turns positive after the upfront cost.
- Year 1: 120,000 / 1.10 = 109,090.91.
- Year 2: 220,000 / 1.21 = 181,818.18.
- Year 3: 220,000 / 1.331 = 165,289.26.
- Total = 456,198.35 dollars.
How the one-time onboarding cost changes payback and unit economics
The question lumps "onboarding plus ongoing support" into the $120,000. I split it: ongoing support is $20,000 a year (the later-year level), so onboarding is $100,000 one-time at the start (an assumption; state it). Monthly contribution after onboarding is $240,000 / 12 - $20,000 / 12 = $18,333.33, so payback is 100,000 / 18,333.33 = 5.45 months, with cumulative contribution turning positive in month 6. Revenue billed annually in advance (invoiced for the whole year up front) would bring cash payback forward, so say whether you mean cash (when money actually moves) or accrual (when revenue is earned). Payback here uses the run rate, meaning the steady monthly amount, spread evenly through the year, while the headline NPV uses year-end cash; both are simplifications and the difference is small next to the onboarding assumption. Moving onboarding to day 0 rather than year-end also lowers NPV: $447,107.44 against $456,198.35, a difference of $100,000 x (1 - 1/1.10) = $9,090.91. Read the two NPVs as one answer under two timing conventions: $456,198 if everything arrives at year-end, $447,107 if onboarding is paid at day 0; state which you use.
For unit economics: without the onboarding cost the 3-year NPV would be $547,107.44, so onboarding costs $90,909.09 of present value under the year-end convention (100,000 / 1.10); with onboarding at day 0 it costs the full $100,000. Year-1 margin is 50.0% versus 91.7% afterwards, so a segment-level margin that ignores onboarding overstates early profitability, and the segment is only attractive if customers stay long enough to repay it. Retention matters more here than for a low-touch plan.
r = 0.10
arr = 240_000
cost = [120_000, 20_000, 20_000] # servicing cost in years 1..3
contrib = [arr - c for c in cost]
pv = [c / (1 + r) ** (t + 1) for t, c in enumerate(contrib)] # cash at END of each year
print("year contribution discount PV")
for t, (c, p) in enumerate(zip(contrib, pv), start=1):
print(f"{t:>4} {c:>12,.0f} {1/(1+r)**t:>9.4f} {p:>10,.2f}")
print(f"NPV of gross contribution, 3 years: ${sum(pv):,.2f}")
# no-onboarding comparison: steady state cost $20k every year
pv_ss = sum((arr - 20_000) / (1 + r) ** t for t in (1, 2, 3))
print(f"NPV if year 1 looked like later years: ${pv_ss:,.2f}; onboarding costs ${pv_ss - sum(pv):,.2f} of PV")
# payback: assume year-1 split = $100k one-time onboarding at day 0 + $20k support spread evenly
onb, support, rev = 100_000, 20_000, arr
monthly = rev / 12 - support / 12
print(f"monthly contribution after onboarding: ${monthly:,.2f}; payback = {onb/monthly:.2f} months")
cum = -onb
for m in range(1, 13):
cum += monthly
if cum >= 0:
print("cumulative turns positive in month", m, f"(cum = ${cum:,.0f})")
break
print(f"year-1 gross margin {contrib[0]/arr:.1%}, later years {contrib[1]/arr:.1%}")
# variant: large segment, expected-value basis (survival 92%/yr, revenue expands 15%/yr for survivors)
acv, gm, exp, churn, onb2, sup = 300_000, 0.70, 0.15, 0.08, 200_000, 60_000
cash = [-onb2] # t=0
for y in range(3):
s = (1 - churn) ** y
revenue = acv * (1 + exp) ** y
cash.append(s * (revenue * gm - sup))
print("\nlarge segment cash flows (t=0..3):", [f"{c:,.0f}" for c in cash])
npv = sum(c / (1 + r) ** t for t, c in enumerate(cash))
print(f"large-segment NPV (onboarding at t=0, other flows year-end): ${npv:,.0f}")
# segment template: ACV, gross margin, annual expansion, annual churn, one-time onboarding, annual support
def segment(acv, gm, exp, churn, onb, sup, r=0.10):
cash = [-onb]
for y in range(3):
cash.append((1 - churn) ** y * (acv * (1 + exp) ** y * gm - sup))
npv = sum(c / (1 + r) ** t for t, c in enumerate(cash))
payback_months = onb / ((acv * gm - sup) / 12) # year-1 run rate, ignores churn and expansion
return npv, payback_months, cash[1] / acv
print("\nsegment ACV NPV(3y) payback(mo) yr-1 contribution/ACV")
for name, args in [("Small", (12_000, 0.80, 0.05, 0.20, 3_000, 2_000)),
("Medium", (60_000, 0.75, 0.10, 0.12, 30_000, 12_000)),
("Large", (300_000, 0.70, 0.15, 0.08, 200_000, 60_000))]:
npv, pb, mg = segment(*args)
print(f"{name:<8} {args[0]:>7,} {npv:>9,.0f} {pb:>10.1f} {mg:>10.1%}")
print(f"onboarding at day 0 instead of year-end in the main example: NPV ${sum(pv) + 100_000/(1+r) - 100_000 :,.2f}")
year contribution discount PV
1 120,000 0.9091 109,090.91
2 220,000 0.8264 181,818.18
3 220,000 0.7513 165,289.26
NPV of gross contribution, 3 years: $456,198.35
NPV if year 1 looked like later years: $547,107.44; onboarding costs $90,909.09 of PV
monthly contribution after onboarding: $18,333.33; payback = 5.45 months
cumulative turns positive in month 6 (cum = $10,000)
year-1 gross margin 50.0%, later years 91.7%
large segment cash flows (t=0..3): ['-200,000', '150,000', '166,980', '184,282']
large-segment NPV (onboarding at t=0, other flows year-end): $212,818
segment ACV NPV(3y) payback(mo) yr-1 contribution/ACV
Small 12,000 13,379 4.7 63.3%
Medium 60,000 51,971 10.9 55.0%
Large 300,000 212,818 16.0 50.0%
onboarding at day 0 instead of year-end in the main example: NPV $447,107.44
Segment comparison (small, medium, large)
The same template extends to a segment, on an expected-value basis (each year's cash flow is weighted by the probability the customer is still there): expected cash flow in year y is survival ((1 - churn) to the power y-1) times (ACV x (1 + expansion) to the power y-1 x gross margin minus annual support), with onboarding at time 0 and the 10% discount rate. ACV (annual contract value) is the yearly value of a contract. The large segment, an illustrative case I chose rather than data from the question, is ACV $300,000, 70% margin, 15% annual expansion, 8% annual churn, $200,000 onboarding and $60,000 support a year. Its flows are -$200,000, then $150,000, $166,980 and $184,282, giving NPV $212,818 and payback 16.0 months at the year-1 run rate. The small and medium rows are illustrative inputs I chose to show how the table reads, not data:
| Segment | ACV | NPV (3 years) | Payback (months) | Year-1 contribution / ACV |
|---|---|---|---|---|
| Small (illustrative: 80% margin, 5% expansion, 20% churn, $3,000 onboarding, $2,000 support) | $12,000 | $13,379 | 4.7 | 63.3% |
| Medium (illustrative: 75% margin, 10% expansion, 12% churn, $30,000 onboarding, $12,000 support) | $60,000 | $51,971 | 10.9 | 55.0% |
| Large (illustrative: 70% margin, 15% expansion, 8% churn, $200,000 onboarding, $60,000 support) | $300,000 | $212,818 | 16.0 | 50.0% |
Large has the highest NPV but the longest payback and the lowest year-1 margin, so it is the most exposed to churn in year 1 and to onboarding overruns. Its NPV is the one most sensitive to onboarding cost and to the churn assumption.
Trade-offs and pitfalls
- The question's 3-year horizon ignores later years; a customer who stays longer is worth more, and one who leaves in year 1 never repays onboarding.
- Mixing gross and net contribution, or discounting some flows and not others, breaks comparisons.
- The most sensitive assumption is how much of the $120,000 is one-time; if it is mostly ongoing, payback is not a meaningful concept.
A seat-expansion program aims to lift average seats per account from 20 to 30 across 1,000 accounts. Seats cost $50 per month, gross margin is 70%, annual churn is 8% and the program costs $600,000 a year. Estimate the LTV uplift and ROI, and say what has to hold for it to be worth running.
Sample Answer
Direct answer
Lifting average seats per account from 20 to 30 adds 10 seats x $50 x 12 x 70% = $4,200 of gross profit per account per year. Across 1,000 accounts that is 10,000 extra seats, $6.0M of added annual revenue and $4.2M of added gross profit a year, against a $600,000 programme: a one-year return on investment (ROI, net gain divided by cost) of (4.2M - 0.6M) / 0.6M = 6.0x if every account expands because of the programme. The LTV (customer lifetime value) uplift per account is $52,500 on the simple formula (gross profit per year divided by annual churn: $105,000 rises to $157,500) and $13,784 over five years discounted at an illustrative 10% (discounting scales each later year's profit down because a dollar received later is worth less than a dollar today: at 10% a year, a dollar arriving in year 5 counts as about 62 cents). The programme is worth running if the expansion is caused by the programme (not already on its way), the new seats are actually used (otherwise churn rises), and the margin and price per seat hold on the added seats.
Three return figures appear below and they answer different questions. For a one-year budget conversation quote the 6.0x. For a finance review quote the five-year discounted figure with five years of spend, about 5.1x (step 5). The 22.0x counts only one year of spend and is an upper bound, valid only if a single year of spend permanently creates the expansion.
Step by step
Terms: a seat is one licensed user in an account; gross margin is the share of revenue left after the direct cost of serving it; annual churn (logo churn, where a logo is one customer account) is the share of accounts lost per year; a holdout is a randomly chosen group of accounts kept out of the programme so they can be compared with the accounts that received it; a discount rate is the yearly rate used to scale later dollars down to today's value.
- Gross profit per seat per year: $50 x 12 months x 0.70 = $420.
- Per account today: 20 seats x $420 = $8,400 a year. After: 30 x $420 = $12,600 a year.
- Lifetime on the simple formula: 1 / 0.08 = 12.5 years. LTV = $8,400 x 12.5 = $105,000 before and $12,600 x 12.5 = $157,500 after, an uplift of $52,500 per account and $52.5M over 1,000 accounts.
- Twelve and a half years of undiscounted profit is not a number a finance reviewer will accept, so also show a horizon-capped version: five years, a 10% annual discount rate (illustrative), and accounts surviving at 92% a year. The uplift per account is $4,200 x the sum over years y = 1 to 5 of 0.92^(y-1) / 1.10^y, which the code below gives as $13,784.
- ROI, three framings and which to quote. (a) One year of spend against one year of profit: 6.0x, as in the Direct answer. (b) The question says the programme costs $600,000 a year, so for a five-year view I count five years of spend: about $2.27M discounted ($600,000 x 3.7908, where 3.7908 is the sum of 1 / 1.10^y for y = 1 to 5) against $13.78M of discounted profit, so ROI is ($13.78M - $2.27M) / $2.27M = 5.1x, and the share of accounts that must expand because of the programme to break even is $2.27M / $13.78M = 16.5%. This is the figure I would quote to finance. (c) The 22.0x, ($13.78M - $0.6M) / $0.6M, counts one year of spend only. It holds only if one year of spend permanently creates the expansion, so treat it as the upper bound. The code's ROI table and its 4.4% break-even are on this one-year-spend basis.
accounts, seats0, seats1 = 1000, 20, 30
price, gm, churn, cost = 50.0, 0.70, 0.08, 600_000.0 # $/seat/month, gross margin, annual logo churn, program $/year
disc = 0.10 # illustrative annual discount rate
gp_per_seat_year = price * 12 * gm
extra_seats = accounts * (seats1 - seats0)
uplift_gp_year = extra_seats * gp_per_seat_year
print(f"gross profit per seat-year ${gp_per_seat_year:.0f}; extra seats {extra_seats:,}")
print(f"added revenue ${extra_seats*price*12/1e6:.1f}M/yr, added gross profit ${uplift_gp_year/1e6:.2f}M/yr")
print(f"program ROI, one year, full effect: ({uplift_gp_year:,.0f} - {cost:,.0f}) / {cost:,.0f} = {(uplift_gp_year-cost)/cost:.1f}x")
print(f"break-even accounts that must really expand: {cost/((seats1-seats0)*gp_per_seat_year):.0f} of {accounts}")
# LTV per account
ltv0 = seats0*gp_per_seat_year/churn
ltv1 = seats1*gp_per_seat_year/churn
print(f"LTV per account, simple GP/churn: {ltv0:,.0f} -> {ltv1:,.0f}, uplift {ltv1-ltv0:,.0f}; x{accounts} = ${(ltv1-ltv0)*accounts/1e6:.1f}M")
def pv(gp_year, years=5):
return sum(gp_year*(1-churn)**(y-1)/(1+disc)**y for y in range(1, years+1))
up = (seats1-seats0)*gp_per_seat_year
pv_up = pv(up)
print(f"5-year discounted GP uplift per expanded account ${pv_up:,.0f} (survival {1-churn:.0%}/yr, discount {disc:.0%})")
print(f"accounts that must expand for a 5-year payback of the one-year program cost: {cost/pv_up:.1f} of {accounts} ({cost/pv_up/accounts:.1%})")
print("program (one year of spend, $600k) vs 5-year discounted benefit, by seats gained per expanded account and share of accounts that expand BECAUSE of the program:")
for seats in (5, 10):
for share in (1.0, 0.5, 0.25):
ben = accounts*share*pv(seats*gp_per_seat_year)
print(f" +{seats:>2} seats, {share:>4.0%} of accounts: benefit ${ben/1e6:5.2f}M, ROI {(ben-cost)/cost:5.1f}x")
arr_added = extra_seats*price*12
print(f"program cost per $1 of added annual recurring revenue (full effect): {cost/arr_added:.2f}")
Output:
gross profit per seat-year $420; extra seats 10,000
added revenue $6.0M/yr, added gross profit $4.20M/yr
program ROI, one year, full effect: (4,200,000 - 600,000) / 600,000 = 6.0x
break-even accounts that must really expand: 143 of 1000
LTV per account, simple GP/churn: 105,000 -> 157,500, uplift 52,500; x1000 = $52.5M
5-year discounted GP uplift per expanded account $13,784 (survival 92%/yr, discount 10%)
accounts that must expand for a 5-year payback of the one-year program cost: 43.5 of 1000 (4.4%)
program (one year of spend, $600k) vs 5-year discounted benefit, by seats gained per expanded account and share of accounts that expand BECAUSE of the program:
+ 5 seats, 100% of accounts: benefit $ 6.89M, ROI 10.5x
+ 5 seats, 50% of accounts: benefit $ 3.45M, ROI 4.7x
+ 5 seats, 25% of accounts: benefit $ 1.72M, ROI 1.9x
+10 seats, 100% of accounts: benefit $13.78M, ROI 22.0x
+10 seats, 50% of accounts: benefit $ 6.89M, ROI 10.5x
+10 seats, 25% of accounts: benefit $ 3.45M, ROI 4.7x
program cost per $1 of added annual recurring revenue (full effect): 0.10
What has to hold (and how far it can bend)
| Condition | Why it matters | Break point from the numbers |
|---|---|---|
| Expansion is caused by the programme | Accounts that would have grown anyway are not programme benefit | Year-one payback needs 143 of 1,000 accounts to expand because of it (14.3%); five-year discounted payback needs only 43.5 (4.4%) if one year of spend is counted, or 16.5% of accounts if five years of spend are |
| Added seats are used | Unused seats come back as downgrades or churn | If churn rises from 8%, both LTV figures fall; check seat activation 60 to 90 days after the add |
| Gross margin on added seats holds | Support and cloud cost can scale with seats | At 60% margin the one-year ROI at +10 seats falls from 6.0x to 5.0x (10,000 seats x $600 x 0.60 = $3.6M; (3.6 - 0.6) / 0.6) |
| Price per seat holds | Volume discounts can cut the price of the extra seats | A 20% discount on the added seats cuts added revenue by a fifth. If the cost to serve a seat does not fall, gross profit per added seat drops from $420 to $300 ($480 of revenue less $180 of serving cost), a 29% cut, and the one-year ROI falls from 6.0x to 4.0x (10,000 seats x $300 = $3.0M; (3.0 - 0.6) / 0.6) |
| The average is not a few large accounts | A rise from 20 to 30 seats needs 10,000 extra seats across the base, and four accounts adding 2,500 seats each would supply all of them while the other 996 accounts stay at 20 seats | Look at the distribution before approving |
The discounted sensitivity table (one year of programme spend against five-year discounted profit): +10 seats at 100%, 50% and 25% of accounts gives 22.0x, 10.5x and 4.7x; +5 seats gives 10.5x, 4.7x and 1.9x. It stays positive in every case shown, so the decision is driven by whether the causal share clears the break-even, 4.4% of accounts at +10 seats and 8.7% at +5 seats (600,000 / (1,000 x $6,892) of discounted profit per account), not by the margin assumptions. On the five-years-of-spend basis the weakest cases turn negative: +5 seats at 25% of accounts returns $1.72M of discounted profit against $2.27M of spend.
Ramp matters too. If expansion accrues evenly through the year instead of on day one, the first-year profit is about half ($2.1M), so the first-year ROI is (2.1M - 0.6M) / 0.6M = 2.5x. That is an estimate under an even-ramp assumption, not a measurement.
Contrast with new-logo CAC
The programme costs $600,000 to add $6.0M of annual recurring revenue (ARR, the yearly value of subscriptions), which is $0.10 of spend per $1 of added ARR at full effect. Compare that with your own new-logo figure: sales and marketing spend divided by new-logo ARR. If that were $1.00 per $1 of new ARR (an illustrative number, not a benchmark), expansion would be ten times cheaper per dollar. The reasons expansion is cheaper: the customer is already won, trust exists and the seats cost almost nothing to serve. The limit is headroom: new logos are open-ended, expansion stops when the accounts have no more people to add.
Pitfalls
- Reporting $52.5M as the benefit. It rests on a 12.5-year lifetime and on every account expanding because of the programme, which is the least likely condition.
- Skipping a control group. Randomly choose a set of accounts (say 10%) that get no seat-expansion outreach or incentives, then compare their seat growth over the same months with the accounts that received the programme. The gap is the measured uplift; without it the uplift is only inferred.
- Counting seats sold instead of seats used.
Model unit economics for a land-and-expand product. Define the structure and inputs (initial contract value, yearly probability and size of expansion, tenure-based churn), the outputs (cohort NPV, LTV, payback, contribution margin), and how the model would decide whether to invest in a larger initial sales effort.
Sample Answer
Direct answer
A land-and-expand model follows one cohort of accounts that start small (the land) and grow through upsell (the expand). For each year of tenure it needs three inputs: the chance an account is lost at renewal, the chance a surviving account expands, and the size of that expansion. From these it computes expected revenue per account landed, then contribution (revenue after cost of goods sold, the direct cost of delivering the product, and cost to serve), the LTV (lifetime value: here the five-year contribution added up without discounting), the NPV (net present value: contribution discounted, meaning each later year is scaled down because a dollar next year is worth less than a dollar today, minus the cost to land the account) and payback (how long until cumulative contribution repays the acquisition cost). The invest decision compares NPV per landed account and payback for a bigger initial sales effort against the base case. In the illustrative base below the account has an NPV of about $31,000 and pays back in 2.2 years; a bigger land is worth it at extra acquisition cost up to roughly $38,000 (the break-even, where its NPV equals the base case's) if expansion headroom is unchanged, and only up to about $29,000 if the larger land leaves half as much room to expand.
Structure and inputs
| Input | Symbol | Illustrative value |
|---|---|---|
| Initial contract value (annual revenue in year 1) | ICV | $30,000 |
| Loss probability at the renewal ending year t (tenure-based: it depends on how long the account has been a customer) | c_t | 15%, 10%, 7%, 7%, 7% |
| Probability a survivor expands at the start of year t | p_t | 0%, 35%, 35%, 30%, 25% |
| Size of an expansion (share of the current contract) | g | 30% |
| Gross margin, and serving cost as share of revenue | gm, s | 75%, 5% |
| Cost to land the account (sales and marketing) | CAC | $45,000 |
| Discount rate, horizon | d, T | 10%, 5 years |
In plain words
Each year an account either leaves or stays. A survivor may expand: with probability p_t its contract grows by 30%, so on average (an expected value) a survivor's contract grows by p_t x 30%. Multiply the share still alive by the average contract size to get expected revenue per account landed, then by the contribution margin (75% gross margin minus 5% serving cost = 70%). Add the five years for LTV. For NPV, scale each year by 1 / (1.10)^t and subtract the $45,000 cost to land.
The mathematical form
Survival to year t, the expected contract size of a survivor and expected revenue per account landed:
St=∏k=1t−1(1−ck),A1=ICV,At=At−1(1+ptg),Rt=StAt
Contribution margin m=gm−s and the outputs:
LTV=∑t=1TmRt,NPV=−CAC+∑t=1T(1+d)tmRt
Payback is the first time cumulative contribution minus CAC reaches zero, found by linear interpolation inside the year.
Estimating the parameters from history. For each landing cohort and tenure year, the expansion probability is accounts that expanded divided by accounts still alive at the start of that year; the size g is the average uplift among those that expanded; the loss probability is accounts lost at renewal divided by accounts alive going into it. Recent cohorts only contribute to early tenure years (they are right-censored: still active when the data ends, so their later years are not yet observed), so each tenure year uses only cohorts old enough to have reached it. Where expansion probability differs by cohort, estimate p by cohort (or by ICV band), and shrink small cohorts toward the pooled value (pull a small cohort's estimate toward the all-cohort average, more strongly the fewer accounts it has) so a thin cohort does not drive the model.
Worked example
from dataclasses import dataclass, replace
@dataclass(frozen=True)
class Model:
icv: float = 30_000.0 # initial contract value, annual revenue in year 1 ($)
churn: tuple = (0.15, 0.10, 0.07, 0.07, 0.07) # probability an account is lost at the renewal ending year t (tenure-based)
p_expand: tuple = (0.00, 0.35, 0.35, 0.30, 0.25) # probability a surviving account expands at the start of year t
expand_size: float = 0.30 # expansion lifts the account's annual contract by 30%
gm: float = 0.75 # gross margin
serve_cost_rate: float = 0.05 # account management / support, share of revenue (contribution margin deduction)
cac: float = 45_000.0 # sales and marketing spend to land the account ($)
disc: float = 0.10 # annual discount rate
years: int = 5
def cohort(m):
alive, acv = 1.0, m.icv
rows, cum_cash, payback = [], -m.cac, None
npv = -m.cac; ltv = 0.0
for t in range(1, m.years + 1):
# expected ACV of a survivor grows by expand_size with probability p_expand[t-1]
acv = acv * (1 + m.p_expand[t-1] * m.expand_size) if t > 1 else m.icv
rev = alive * acv # expected revenue per account landed
contrib = rev * (m.gm - m.serve_cost_rate) # contribution after COGS and serving cost
df = 1 / (1 + m.disc) ** t
ltv += contrib; npv += contrib * df
prev = cum_cash; cum_cash += contrib
if payback is None and cum_cash >= 0:
payback = t - 1 + (-prev / contrib) # linear interpolation inside the year
rows.append((t, alive, acv, rev, contrib, cum_cash))
alive *= 1 - m.churn[t-1] # renewal at end of year t
return rows, ltv, npv, payback
b = Model()
rows, ltv, npv, pb = cohort(b)
print("year alive ACV of survivor expected revenue contribution cumulative cash after CAC")
for t, a, acv, rev, c, cum in rows:
print(f"{t:>4} {a:5.3f} {acv:14,.0f} {rev:16,.0f} {c:12,.0f} {cum:12,.0f}")
print(f"5-year contribution LTV ${ltv:,.0f}, LTV:CAC {ltv/b.cac:.2f}, NPV after CAC ${npv:,.0f}, payback {pb:.2f} years")
# Decision: heavier initial sales effort lifts the landed contract and (assumed) adds CAC
print("\nLarger initial land: ICV $30k -> $45k costs extra CAC of X; NPV per landed account")
for extra in (10_000, 20_000, 30_000):
big = replace(b, icv=45_000.0, cac=b.cac + extra)
_, l2, n2, p2 = cohort(big)
print(f" extra CAC ${extra:,}: NPV ${n2:,.0f} (base ${npv:,.0f}), delta ${n2-npv:,.0f}, payback {p2:.2f} y")
print("\nsame decision if the bigger landing also churns more (year-1 churn 15% -> 22%):")
for extra in (10_000, 20_000):
big = replace(b, icv=45_000.0, cac=b.cac + extra, churn=(0.22,) + b.churn[1:])
_, l2, n2, p2 = cohort(big)
print(f" extra CAC ${extra:,}: NPV ${n2:,.0f}, delta ${n2-npv:,.0f}")
print("\nsame decision if the bigger land leaves less to expand into (expansion probabilities halved):")
for extra in (10_000, 20_000, 30_000):
big = replace(b, icv=45_000.0, cac=b.cac + extra, p_expand=tuple(x/2 for x in b.p_expand))
n2 = cohort(big)[2]
print(f" extra CAC ${extra:,}: NPV ${n2:,.0f}, delta ${n2-npv:,.0f}")
# break-even extra CAC for the bigger land
big = replace(b, icv=45_000.0)
_, _, n_big_free, _ = cohort(big)
print(f"\nbreak-even extra CAC for a 45k land (churn unchanged): ${n_big_free - npv:,.0f}")
# sensitivity of base NPV to expansion probability and to year-1 churn (the loss at the renewal ending year 1)
print("\nbase NPV, expansion probability scaled and year-1 churn:")
for scale in (0.5, 1.0, 1.5):
cells = []
for c1 in (0.10, 0.15, 0.20):
q = replace(b, p_expand=tuple(min(1.0, x*scale) for x in b.p_expand), churn=(c1,)+b.churn[1:])
cells.append(f"{cohort(q)[2]:>9,.0f}")
print(f" expansion x{scale}: " + " ".join(cells), " (columns: year-1 churn 10%, 15%, 20%)")
Output:
year alive ACV of survivor expected revenue contribution cumulative cash after CAC
1 1.000 30,000 30,000 21,000 -24,000
2 0.850 33,150 28,178 19,724 -4,276
3 0.765 36,631 28,023 19,616 15,340
4 0.711 39,928 28,406 19,885 35,225
5 0.662 42,922 28,399 19,880 55,104
5-year contribution LTV $100,104, LTV:CAC 2.22, NPV after CAC $31,055, payback 2.22 years
Larger initial land: ICV $30k -> $45k costs extra CAC of X; NPV per landed account
extra CAC $10,000: NPV $59,082 (base $31,055), delta $28,027, payback 1.79 y
extra CAC $20,000: NPV $49,082 (base $31,055), delta $18,027, payback 2.13 y
extra CAC $30,000: NPV $39,082 (base $31,055), delta $8,027, payback 2.47 y
same decision if the bigger landing also churns more (year-1 churn 15% -> 22%):
extra CAC $10,000: NPV $52,045, delta $20,991
extra CAC $20,000: NPV $42,045, delta $10,991
same decision if the bigger land leaves less to expand into (expansion probabilities halved):
extra CAC $10,000: NPV $50,244, delta $19,189
extra CAC $20,000: NPV $40,244, delta $9,189
extra CAC $30,000: NPV $30,244, delta $-811
break-even extra CAC for a 45k land (churn unchanged): $38,027
base NPV, expansion probability scaled and year-1 churn:
expansion x0.5: 28,167 25,162 22,158 (columns: year-1 churn 10%, 15%, 20%)
expansion x1.0: 34,405 31,055 27,704 (columns: year-1 churn 10%, 15%, 20%)
expansion x1.5: 41,178 37,451 33,724 (columns: year-1 churn 10%, 15%, 20%)
Reading it: a landed account expects $100,104 of contribution over five years, an LTV:CAC of 2.22, an NPV after CAC of $31,055, and repays its $45,000 CAC in 2.22 years.
The decision: a larger initial sales effort
The proposal lifts the land from $30,000 to $45,000 at extra acquisition cost. The break-even extra cost is where NPV equals the base case.
| Extra CAC | NPV per account | Gain over base | Payback |
|---|---|---|---|
| $10,000 | $59,082 | +$28,027 | 1.79 years |
| $20,000 | $49,082 | +$18,027 | 2.13 years |
| $30,000 | $39,082 | +$8,027 | 2.47 years |
Under unchanged expansion behaviour the break-even is $38,027 of extra CAC. Three further checks change the answer:
- Less headroom. A bigger land leaves less to expand into. With expansion probabilities halved, the gain is +$19,189 at $10,000, +$9,189 at $20,000 and -$811 at $30,000, so the break-even falls to about $29,000.
- More early churn. If the bigger land also churns more in year one (22% instead of 15%), the gain over the base falls to +$20,991 at $10,000 and +$10,991 at $20,000, and the break-even falls to about $31,000. Halved headroom and higher year-one churn move the break-even by similar amounts ($29,000 and $31,000, against $38,000), so test both on past large lands before deciding.
- Cash limit. If the company will not accept payback beyond 2 years, $20,000 of extra CAC (2.13 years) fails even though its NPV is positive, while $10,000 (1.79 years) passes.
The sensitivity of the base case: moving the expansion probabilities from half to 1.5 times the base at 15% year-one churn moves NPV from $25,162 to $37,451 (a swing of $12,289); moving year-one churn from 10% to 20% at base expansion moves it from $34,405 to $27,704 (a swing of $6,701). In these ranges expansion matters more than early churn.
Pitfalls
- Treating a cohort as survivors only and forgetting that an expansion probability applies to accounts still alive.
- Using one flat churn rate for all tenures; early churn is usually higher and the model above encodes tenure.
- Assuming a larger land has the same expansion headroom and the same churn. Both are testable on past data: compare cohorts that landed large with those that landed small.
- Reporting an NPV with no range. Report the parameter ranges from the cohort history and the decision threshold.
Unlock Full Question Bank
Get access to all Business Metrics and Unit Economics interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.