Why Promo Codes Beat Sales: Price Discrimination in Practice
A blanket 20% discount earns $20,000 less than no discount at all. The same discount behind a code that only bargain hunters bother to find earns $64,000 more. The friction is the entire mechanism.
Table of contents
The reason promo codes are annoying to find is that they are supposed to be. The effort required to locate one is a sorting mechanism, and it sorts customers by exactly the thing you want to know: how price sensitive they are.
Economists call this third-degree price discrimination. Marketers call it a coupon. Both are describing a system that charges different prices to different people without asking anyone what they are willing to pay.
The short answer
With 10,000 customers where 6,000 will pay $100 and 4,000 will pay at most $80, against a $55 unit cost: charging list earns $270,000, a blanket discount to $80 earns $250,000, and a code-gated discount to $80 with 30% leakage into the high-willingness segment earns $334,000. The gate beats no discount as long as fewer than 83.3% of full-price customers use the code.
Why the blanket discount loses money
This is the counterintuitive part, so it is worth doing slowly.
Charge $100 with no discount. The 4,000 price-sensitive customers do not buy, because $100 is above what they will pay. The 6,000 others buy at $45 contribution each, for $270,000.
Now discount to $80 for everyone. All 10,000 buy, at $25 contribution each, for $250,000. You gained 4,000 customers and lost $20,000.
The gain from the new segment is 4,000 x $25 = $100,000. The loss is $20 given away to 6,000 people who were paying full price, which is $120,000. The second number is bigger, and it is bigger for exactly the same reason a blanket promo discount fails: most of the money goes to people who did not need it.
What the gate changes
Put the same $80 price behind a code that requires effort to find. Price-sensitive customers, by definition the ones for whom $20 is worth ten minutes of searching, all find it. Full-price customers mostly do not bother.
| Leakage into full-price segment | Paying $100 | Using the code | Profit | vs no discount |
|---|---|---|---|---|
| 0% | 6,000 | 0 | $370,000 | +$100,000 |
| 10% | 5,400 | 600 | $358,000 | +$88,000 |
| 20% | 4,800 | 1,200 | $346,000 | +$76,000 |
| 30% | 4,200 | 1,800 | $334,000 | +$64,000 |
| 50% | 3,000 | 3,000 | $310,000 | +$40,000 |
| 70% | 1,800 | 4,200 | $286,000 | +$16,000 |
| 100% | 0 | 6,000 | $250,000 | -$20,000 |
The bottom row is the blanket discount. It is the special case where the gate has completely failed and everyone gets the lower price.
N, SHARE_HIGH = 10_000, 0.60
WTP_HIGH, WTP_LOW, COST = 100.0, 80.0, 55.0
n_high, n_low = N * SHARE_HIGH, N * (1 - SHARE_HIGH)
no_discount = n_high * (WTP_HIGH - COST)
print(f"no discount: ${no_discount:>10,.0f}")
print(f"blanket discount: ${N * (WTP_LOW - COST):>10,.0f}")
print(f"\n{'leakage':>9} {'profit':>12} {'vs no discount':>16}")
for leak in (0.0, 0.1, 0.2, 0.3, 0.5, 0.7, 1.0):
coded = n_high * leak
profit = (n_high - coded) * (WTP_HIGH - COST) + (coded + n_low) * (WTP_LOW - COST)
print(f"{leak:>9.0%} {profit:>12,.0f} {profit - no_discount:>+16,.0f}")
breakeven = (n_low * (WTP_LOW - COST)) / (n_high * (WTP_HIGH - WTP_LOW))
print(f"\ngating beats no discount while leakage < {breakeven:.1%}")
Output:
no discount: $ 270,000
blanket discount: $ 250,000
leakage profit vs no discount
0% 370,000 +100,000
10% 358,000 +88,000
20% 346,000 +76,000
30% 334,000 +64,000
50% 310,000 +40,000
70% 286,000 +16,000
100% 250,000 -20,000
gating beats no discount while leakage < 83.3%
Designing the gate
Everything depends on leakage, so the design question is how to make the discount findable by people who will search and invisible to people who will not.
| Gate | Leakage | Notes |
|---|---|---|
| Public sitewide banner | ~100% | Not a gate. This is a blanket discount. |
| Code on a coupon aggregator site | Low | Requires deliberate searching, which is the sort |
| Newsletter-only code | Low to medium | Sorts by engagement rather than price sensitivity |
| Abandoned-cart email | Medium | Sorts by hesitation, a decent proxy |
| Student or age verification | Very low | Sorts by verified attribute, not by effort |
| Code auto-applied by a browser extension | Rising toward 100% | This is the mechanism that breaks the model |
The last row is the live threat. Browser extensions that automatically apply every known code remove the effort that made the gate work, which converts your carefully sorted discount back into a blanket one. If a meaningful share of your traffic uses one, your leakage is much higher than you think and the table above says what that costs.
The caveats that matter
The two-segment model is a simplification. Real willingness to pay is a continuous distribution and the gain from discrimination is smaller than a clean two-block model suggests. The direction is robust; the magnitude is not.
Fairness is a genuine constraint rather than a footnote. Charging different prices to different people is legal in most contexts and legally restricted in some, and the reputational risk is real when the sorting variable correlates with something protected. Sorting on effort is broadly accepted because the customer chooses how much effort to spend. Sorting on inferred income, location or device is a different proposition and has caused real damage to companies that tried it.
Reference price erosion is the long-run cost. Customers who learn that a code always exists stop buying at list, which raises leakage permanently and moves you toward the bottom row of the table. Codes work best when they are genuinely intermittent, and a company running a permanent code is running a blanket discount with extra steps.
Get the Promo & Bonus Economics Workbook
Every formula for pricing a promo before you launch it: expected value, wagering cost, breakage, breakeven conversion. Worked examples with real executed numbers.
Browse all free guides →Want to implement this with guidance?
Santosh helps founders turn insights like this into real systems.
External Resources
Further Reading & Tools
NumPy documentation
Array computing and random number generation, the backbone of every model here
Python random module
Standard library sampling and distributions, enough for most marketing simulations
Python statistics module
Standard library descriptive statistics with no third-party dependency
QuantEcon
Open lectures on quantitative economic modelling in Python, including simulation methods