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.
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.
As the analyst advising a growth team, how would you decide whether to scale spend on a channel? Describe the metrics you would watch, how you would estimate diminishing returns, and the decision rules you would use to set a budget ceiling.
Sample Answer
Direct answer
I would scale a channel only while the cost of the next customer (marginal CAC, customer acquisition cost) stays below what a customer is worth to us (the allowable CAC), and I would set the budget ceiling at the spend where those two meet. That needs three pieces: the right metrics, an estimated response curve showing how fast returns fall, and a written rule for the ceiling with guardrails.
Metrics to watch
- Spend, new paying customers, average CAC and marginal CAC by week, per channel.
- Payback and LTV:CAC from cohorts (groups of customers acquired in the same period), on gross profit, not from a blended model. LTV is lifetime value; payback is months to earn back CAC.
- Quality as spend rises: activation (reaching first value in the product), early retention, and plan mix. Extra spend often reaches less-qualified audiences.
- Saturation signals: rising cost per click, a falling impression share (the share of eligible ad auctions in which your ad appears) or the same people seeing your ads more and more often (creative fatigue: the audience tires of the same ads).
- Incrementality: a holdout test or spend-cut test showing how many customers vanish when spend drops.
Estimating diminishing returns
Steps 1 to 3 produce the estimate; steps 4 and 5 attach a range and a real-world check to it.
- Collect weekly spend and new paying customers for the channel (26 weeks here, with real variation in spend).
- Fit a response curve (the relationship between spend and the customers it buys). A power curve, customers = a x spend^b, is the simplest shape: taking logs turns it into a straight line, log(customers) = log(a) + b x log(spend), which ordinary regression fits (a log-log fit). A value of b below 1 is diminishing returns: with b = 0.67, 10% more spend gives about 6.6% more customers (1.1^0.67 = 1.066).
- Convert to marginal CAC. The customers added by one extra dollar is the slope of the curve at that spend. By the power rule of calculus the slope of a x spend^b is a x b x spend^(b-1), so the extra spend for one extra customer is the inverse, 1 / (a x b x spend^(b-1)). A shortcut that needs no calculus: marginal CAC = average CAC / b, because the slope is b times the average customers per dollar. With b = 0.67, the next customer costs about 1.5 times the average one.
- Express uncertainty with a bootstrap (redraw the 26 weeks at random, with repeats, refit the curve each time, and look at how much the answer moves) so the ceiling comes with a range.
- Calibrate with an experiment. Observational data (spend and results as they happened, rather than as an experiment set them) can mislead because budgets rise in good seasons, which makes returns look better than they are, and because past effects linger. A planned step-up (a deliberate temporary increase in spend) or a geographic split (raising spend in some regions only and comparing with the rest) gives the cleanest check.
Decision rules for the ceiling
- Allowable CAC = the smaller of (LTV / required ratio) and (payback limit in months x monthly gross profit per customer).
- Ceiling = the spend where marginal CAC equals allowable CAC. Setting 1 / (a x b x spend^(b-1)) equal to the allowable CAC and solving for spend gives spend^(1-b) = a x b x allowable CAC, so ceiling = (a x b x allowable CAC)^(1/(1-b)).
- Guardrails: raise spend in steps of 10 to 20%, read results for two weeks each, never extrapolate far past the range of spend you have observed, and cut back if cohort quality falls.
Worked example (simulated data, fixed seed)
Assumptions: gross-margin LTV $900, required ratio 3.0, gross profit $40 a month per customer, payback limit 12 months. Allowable CAC = min(900 / 3, 40 x 12) = min(300, 480) = $300.
import math, random
random.seed(7)
# 1. Simulated 26 weeks of one channel: weekly spend ($) and new paying customers.
# Truth used only to generate data: customers = 0.1 * spend**0.70 * noise
weeks = 26
spend = [random.uniform(6_000, 30_000) for _ in range(weeks)]
cust = [0.1 * s ** 0.70 * math.exp(random.gauss(0, 0.08)) for s in spend]
def fit_loglog(xs, ys):
lx = [math.log(x) for x in xs]; ly = [math.log(y) for y in ys]
n = len(xs); mx = sum(lx) / n; my = sum(ly) / n
b = sum((a - mx) * (c - my) for a, c in zip(lx, ly)) / sum((a - mx) ** 2 for a in lx)
return math.exp(my - b * mx), b # a, b in customers = a * spend**b
a, b = fit_loglog(spend, cust)
print(f"fit: customers = {a:.2f} * spend^{b:.2f}")
def avg_cac(s): return s / (a * s ** b)
def marg_cac(s): return 1 / (a * b * s ** (b - 1)) # $ for the next customer
for s in (10_000, 15_000, 20_000, 25_000, 30_000):
print(f"weekly spend {s:>6,}: avg CAC {avg_cac(s):6.0f} marginal CAC {marg_cac(s):6.0f}")
# 2. Decision rule: marginal CAC must stay under the allowable CAC
ltv_gm = 900 # $ gross-margin LTV per customer
min_ratio = 3.0 # company's chosen LTV:CAC floor for the NEXT dollar
gp_month = 40 # $ gross profit per customer per month
max_payback = 12 # months
cap = min(ltv_gm / min_ratio, gp_month * max_payback)
print("allowable marginal CAC:", cap)
ceiling = (a * b * cap) ** (1 / (1 - b))
print(f"weekly spend ceiling: {ceiling:,.0f}")
# 3. Uncertainty: bootstrap the 26 weeks 500 times
ceilings = []
for _ in range(500):
idx = [random.randrange(weeks) for _ in range(weeks)]
xs = [spend[i] for i in idx]; ys = [cust[i] for i in idx]
if max(xs) == min(xs): continue
a2, b2 = fit_loglog(xs, ys)
if b2 >= 0.99: continue
ceilings.append((a2 * b2 * cap) ** (1 / (1 - b2)))
ceilings.sort()
print(f"bootstrap 90% range of ceiling: {ceilings[int(0.05*len(ceilings))]:,.0f} to {ceilings[int(0.95*len(ceilings))]:,.0f}")
Output:
fit: customers = 0.13 * spend^0.67
weekly spend 10,000: avg CAC 158 marginal CAC 235
weekly spend 15,000: avg CAC 181 marginal CAC 269
weekly spend 20,000: avg CAC 198 marginal CAC 296
weekly spend 25,000: avg CAC 214 marginal CAC 318
weekly spend 30,000: avg CAC 227 marginal CAC 338
allowable marginal CAC: 300.0
weekly spend ceiling: 20,898
bootstrap 90% range of ceiling: 15,684 to 30,867
By hand, using the unrounded fit a = 0.1307 and b = 0.6712 (the printout rounds them to 0.13 and 0.67): at $20,000 a week the curve gives 0.1307 x 20,000^0.6712 = 100.8 customers, so average CAC is 20,000 / 100.8 = $198, and marginal CAC is 198 / 0.6712 = $296. The ceiling is (0.1307 x 0.6712 x 300)^(1 / 0.3288) = 26.32^3.04 = about $20,900. Rounded inputs give different answers by hand (b = 0.67 gives $302 and a ceiling near $19,700) because the exponent 1 / (1 - b) is above 3 and magnifies small rounding. So at $20,000 a week the average CAC is $198 but the marginal CAC is $296, already near the $300 limit. The fitted ceiling is about $20,900 a week, with a bootstrap range of roughly $15,700 to $30,900. I would move toward the lower part of that range, step up in small increments, and use the next experiment to narrow it.
Trade-offs and pitfalls
- A power curve never fully saturates, so it will keep suggesting that more money buys more customers. Use it only within the observed range, and switch to a saturating form if the data flatten.
- Two channels targeting the same audience share conversions; fitting them separately double-counts the response.
- The ceiling moves: seasonality, creative fatigue and competitor bids change the curve, so re-fit on a schedule.
How would you explain the uncertainty in an LTV estimate to Finance and Product stakeholders who need to act on it? Cover the language and visuals you would use and which assumptions you would put in front of them.
Sample Answer
Direct answer
Tell stakeholders that LTV (lifetime value, the gross profit a customer is expected to generate over their relationship with us) is an estimate with a range, not a fact, and show them the range, the handful of assumptions that drive it (five are listed in Step 4), and which decision survives the whole range. Quantify the range with a bootstrap (for sampling noise in the cohort you observed) and a Monte Carlo simulation (for uncertainty in ARPU, margin, churn and CAC). In the room, lead with the decision ("at the current price this channel fails our bar in most scenarios") and the probability, not the formula.
Step 1: Quantify the range
- Bootstrap the cohort: resample customers with replacement many times, recompute cohort LTV each time, and take the middle 95% of the results. It captures the noise from having a finite sample, but not the risk that the future behaves differently from the observed past.
- Monte Carlo the model: give each uncertain input a distribution (ARPU, margin, monthly churn, CAC), draw thousands of combinations, and compute LTV, LTV:CAC and payback in each. LTV:CAC is lifetime value divided by acquisition cost, the gross profit returned per dollar spent to win the customer; 3 or more is a common rule-of-thumb bar (a convention, not a law), which is why the code counts how often the ratio reaches 3. It captures assumption uncertainty, which is usually the dominant part.
How to read the code: random.gauss(20, 1.0) draws from a bell curve centred on $20 with a typical spread of $1, a natural shape for a well-measured figure like ARPU. random.uniform(0.65, 0.75) treats every margin between 65% and 75% as equally likely, which is what you say when you only know a range. random.triangular(low, high, mode) draws from a triangle that peaks at the most likely value (the mode) and tapers to the low and high bounds, so a churn of 5% is most likely, 3% and 8% are the extremes, and the long right side reflects that churn is more likely to surprise upward. Each of the 20,000 loops draws one plausible set of inputs and computes the outcome. The printed p10 / p50 / p90 are the 10th, 50th and 90th percentiles: 10% of outcomes fall below p10, p50 is the median (the middle outcome), and 90% fall below p90.
Bootstrap in miniature: take five customers who paid $20, $20, $40, $100 and $120 (average $60). Resampling with replacement draws five customers at random from those five, so the same customer can appear twice: one resample might be $20, $100, $100, $20, $40 (average $56), another $120, $120, $40, $20, $20 (average $64). Repeat thousands of times and the spread of the averages shows how much the result depends on which customers happened to be in the sample.
import random
random.seed(42)
N = 20_000
def pct(xs, p):
xs = sorted(xs)
return xs[int(p / 100 * (len(xs) - 1))]
ltv, ratio, pay = [], [], []
for _ in range(N):
arpu = random.gauss(20, 1.0) # $/month
margin = random.uniform(0.65, 0.75) # gross margin
churn = random.triangular(0.03, 0.08, 0.05) # monthly churn: low, high, mode
cac = random.triangular(95, 140, 110) # $ per customer
v = arpu * margin / churn
ltv.append(v)
ratio.append(v / cac)
pay.append(cac / (arpu * margin)) # months of gross profit to recover CAC
print("LTV $ p10 %.0f p50 %.0f p90 %.0f" % (pct(ltv, 10), pct(ltv, 50), pct(ltv, 90)))
print("LTV:CAC p10 %.1f p50 %.1f p90 %.1f" % (pct(ratio, 10), pct(ratio, 50), pct(ratio, 90)))
print("Payback months p50 %.1f p90 %.1f" % (pct(pay, 50), pct(pay, 90)))
print("P(LTV:CAC >= 3) = %.0f%%" % (100 * sum(r >= 3 for r in ratio) / N))
# one-at-a-time swing: move one input to its low/high bound, others at the central case
def case(arpu=20, margin=0.70, churn=0.05, cac=110):
return arpu * margin / churn
print("swing churn 0.03..0.08: %.0f..%.0f" % (case(churn=0.08), case(churn=0.03)))
print("swing margin 0.65..0.75: %.0f..%.0f" % (case(margin=0.65), case(margin=0.75)))
print("swing ARPU 18..22: %.0f..%.0f" % (case(arpu=18), case(arpu=22)))
# bootstrap on a synthetic 6-month cohort of 400 customers
random.seed(7)
cohort = []
for _ in range(400):
total, alive = 0.0, True
for m in range(6):
if alive:
total += 20
alive = random.random() > 0.05
cohort.append(total)
means = []
for _ in range(2000):
s = [random.choice(cohort) for _ in range(len(cohort))]
means.append(sum(s) / len(s))
print("observed 6-mo revenue per customer %.2f, bootstrap 95%% interval %.2f to %.2f"
% (sum(cohort) / len(cohort), pct(means, 2.5), pct(means, 97.5)))
LTV $ p10 204 p50 265 p90 354
LTV:CAC p10 1.8 p50 2.3 p90 3.1
Payback months p50 8.2 p90 9.4
P(LTV:CAC >= 3) = 13%
swing churn 0.03..0.08: 175..467
swing margin 0.65..0.75: 260..300
swing ARPU 18..22: 252..308
observed 6-mo revenue per customer 104.15, bootstrap 95% interval 100.95 to 107.10
The input distributions are invented for this illustration; in practice fit them to your data (for example, the spread of churn across cohorts) and have Finance agree the ranges.
Two things to read from the output. First, the bootstrap interval for the observed six months is narrow ($100.95 to $107.10), so the observed part of LTV is solid; the wide spread of LTV ($204 to $354 between the 10th and 90th percentiles, an 80% range) comes from the unobserved tail (churn above all, as the swings below show) together with ARPU and margin; CAC does not enter LTV at all, it only widens the LTV:CAC and payback ranges. Second, payback (months of gross profit needed to repay CAC) is far steadier than LTV, because it depends on the first months only, so a decision that can rest on payback is safer than one that rests on lifetime value.
Step 2: Language that survives a meeting
- Say "our central estimate is about $265 of gross profit per customer, and we are 80% sure it lies between $204 and $354", not "LTV is $265".
- Convert to decision language: "if our bar is an LTV:CAC of at least 3 (an illustrative internal threshold), only 13% of plausible scenarios clear it".
- Separate what we measured ("six months of revenue per customer is $104 plus or minus about $3") from what we assume (the tail).
- Avoid "confidence interval" jargon with Finance and Product; use "range" and "chance". If you use a technical term, say what the interval does not cover.
Step 3: Visuals
- Cumulative gross profit per customer by month since signup, with the observed part as a solid line, the projection as a dashed line with a shaded band (10th to 90th percentile), and a horizontal line at CAC. The crossing shows payback and its uncertainty at a glance.
- Histogram of LTV:CAC with the decision threshold marked and the share of outcomes above it written on the chart.
- Tornado chart (horizontal bars, one per assumption, the widest at the top so the picture narrows downward like a tornado; each bar spans the result when that assumption alone moves from its low to its high value): churn $175 to $467, ARPU $252 to $308, margin $260 to $300 in the example. It tells Product where measurement effort should go: here, the churn tail.
Step 4: Assumptions to put in front of them
- Tail churn (the biggest swing): how it was extrapolated and what the observed months support.
- CAC definition: fully loaded or media only, blended or paid.
- Gross margin basis and what cost of revenue includes.
- Horizon and discounting (for example, 36 months, discounted at a stated rate).
- Representativeness: whether the cohort comes from channels and prices we will use going forward.
Pitfalls
- Reporting only the bootstrap interval and calling it the uncertainty: it ignores model risk.
- Drawing inputs independently when they move together (a discount that lowers ARPU may also raise churn); say how correlation was handled.
- Burying the range in an appendix. If the decision flips inside the range, that is the headline.
You need operating margin per customer for a product line. Compare ways to allocate shared fixed costs (flat per customer, proportional to revenue, usage-based), explain how each affects pricing decisions, and recommend one for a product with highly variable usage.
Sample Answer
Direct answer
Use usage-based allocation for a product with highly variable usage, but make pricing and keep-or-drop decisions on contribution margin (revenue minus the costs that vary with the customer) and use allocated operating margin only as a long-run price floor. Flat per customer, revenue-proportional and usage-based allocations each change which customers look unprofitable, and so which ones get a price rise: flat penalises small customers, revenue-proportional penalises big payers whatever they consume, and usage-based charges cost to whoever drives it.
Worked example
Illustrative monthly numbers for an API product with $30,000 of shared fixed cost (platform team, on-call, tooling). Usage is in millions of API calls. Contribution margin is revenue minus variable cost (infrastructure and support that scale with use).
| Customer | Revenue | Variable cost | Usage (M calls) | Contribution margin |
|---|---|---|---|---|
| Small | $10,000 | $2,000 | 5 | $8,000 |
| Mid | $30,000 | $9,000 | 15 | $21,000 |
| Spiky | $20,000 | $14,000 | 80 | $6,000 |
Total revenue is $60,000, total usage 100M calls, total contribution $35,000, so operating margin overall is $35,000 - $30,000 = $5,000.
| Customer | Flat ($10,000 each) | Margin after | Revenue-proportional | Margin after | Usage-based | Margin after |
|---|---|---|---|---|---|---|
| Small | $10,000 | -$2,000 | $5,000 | $3,000 | $1,500 | $6,500 |
| Mid | $10,000 | $11,000 | $15,000 | $6,000 | $4,500 | $16,500 |
| Spiky | $10,000 | -$4,000 | $10,000 | -$4,000 | $24,000 | -$18,000 |
(Revenue-proportional shares are 10/60, 30/60 and 20/60 of $30,000; usage-based shares are 5, 15 and 80 percent.) Each column of margins adds to $5,000.
How each method distorts pricing
- Flat: Small looks unprofitable at -$2,000 even though it uses 5% of capacity. The tempting response is to raise Small's price or drop it, which is wrong. Meanwhile Spiky, which consumes 80% of usage, looks only mildly negative and escapes scrutiny.
- Revenue-proportional: Mid, the best-margin customer in absolute terms, is charged the most ($15,000) just for paying more. Pricing conclusions point at big payers, and Spiky's heavy usage is still invisible.
- Usage-based: Spiky is correctly exposed at -$18,000, and the message for pricing is specific: its price per million calls is $20,000 / 80 = $250, against $175 of variable cost per million calls and $300 of fixed cost per million calls ($30,000 / 100M), a full cost of $475 per million. Keep it (it covers variable cost and contributes $6,000), but move it to a metered or tiered price (about $38,000 at 80M calls to cover full cost: 80 x $475) at renewal, or a committed-spend plan.
Why not decide on allocated margin
Suppose the flat view makes you drop Spiky because it shows -$4,000. You lose $6,000 of contribution and still carry $30,000 of fixed cost, so total operating margin goes from +$5,000 to -$1,000 (29,000 - 30,000). The $30,000 now spreads over two customers ($15,000 each), Small at -$7,000 and Mid at +$6,000, and the next review targets Small. This is the allocation death spiral, and it is why the decision rule is contribution margin, with allocated margin telling you whether the overall price level can ever cover the fixed base.
Recommendation
For this variable-usage product: allocate shared fixed cost by usage (calls, or compute hours, whichever best tracks the platform cost), publish contribution margin beside it, and price new deals on a metered or tiered model so that price follows the same driver as cost. Revisit the driver if fixed cost is not really usage-driven (an on-call team is staffed for peak load, not total volume; then peak usage, not monthly total, is a better driver).
Pitfalls
- Using one allocation for every decision. Pricing floors, drop decisions and finance reporting need different views.
- Treating a usage allocation as precise when the driver (total calls) is noisy or dominated by a spike.
- Forgetting that usage-based allocation can create a feedback loop: raising Spiky's price lowers its usage, shifting costs onto other customers next period.
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.