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.
Cohort LTV is computed from highly skewed transaction revenue. Why can a normal-approximation confidence interval mislead, which two robust alternatives would you use, and how would you decide whether LTV differences between segments are real or noise?
Sample Answer
Direct answer
A normal-approximation confidence interval (the sample mean plus or minus 1.96 standard errors) assumes the sample mean is roughly bell-shaped. With skewed revenue, a handful of very large customers decide the mean, and the sample mean needs a very large sample before it looks normal. The interval then covers the true value less often than 95% (this hit rate is called coverage), can be symmetric when the truth is lopsided, and can even dip below zero. I would use two robust alternatives: a bootstrap interval for the mean (resample your own customers with replacement thousands of times, recompute the mean each time, and read the interval off the spread of those means; BCa, bias-corrected and accelerated, is a version that shifts the percentiles to correct for lopsidedness in that spread), and a capped (winsorised) LTV (every value above a chosen cap is replaced by the cap) that limits the influence of extreme customers, with LTV meaning customer lifetime value, the total profit a customer brings over their life, reported next to the uncapped figure. To decide whether segment differences are real, I would build the interval or test on the difference between segments with a permutation test (shuffle the segment labels and see how often chance alone produces a gap this big) or bootstrap, correct for the number of segments compared, require a minimum effect size that matters to the business, and suppress segments that are too small to resolve it. The bootstrap interval and the capped LTV are my two tools for an interval on one segment; the permutation test, the multiple-comparison correction and the sample-size rules govern comparing segments.
Why the normal interval misleads
LTV (customer lifetime value, the total profit a customer brings over their life) per customer is usually a few big values and many small ones. The central limit theorem (averages of many independent values look bell-shaped) says the sample mean becomes normal as the sample grows, but the speed depends on the skew. Three things go wrong:
- Coverage is below the stated 95%. A typical sample contains few or no whales (customers whose spend is far above everyone else's), so the sample mean and standard deviation are both too low, and the interval misses the true mean more often than 5% of the time.
- The interval is symmetric but the uncertainty is not. The upside risk (an unseen big customer) is larger than the downside.
- The standard error is itself noisy, since it comes from the same few points.
Measuring it: coverage of each interval
The simulation draws lognormal spend (a skewed shape in which the logarithm of spend is bell-shaped; here illustrative, with log-scale spread sigma = 2, median $20.09 and true mean $148.41; its coefficient of variation, CV, is the standard deviation divided by the mean, a measure of spread relative to the average), builds each kind of interval 300 times per sample size, and counts how often the interval contains the truth. In order, the code builds the five interval types, repeats 300 samples at each size, then runs a segment comparison on two simulated segments:
import math
from statistics import NormalDist
import numpy as np
ND = NormalDist()
MU, SIGMA = 3.0, 2.0 # illustrative skewed spend: lognormal, median $20.09
TRUE_MEAN = math.exp(MU + SIGMA ** 2 / 2) # exact mean of this distribution
CV = math.sqrt(math.exp(SIGMA ** 2) - 1) # coefficient of variation (std / mean)
print(f"true mean ${TRUE_MEAN:,.2f}, median ${math.exp(MU):,.2f}, coefficient of variation {CV:.2f}")
CAP = math.exp(MU + ND.inv_cdf(0.99) * SIGMA) # fixed cap at the population 99th percentile
TRUE_CAPPED = (math.exp(MU + SIGMA ** 2 / 2) * ND.cdf((math.log(CAP) - MU - SIGMA ** 2) / SIGMA)
+ CAP * (1 - ND.cdf((math.log(CAP) - MU) / SIGMA))) # exact E[min(X, CAP)]
print(f"cap ${CAP:,.2f} keeps {TRUE_CAPPED / TRUE_MEAN:.1%} of the true mean")
def normal_ci(x, alpha=0.05):
half = ND.inv_cdf(1 - alpha / 2) * x.std(ddof=1) / math.sqrt(len(x))
return x.mean() - half, x.mean() + half
def bootstrap_means(x, rng, B):
idx = rng.integers(0, len(x), size=(B, len(x)))
return x[idx].mean(axis=1)
def percentile_ci(x, rng, B=300, alpha=0.05):
m = bootstrap_means(x, rng, B)
return tuple(np.quantile(m, [alpha / 2, 1 - alpha / 2]))
def bca_ci(x, rng, B=300, alpha=0.05):
n, theta = len(x), x.mean()
m = bootstrap_means(x, rng, B)
frac = np.clip((m < theta).mean(), 1 / (B + 1), B / (B + 1))
z0 = ND.inv_cdf(frac)
loo = (x.sum() - x) / (n - 1) # leave-one-out means (jackknife)
d = loo.mean() - loo
acc = (d ** 3).sum() / (6 * ((d ** 2).sum() ** 1.5))
out = []
for q in (alpha / 2, 1 - alpha / 2):
z = ND.inv_cdf(q)
adj = ND.cdf(z0 + (z0 + z) / (1 - acc * (z0 + z)))
out.append(np.quantile(m, adj))
return tuple(out)
rng = np.random.default_rng(2024)
REPS = 300
print(f"{'n':>6} {'normal':>8} {'percentile':>11} {'BCa':>6} {'capped, normal':>15} {'normal lower<0':>15}")
for n in (100, 1000, 10000):
hit = {"normal": 0, "percentile": 0, "bca": 0, "capped": 0}
neg = 0
for _ in range(REPS):
x = rng.lognormal(MU, SIGMA, n)
lo, hi = normal_ci(x); hit["normal"] += lo <= TRUE_MEAN <= hi; neg += lo < 0
lo, hi = normal_ci(np.minimum(x, CAP)); hit["capped"] += lo <= TRUE_CAPPED <= hi
lo, hi = percentile_ci(x, rng); hit["percentile"] += lo <= TRUE_MEAN <= hi
lo, hi = bca_ci(x, rng); hit["bca"] += lo <= TRUE_MEAN <= hi
print(f"{n:>6} {hit['normal'] / REPS:>8.1%} {hit['percentile'] / REPS:>11.1%} "
f"{hit['bca'] / REPS:>6.1%} {hit['capped'] / REPS:>15.1%} {neg / REPS:>15.1%}")
# Sample size for a +/-10% relative error on the mean under the normal approximation
need = (ND.inv_cdf(0.975) * CV / 0.10) ** 2
print(f"n for +/-10% relative error (normal-theory planning figure): {need:,.0f}")
# Segment comparison: two segments, same distribution except segment B's mean is 15% higher
rng = np.random.default_rng(5)
a = rng.lognormal(MU, SIGMA, 800)
b = rng.lognormal(MU + math.log(1.15), SIGMA, 800)
obs = b.mean() - a.mean()
pooled = np.concatenate([a, b])
perm = np.empty(5000)
for k in range(5000):
rng.shuffle(pooled)
perm[k] = pooled[800:].mean() - pooled[:800].mean()
p_value = (np.abs(perm) >= abs(obs)).mean()
diffs = bootstrap_means(b, rng, 2000) - bootstrap_means(a, rng, 2000)
lo, hi = np.quantile(diffs, [0.025, 0.975])
print(f"mean A ${a.mean():,.2f}, mean B ${b.mean():,.2f}, difference ${obs:,.2f}")
print(f"permutation p-value {p_value:.3f}; bootstrap 95% CI for difference (${lo:,.2f}, ${hi:,.2f})")
top = np.sort(b)[-8:].sum()
print(f"top 1% of B (8 customers) = {top / b.sum():.1%} of B's total revenue")
Output:
true mean $148.41, median $20.09, coefficient of variation 7.32
cap $2,106.32 keeps 77.0% of the true mean
n normal percentile BCa capped, normal normal lower<0
100 75.0% 77.0% 82.0% 91.0% 7.3%
1000 86.3% 85.3% 86.7% 95.0% 1.0%
10000 91.3% 89.0% 89.0% 95.3% 0.0%
n for +/-10% relative error (normal-theory planning figure): 20,590
mean A $124.04, mean B $153.75, difference $29.71
permutation p-value 0.303; bootstrap 95% CI for difference ($-22.06, $86.34)
top 1% of B (8 customers) = 32.7% of B's total revenue
What it shows (300 repetitions per row, so each rate carries roughly 2 to 3 points of simulation error):
- The normal interval's coverage is 75.0% at n = 100 and 86.3% at n = 1,000, well under 95%. Even at n = 10,000 it is 91.3%. At n = 100 its lower bound is below zero in 7.3% of samples.
- The bootstrap helps only a little with this much skew: BCa reaches 82.0% at n = 100 and is no better than the normal interval at n = 10,000. A bootstrap can only resample the customers you have, and if the sample has no whale, no resample does either. Do not present it as a fix for tiny samples.
- Capping at a fixed level (here the population 99th percentile, $2,106.32) gives near-nominal coverage from n = 1,000 (91.0%, 95.0% and 95.3% at the three sizes), but it changes the quantity: the capped mean keeps 77.0% of the true mean. That is the trade-off, a lower, steadier number for a higher, noisier one, so report both.
The two robust alternatives, concretely
- BCa bootstrap CI of the mean. Resample customers (not transactions) with replacement, recompute the mean, and take adjusted percentiles. Resample by customer, because transactions of one customer are not independent. Use at least a few thousand resamples in production. In the BCa code,
z0measures bias (how far the bootstrap means sit below or above the sample mean, on a normal-score scale) andaccmeasures acceleration (how strongly single customers sway the mean, found by recomputing the mean with each customer left out in turn, the jackknife); both shift which percentiles of the bootstrap means become the interval ends. - Capped or winsorised LTV. Fix the cap in advance from a historical percentile (for example the 99th, the value that 99% of customers fall below), apply it, and show the capped mean with a normal or bootstrap interval, plus the share of revenue the cap removed. Whale accounts are then handled by account management, not statistics.
Real or noise between segments
Using the same simulated data, one segment has a true mean 15% higher than the other (n = 800 each). The observed difference is $29.71 ($153.75 against $124.04) and the permutation p-value is 0.303; the bootstrap 95% interval for the difference runs from -$22.06 to $86.34. A permutation test shuffles segment labels many times and asks how often a difference this large appears by chance. Here a real 15% effect cannot be told apart from noise at n = 800 per segment, and the top 8 customers (1%) account for 32.7% of that segment's revenue.
My decision procedure:
- Interval on the difference, not two separate intervals that are compared by eye. If the interval contains zero or an immaterial amount, call it noise.
- Permutation test for the same difference as a sanity check, since it makes no distribution assumption.
- Many segments means many chances to be fooled. With 20 segments compared, one will look significant by luck. Control the false discovery rate (the expected share of declared differences that are false) with the Benjamini-Hochberg procedure: sort the m p-values from smallest to largest, compare the i-th with (i / m) x 0.05, and declare the largest i that passes, and every smaller p-value, a real difference. With five p-values 0.001, 0.008, 0.039, 0.041 and 0.20 the thresholds are 0.01, 0.02, 0.03, 0.04 and 0.05, so the first two pass and the other three do not, whereas testing each at 0.05 on its own would have declared four.
- Pre-declare a minimum effect that matters (say a 10% LTV gap) and ask whether the interval excludes smaller gaps, not just zero.
- Check the whale sensitivity: recompute without the top 1% of customers. If the conclusion flips, it is one customer, not a segment.
- Check persistence: if the segment's gap shows up again in later cohorts, treat it as real.
Minimum sample sizes and suppression on a dashboard
Under the normal approximation the sample size for a relative error e is n = (1.96 x CV / e)^2, where CV is the coefficient of variation (standard deviation divided by mean). In the simulation CV = 7.32, so a plus or minus 10% error needs about 20,590 customers. That is a lower bound, since the simulation shows the normal interval undercovering even at 10,000. Rules I would apply:
- Suppress or grey out any segment whose capped-LTV interval is wider than a stated tolerance (for example more than plus or minus 25% of the mean), or with fewer than a floor of customers (a few hundred) and a minimum number of purchasers.
- Show the interval, not just the point, so a reader sees a wide band instead of a falsely precise number.
- Estimate CV from capped values, since uncapped CV is itself unstable.
Pitfalls
- Comparing two overlapping intervals by eye instead of testing the difference.
- Quoting the uncapped mean from a small segment as if it were stable.
- Resampling transactions instead of customers.
- Ranking 50 segments by LTV and presenting the top few as findings.
For a subscription product, explain MRR and ARR and how expansion, contraction, churn, upgrades, downgrades and reactivations each move them. Give the formulas you would use for net new MRR and for ARR.
Sample Answer
Direct answer
MRR (monthly recurring revenue) is the normalised monthly value of all active recurring subscriptions at a point in time (every plan converted to its monthly equivalent, so an annual plan counts as one twelfth per month). ARR (annual recurring revenue) is MRR annualised: ARR = MRR x 12 (for recurring revenue only). It is a run-rate: today's monthly level extended over twelve months, not revenue actually collected over the year. Movement in MRR over a period is built from five flows:
Net new MRR=New+Expansion+Reactivation−Contraction−Churn
Ending MRR=Starting MRR+Net new MRRARR=Ending MRR×12
Expansion means existing customers paying more (more seats, add-ons, a higher plan); contraction means existing customers paying less (fewer seats, a downgrade, a discount) while still staying.
How each event moves MRR
| Event | Effect on MRR | Bucket |
|---|---|---|
| New customer starts a paid plan | Increase by plan's monthly value | New |
| Expansion: more seats, add-ons, price increase on existing customer | Increase | Expansion |
| Upgrade to a higher-priced plan | Increase (the difference) | Expansion |
| Contraction: fewer seats, discount applied, usage tier down | Decrease | Contraction |
| Downgrade to a lower-priced plan | Decrease (the difference) | Contraction |
| Churn: customer cancels and leaves | Decrease by their whole MRR | Churn |
| Reactivation: a previously churned customer returns | Increase | Reactivation (kept separate from New so acquisition metrics stay clean) |
Upgrades and downgrades are the plan-change versions of expansion and contraction. Reactivation is tracked apart from new business because a returning customer usually costs less to win back than a brand-new customer (a new "logo" in subscription jargon, meaning one more company name on the customer list), and mixing the two would flatter the acquisition numbers.
Worked example (illustrative)
Starting MRR is $100,000. During the month: new customers add $12,000, upgrades and extra seats add $6,000, one returning customer reactivates for $2,000, downgrades reduce $3,000, and cancellations remove $5,000.
Net new MRR=12,000+6,000+2,000−3,000−5,000=12,000
Ending MRR = $112,000, so ARR = 112,000 x 12 = $1,344,000.
Counting rules that must be written down
- Prorated starts: a customer who joins mid-month pays a prorated (part-month) amount, but MRR counts the full monthly plan price from the start date, not the partial cash. Example: a $100 per month plan starting on day 16 of a 30-day month collects about $50 that month, yet adds $100 of MRR immediately.
- Trials: a free trial counts as zero MRR until it converts to paid.
- Cancellations: pick one rule and keep it. Common choices are to remove MRR when the cancellation takes effect (end of the paid term) or when the cancel is requested. Mixing them shifts churn between months. Example: a $100 per month customer asks to cancel on March 10 but is paid through March 31. Under the requested rule the $100 leaves in March's churn; under the takes-effect rule it leaves in April's.
- One-time charges (setup, implementation, and overage, meaning usage charges above the plan's included amount that vary month to month) are excluded from MRR and ARR.
- Annual and multi-month plans: divide by the term in months for MRR (a $1,200 annual plan is $100 MRR in each of its twelve months, even though all $1,200 is collected on day one).
- Discounts: use the actual discounted recurring price.
- Currency: convert at a fixed rate for the period so exchange movements are not reported as expansion or contraction.
Pitfalls
- Counting cash collected as MRR (an annual prepay lands in one month's cash but is 1/12 of MRR each month).
- Treating price increases as new business instead of expansion.
- Recording churn only when a whole customer (logo) cancels, so contraction goes unseen. A customer who halves their plan is not churn, but it is lost MRR.
In a spreadsheet, monthly retention rates for months 1 to 36 sit in B2:B37, monthly ARPU in B1 and an annual discount rate in B38. Write a single formula for discounted LTV as the sum of expected discounted monthly revenue, and explain how it handles survival and the monthly conversion of the annual rate.
Sample Answer
Direct answer
Assuming B2:B37 holds the month-over-month retention rate (the share of last month's customers still paying this month) for months 1 to 36, B1 is ARPU (average revenue per user per month) and B38 is the annual discount rate, one cell computes discounted LTV:
=B1*SUMPRODUCT(EXP(MMULT(--(ROW(B2:B37)>=TRANSPOSE(ROW(B2:B37))),LN(B2:B37)))/(1+B38)^((ROW(B2:B37)-ROW(B1))/12))
It works in three steps: it turns the monthly rates into survival (the share of the original customers still paying in month t, which is the running product of retention rates), divides each month's expected revenue by a discount factor for t months, and sums the 36 results. In Excel 2019 or earlier (before dynamic arrays, which let one formula fill many cells), enter it as an array formula by pressing Ctrl+Shift+Enter. If array formulas are new to you, the helper-column version at the end of the check section is the easiest to audit and a fine choice; the one-cell formula is the compact form.
How each piece works
- Survival. If the column holds conditional retention, then the share still paying in month t is B2 x B3 x ... x Bt. A running product is not a built-in function, so the formula uses logarithms: the sum of LN of the rates up to month t equals the log of their product, and EXP undoes the log.
ROW(B2:B37)>=TRANSPOSE(ROW(B2:B37))builds a 36 x 36 lower-triangular matrix of TRUE and FALSE (row i has TRUE in columns 1 to i).--turns it into 1s and 0s.MMULT(matrix, LN(B2:B37))multiplies that matrix by the column of logs, which gives the cumulative sum of logs for every month at once.EXP(...)returns the 36 survival values.
- Annual to monthly rate. Discounting month t revenue by t months needs a monthly rate. The correct conversion is (1 + annual)^(1/12) - 1, not annual / 12. At 10% a year the monthly rate is 0.7974%, whereas 10% / 12 is 0.8333%. The formula never builds the monthly rate: raising (1 + B38) to the power t/12 discounts by exactly the compounded monthly rate, because (1+im)t=(1+rannual)t/12
ROW(B2:B37)-ROW(B1)is 1, 2, ..., 36, so the exponent is t/12.
- Sum. SUMPRODUCT of a single array adds the 36 discounted survival values. Multiplying by B1 applies ARPU.
A three-month example by hand
Take ARPU $30, a 10% annual discount rate and retention of 90%, 97% and 97% in months 1 to 3. The 3 x 3 triangular matrix has rows (1,0,0), (1,1,0) and (1,1,1); multiplying it by the column of LN values adds up the logs seen so far in each row.
| Month t | Retention | LN(retention) | Running sum of LN (matrix x LN) | EXP = survival | Discount factor 1.10^(t/12) | $30 x survival / factor |
|---|---|---|---|---|---|---|
| 1 | 0.90 | -0.1054 | -0.1054 | 0.9000 | 1.0080 | $26.79 |
| 2 | 0.97 | -0.0305 | -0.1358 | 0.8730 | 1.0160 | $25.78 |
| 3 | 0.97 | -0.0305 | -0.1663 | 0.8468 | 1.0241 | $24.81 |
The three discounted values sum to $77.37, which is what the formula returns for these three months. Survival in month 3 is 0.90 x 0.97 x 0.97 = 0.8468, the same as EXP(-0.1663).
Timing and margin assumptions
- Each month's revenue is received at the end of the month and discounted by the full t months, and a customer pays in month t only if still retained at month t. Receive revenue at the start instead and the exponent becomes (t-1)/12.
- The result is revenue, not profit. To get gross-profit LTV multiply by a gross margin cell, for example
=B1*B39*SUMPRODUCT(...)with B39 the margin. - If B2:B37 holds cumulative survival rather than month-over-month retention, no product is needed:
=B1*SUMPRODUCT(B2:B37/(1+B38)^((ROW(B2:B37)-ROW(B1))/12)).
Checked against a plain loop
The check below uses ARPU $30, 10% annual discount, retention of 90% in month 1, 97% in months 2 to 6 and 98.5% in months 7 to 36. It computes LTV with a plain monthly loop, then evaluates the formula's own logic (the triangular matrix, the log-sum and the exponent) with numpy, and compares them.
import numpy as np
arpu, annual = 30.0, 0.10
ret = np.array([0.90] + [0.97]*5 + [0.985]*30) # B2:B37, month-over-month retention, 36 values
assert len(ret) == 36
# Reference: plain loop
monthly = (1 + annual) ** (1/12) - 1
surv, pv = 1.0, 0.0
for t in range(1, 37):
surv *= ret[t-1] # share of the original customers still paying in month t
pv += arpu * surv / (1 + monthly) ** t # revenue at the end of month t, discounted t months
print(f"monthly discount rate {monthly:.6f}")
print(f"loop discounted LTV: {pv:.4f}")
# Emulate the single-cell formula:
# =B1*SUMPRODUCT(EXP(MMULT(--(ROW(B2:B37)>=TRANSPOSE(ROW(B2:B37))),LN(B2:B37)))/(1+B38)^((ROW(B2:B37)-ROW(B1))/12))
rows = np.arange(2, 38)
tri = (rows[:, None] >= rows[None, :]).astype(float) # --(ROW(B2:B37)>=TRANSPOSE(ROW(B2:B37)))
cum = np.exp(tri @ np.log(ret)) # EXP(MMULT(tri, LN(B2:B37))) = running product
t_months = rows - 1 # ROW(B2:B37)-ROW(B1) = 1..36
formula = arpu * np.sum(cum / (1 + annual) ** (t_months / 12))
print(f"formula emulation: {formula:.4f}")
print("match:", abs(formula - pv) < 1e-9)
print(f"undiscounted: {arpu*cum.sum():.4f}")
print(f"no survival (ARPU x 36 discounted): {arpu*np.sum(1/(1+annual)**(t_months/12)):.4f}")
Output:
monthly discount rate 0.007974
loop discounted LTV: 619.5337
formula emulation: 619.5337
match: True
undiscounted: 705.3532
no survival (ARPU x 36 discounted): 935.5938
The two agree at $619.53. For reference, ignoring survival (every customer pays all 36 months) gives $935.59, and ignoring discounting gives $705.35. The Python run emulates the formula's logic and is not a spreadsheet engine, so after pasting the formula into the real sheet, with the same 36 inputs it should show 619.53. Compare it with a helper-column version: put =B2 in C2 and =C2*B3 in C3, fill down, then =B$1*C2/(1+$B$38)^((ROW()-1)/12) in D2 filled down and summed.
An alternative that reads more clearly
In Excel 365 or Google Sheets, SCAN (it walks down a list carrying a running result) builds the running product directly, and LAMBDA defines the small inline function that does one step:
=B1*SUM(SCAN(1,B2:B37,LAMBDA(a,r,a*r))/(1+B38)^((ROW(B2:B37)-ROW(B1))/12))
Pitfalls
- A retention rate of exactly 0 makes
LNfail. Use theSCANversion or a helper column if a rate can reach zero. - Using annual / 12 as the monthly rate understates the discount.
- Treating a retention column as cumulative when it is month-over-month, or the reverse, changes LTV by a large factor. State which it is.
- 36 months is a horizon, not a lifetime; say so, and consider adding a terminal value, a lump sum standing for the profit from customers still paying after month 36, if many customers remain at month 36.
How does revenue recognition for annual prepayments affect how a product team should report ARR and MRR and unit economics? Which adjustments are needed to sales and marketing metrics, and which investor-facing numbers need clarification?
Sample Answer
Direct answer
Revenue recognition for a service delivered over time is spread across the service period, while an annual prepayment is cash received at the start. For an annual subscription, the accounting standards (IFRS 15, used internationally, and its US equivalent ASC 606; both say a company records revenue when it delivers what it promised, not when it is paid) require recognising revenue as the performance obligation (the promise to provide the service) is satisfied, which for software access is typically evenly over the term. So a product team should keep four ideas apart: billings (what was invoiced), cash (what was collected), revenue (what was earned this period), and ARR/MRR (annual and monthly recurring revenue, a run-rate of contracted recurring subscription value, calculated from contract terms). Prepayment changes cash and billings, not revenue or ARR, so unit economics need adjusting wherever a metric silently uses cash or billings.
The mechanics, with numbers
A customer signs a 12-month contract with a list price of $12,000 and pays $11,400 upfront (a 5% discount).
price, list_ = 11400.0, 12000.0
mrr = price / 12
print("discount %", round((1 - price / list_) * 100, 1), "mrr", mrr, "arr", price)
bal = price
for m in range(1, 13):
bal -= mrr
if m in (1, 3, 6, 12):
print("end month", m, "revenue to date", round(mrr * m, 2), "deferred", round(bal, 2))
cac, gm = 9000, 0.80
print("payback months on MRR*GM", round(cac / (mrr * gm), 1))
delivery = mrr * (1 - gm)
print("cash collected at day 0", price, "vs CAC", cac)
print("delivery cost per month", round(delivery, 2), "over 12 months", round(delivery * 12, 2))
for m in (0, 6, 12):
print("cash left after CAC and delivery to month", m, round(price - cac - delivery * m, 2))
discount % 5.0 mrr 950.0 arr 11400.0
end month 1 revenue to date 950.0 deferred 10450.0
end month 3 revenue to date 2850.0 deferred 8550.0
end month 6 revenue to date 5700.0 deferred 5700.0
end month 12 revenue to date 11400.0 deferred 0.0
payback months on MRR*GM 11.8
cash collected at day 0 11400.0 vs CAC 9000
delivery cost per month 190.0 over 12 months 2280.0
cash left after CAC and delivery to month 0 2400.0
cash left after CAC and delivery to month 6 1260.0
cash left after CAC and delivery to month 12 120.0
- Month 0 billings and cash: $11,400. Revenue per month: $950. The unearned balance is deferred revenue (a liability for service still owed), which falls by $950 each month.
- ARR = $11,400 (the discounted, contracted annual recurring value, not the $12,000 list price). MRR = ARR / 12 = $950.
- The invoice is not a revenue spike and must not enter MRR as $11,400 in month 1.
Adjustments a product team should make
To ARR and MRR: compute them from contract terms (annualised recurring price net of discounts), not from invoices or cash. Exclude one-time fees, usage overage and services. For multi-year deals use the annual value, not the total contract value (the full amount over all years). For a ramped price (year 1 low, year 2 higher), use the price in force today for ARR and say how you treat the step-up. State whether ARR counts signed-but-not-yet-live contracts, and be consistent.
To unit economics: CAC payback (months to recover the cost of acquiring a customer) has two honest versions. On a recurring-revenue basis it is CAC / (MRR x gross margin) = 9,000 / (950 x 0.80) = 11.8 months. On a cash basis (gross margin is the share of revenue left after direct delivery costs, 80% here), the annual prepayer hands over $11,400 on day zero, more than the $9,000 CAC, so the cash outlay is recovered immediately. That cash is not all profit: 20% of revenue, $190 a month or $2,280 over the year, still has to be spent delivering the service. Even so, the cash left after CAC and delivery cost stays positive throughout ($2,400 on day zero, $1,260 at month 6) and ends the year at $120, which equals the contract's year of gross profit ($9,120) minus the $9,000 CAC. Annual prepayment improves cash and funding, not the profit economics, so do not tell leadership the unit economics improved because the cash came early. Likewise, LTV (lifetime value) uses recurring margin per month and churn, not billings.
To sales and marketing metrics: align the cost and revenue periods. Sales commissions are often paid at signing but may be capitalised (recorded as an asset) and amortised (expensed gradually over the contract or customer life) in the accounts, so reported expense can differ from the cash cost. Example: if $3,000 of the $9,000 CAC is commission paid on day zero and amortised over 36 months, the accounts show $83 a month of expense while the cash left on day zero, so use cash cost for CAC unless finance specifies otherwise, and say which. Sales efficiency ratios, such as the magic number (the standard form is the quarter-on-quarter change in recurring revenue, multiplied by 4 to annualise, divided by the prior quarter's sales and marketing spend; some teams substitute net new ARR in the numerator, which is the version that avoids billing-mix swings) and CAC ratio (spend per dollar of new recurring revenue), swing with annual-versus-monthly billing mix if their numerator is bookings (contract value signed in a period) or billings (what was invoiced); ratio them to new ARR instead. Example: 100 customers pay $20 a month ($2,000 of monthly billings). If 50 switch to $240 annual plans in one month, that month's billings are 50 x $20 + 50 x $240 = $13,000 while revenue stays about $2,000 and the extra $11,000 sits in deferred revenue.
Investor-facing numbers that need clarification
| Number | What to clarify |
|---|---|
| ARR / MRR | Contracted vs live; net of discounts; excludes usage and one-time fees; how multi-year deals are annualised |
| Revenue | Recognised ratably, so it lags ARR growth; do not compare to billings |
| Billings and deferred revenue | Deferred revenue growth is a cash signal from prepayment (in the example above it rises $11,000 with no new customer); strong billings can come from shifting more customers to annual, not growth |
| Net and gross revenue retention (the share of starting recurring revenue kept from the same customers, with and without upsell counted) | Based on ARR cohorts, not invoices |
| CAC payback | Margin-adjusted or not; recurring or cash basis |
| Free cash flow (cash generated after operating and investment spending) | Flattered when annual prepay share rises: collecting $240 up front instead of $20 a month brings forward $220 of cash for the same revenue |
Pitfalls
- Reporting a billings jump as growth when only the billing frequency changed.
- Counting a monthly customer at $20 and an annual one at $240 in the same average.
- Using list price for ARR when the contract is discounted.
Marketing spent $150,000 in a quarter and acquired 1,200 new customers. Calculate CAC. Then explain how you would adjust the calculation if acquisition typically takes several touches over weeks, and if a meaningful share of customers arrive through offline channels.
Sample Answer
Direct answer
CAC (customer acquisition cost) is spend divided by the new customers it produced: $150,000 / 1,200 = $125 per customer. That figure is only a starting point. When buying takes several touches over weeks, spend and customers fall in different periods, so you align them by lagging the spend or by cohorting on first touch. When some customers arrive offline, you either add the offline spend and customers to the calculation (blended CAC) or report paid CAC separately.
The base calculation: choose what goes in the numerator
- Numerator: is the $150,000 media spend only, or "fully loaded" (agency fees, marketing salaries, tools)? Fully loaded is the version that can be compared to lifetime value. State which one you used.
- Denominator: new customers acquired, not leads, signups or reactivated old customers.
Adjustment 1: acquisition takes several touches over weeks
A customer who buys in March may have clicked an ad in February. Dividing March spend by March customers matches money and results from different moments, which distorts CAC whenever spend is growing, shrinking or seasonal.
- Lag the spend. Measure the typical gap from first touch to purchase (for example, a median of four weeks) and compare each period's customers with the spend from that earlier window. Illustration (an invented number, not part of the question's data): if the previous quarter's spend was $120,000, the lagged CAC is $120,000 / 1,200 = $100, not $125. With flat spend the lag changes little; with fast growth it changes a lot.
- Cohort by first touch instead of purchase date. A cohort is a group of customers who share a starting point; here, those whose first recorded touch fell in the same week or month. Group customers by the date of their first recorded touch and wait for the conversion window to close before judging that cohort. Recent cohorts will look too good until the late converters arrive.
- Do not confuse this with attribution. The attribution model decides how credit is split across channels: first touch gives all credit to the first channel a customer met, last touch to the final one before purchase, position-based splits it (often most to the first and last), and data-driven uses statistics to weight each touch. Because it only reallocates credit, it changes per-channel CAC but not the blended total.
Adjustment 2: a meaningful share of customers arrive offline
Customers who come from events, word of mouth, print or a field sales team do not appear in click-based tracking.
- Blended CAC = all acquisition spend (including offline) / all new customers (including offline and organic). It is the honest company-level number.
- Paid CAC = paid spend for a channel / customers attributable to that channel. It is the number for deciding where the next dollar goes.
- Estimate the offline share with promo codes or vanity URLs (easy-to-remember web addresses used in only one channel, so visits identify that channel) per channel, a "how did you hear about us" question at checkout, and, for a big offline channel, a matched-market test: switch the activity off in some regions and compare new customers with similar regions where it stayed on (for example, if the paused regions' new customers fall by 30 per month and the comparison regions' do not move, the offline activity was worth about 30 customers a month in those regions).
Illustration (inputs invented for the example): of the 1,200 customers, 240 arrived offline, and offline events cost $30,000 that was not in the $150,000. Blended CAC = ($150,000 + $30,000) / 1,200 = $150. Digital CAC, assuming the other 960 customers all came from the digital spend, = $150,000 / 960 = $156.25. If part of those 960 were really organic, the true paid figure is higher still.
The two corrections work on separate assumptions and pull in opposite directions ($125 to $100 for lag, $125 to $150 for missing offline spend), so the naive number is not reliably conservative.
Pitfalls
- Quote CAC with its definition (fully loaded or media only, blended or paid) every time.
- A falling CAC can come from more organic or word-of-mouth growth rather than better marketing; check the paid and organic split.
- CAC alone says nothing about quality. Pair it with how quickly a customer repays it (payback, the months of gross profit needed to recover CAC). Illustratively, if a customer brings $40 of gross profit a month, the $125 CAC is repaid in 125 / 40 = about 3.1 months, and the $150 blended figure in 3.75 months.
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.