First-price vs second-price ad auctions: an interactive bidding simulator
Every time a search or feed page loads, an ad auction decides in milliseconds which ad to show and what it costs. Ads are ranked not by bid alone but by expected revenue: the bid per click times the predicted chance of a click. The pricing rule then changes how advertisers should bid. In a first-price auction the winner pays its bid; in a second-price auction it pays the least it could have bid and still won, which makes bidding what a click is truly worth the best strategy.
The playground has one ad slot and four advertisers. Move your bid, switch the pricing rule, and watch your profit at every possible bid.
A click is worth $3.00 to you, and your ad's predicted click-through rate is 2.5%. Ads are ranked by bid times click-through rate, so Rival B's high $4.00 bid ranks low: few people click it. Bidding $3.00, you win. In a second-price auction you pay $2.41 a click, the least that would still have beaten Rival A, for a profit of $14.75 per 1,000 impressions. Under second price, any bid above $2.40 gives the same profit, so bidding exactly what a click is worth to you is never worse than any other bid.
Ranking by expected revenue
Rival B bids the most, $4 a click, but its ad is clicked by only 1.2% of viewers, so it is worth $48 per thousand impressions to the ad server. Rival A bids half as much on an ad clicked by 3%, worth $60, and ranks above it. Ranking by bid times click-through rate, often adjusted by a wider quality score, maximises revenue and keeps irrelevant ads off the page: a less relevant ad of yours, at 1.2%, would need a bid of over $5 to win the slot.
First price, second price, and how to bid
If a click is worth $3 to you and you bid $3 in a second-price auction, you win and pay $2.41, just enough to beat Rival A, for a profit of $14.75 per thousand impressions. Bidding $2.50 or $4 changes nothing, since the price depends on the runner-up, not on you; bidding below $2.41 just loses the slot. So the best bid is your true value, and nobody needs to guess what others will do. The one danger is overbidding: if a rival’s ad becomes stronger, a $4.50 bid still wins but pays $3.85 for a $3 click.
In a first-price auction, bidding $3 wins and earns nothing, so advertisers shade their bids and try to guess where the threshold is. Ad exchanges moved to first price in 2019, partly because header bidding made second-price prices easy to game, and bidders responded with automated bid shading. Search ads with several slots use a generalised second-price auction, where each advertiser pays what is needed to hold its position; unlike the single-slot version, it no longer makes truthful bidding the best strategy.
A single-slot auction in code
def run_auction(ads, kind="second"):
"""ads: [{"id", "bid" (per click), "ctr" (predicted)}], one slot."""
for ad in ads:
ad["ecpm"] = ad["bid"] * ad["ctr"] * 1000 # expected revenue per 1,000 impressions
ranked = sorted(ads, key=lambda a: a["ecpm"], reverse=True)
winner = ranked[0]
if kind == "first" or len(ranked) == 1:
price = winner["bid"]
else:
runner_up = ranked[1]
# the least the winner could have bid and still ranked first
price = min(winner["bid"], runner_up["ecpm"] / (winner["ctr"] * 1000) + 0.01)
return winner["id"], round(price, 2) # charged only if the ad is clickedIn a system design interview
For an ad server, the auction is the core of the request path: “Candidate ads come from an index matched on targeting, a few hundred per request. A model predicts each one’s click-through rate, we rank by bid times predicted CTR, apply budget pacing so campaigns spend evenly through the day, and price the winner at second price. All of it has to finish within about 50 ms. Impressions and clicks flow to an aggregation pipeline that updates budgets and billing.” Expect the follow-ups: pacing, click fraud, and keeping CTR predictions calibrated, since prices depend on them.
Read the guide to ad serving and auctions, see multi-armed bandits for exploring new ads, or watch a strong candidate design an ad click aggregator.
Questions people ask
How do online ad auctions work?
When a page loads, the ad server gathers eligible ads, predicts each one’s chance of being clicked, ranks them by bid times predicted click-through rate, and charges the winner according to the auction’s pricing rule, all within tens of milliseconds.
What is a second-price auction?
An auction where the winner pays the minimum it would have needed to bid to win, determined by the runner-up, rather than its own bid. In a single-slot second-price auction, bidding your true value is the best strategy whatever others bid.
What is the difference between first-price and second-price auctions?
In a first-price auction the winner pays its own bid, so bidders shade their bids below their true value and must guess the competition. In a second-price auction the price is set by the runner-up, so bidders can simply bid their true value.
Why do ad auctions rank by bid times CTR?
Because the ad server is paid per click, the expected revenue of showing an ad is its bid times the probability of a click. Ranking by this eCPM maximises revenue and favours relevant ads, so a low-quality ad must bid much more to win.
What is eCPM?
Effective cost per mille: the expected revenue per thousand impressions. For a cost-per-click ad it is the bid per click times the click-through rate times 1,000. It lets ads priced per click, per impression or per conversion compete in the same auction.
What is a generalized second-price auction?
The auction used for multiple ad slots, as in search ads: ads are ranked, and each pays the minimum needed to keep its position given the ad below it. Unlike the single-slot second-price auction, it does not make truthful bidding the dominant strategy.
Why did ad exchanges move to first-price auctions?
Header bidding let publishers run several auctions in parallel, which made second-price outcomes opaque and easy to manipulate with price floors. First-price auctions are simpler to reason about across exchanges; buyers now use automated bid shading to avoid overpaying.