← System design

Financial & Trading

Design Google Ads

Every commercial search query starts a tiny market. In a few milliseconds the system has to find relevant ads among two billion keywords, predict who will be clicked, and set a fair price, while keeping thousands of advertisers inside their daily budgets and never billing them for a bot. The serving side may be approximate; the money side may not.
Core: quality-weighted auction + budget pacing + exact click billing160K auctions a second at peak60 ms ad decisionPay per valid click

01

What the ad system must do

02

What the ad system must do

Focus on search ads: a user types a query, a few sponsored results appear above and below the organic ones, and the advertiser pays only when someone clicks. Display and video ads share most of the machinery and can be mentioned at the end.

Functional
  • Advertisers create campaigns with keywords, a maximum cost per click, a daily budget, ad text, landing pages and targeting by location, language, device and schedule.
  • For each eligible query, pick which ads to show and in what order through an auction that weighs bid against expected usefulness.
  • Charge per click at a price set by the auction, never more than the advertiser's maximum bid.
  • Spread each campaign's daily budget across the day and stop serving when it is spent.
  • Filter invalid clicks (bots, accidental double clicks, competitors clicking repeatedly) and do not bill for them.
  • Report impressions, clicks, cost and conversions per campaign, keyword and hour.
Non-Functional
  • The ad decision fits inside the search page's budget: about 60 ms server-side at p99. If ads are late, the page renders without them.
  • Every valid click is billed exactly once. Billing errors erode advertiser trust faster than anything else.
  • Daily spend stays within a small, published tolerance of the budget.
  • Bid and budget changes take effect within a minute or two.
  • Reports are available within a few hours, with final, invoice-grade numbers after invalid click review.
Clarify early

Ask whether this is search ads, display ads bought through exchanges, or both, because display brings real-time bidding with outside buyers. Ask whether billing is per click or per impression. And ask how strict budgets must be: "never overspend by a cent" and "close enough over a day" are very different systems.

02

Sizing the ad system

03

Sizing the ad system

Assume 100,000 search queries a second on average and 250,000 at peak, with about 65% commercial enough to run an auction. There are 30 million active campaigns holding about 2 billion keywords between them.

QuantityWorkingResult
Auctions at peak250K x 65%about 160,000 / s
Candidates per auctionkeyword matches after targeting filtersa few hundred to a few thousand
Model scorings at peak160K auctions x 300 candidates after pre-filterabout 48M click predictions / s
Ads shown per auctionaverage after reserve thresholdsabout 3
Clicks per day100K x 65% x 3 ads x 3% click rate x 86,400 sabout 500M
Click rate500M / 86,400 sabout 5,800 / s average
Invalid clicksassume 10% of raw clicksabout 50M a day not billed
Impression log160K x 3 ads x about 400 B, average load lowerroughly 10 TB a day
Keyword index2B keyword entries x about 120 Babout 240 GB, replicated in every serving region
Budget state30M campaigns x about 200 Babout 6 GB
Where the effort goes

The striking number is tens of millions of click predictions a second, each inside a 60 ms window. That pushes toward cheap pre-filters and a compact model served next to the auction. The billing side is modest in rate but must be exact, which is a completely different engineering style.

03

API design

04

API design

POST/v1/campaignsCreate a campaign (advertiser API)
{ "name": "Monsoon trekking gear", "daily_budget_micros": 25000000000, // 25,000 in account currency "targeting": { "geo": ["IN-KA", "IN-MH"], "devices": ["mobile"] }, "ad_groups": [{ "keywords": [{ "text": "waterproof hiking boots", "match": "phrase", "max_cpc_micros": 42000000 }], "ads": [{ "headline": "Boots that stay dry", "final_url": "https://trailco.example/boots" }] }] } // 201 with status IN_REVIEW; ads serve after automated policy checks pass
POST/internal/ads:selectCalled by the search frontend
// deadline 60 ms; an empty list is a valid answer { "query": "waterproof boots bangalore", "geo": "IN-KA", "device": "mobile", "lang": "en", "request_id": "q_5f21a" } { "ads": [ { "slot": "top_1", "headline": "Boots that stay dry", "click_url": "https://ad.example/c/CjQKEg...sig" } ] }
GET/c/{signed_click_token}Click redirect
// token carries auction id, ad id, price, slot, issue time and an HMAC HTTP/1.1 302 Found Location: https://trailco.example/boots?clickid=CjQKEg // the click event is logged durably before the redirect is sent
GET/v1/reports?campaign=c_8812&from=2026-09-24&to=2026-09-30&by=keyword,dayPerformance report
{ "rows": [{ "keyword": "waterproof hiking boots", "day": "2026-09-29", "impressions": 18240, "clicks": 611, "cost_micros": 17102000000 }], "final_through": "2026-09-27" // later days may still change after click review }
Price the click at auction time

The click URL carries the price that the auction already computed, signed so it cannot be edited. When the click arrives, billing does not need to rerun anything: it validates the signature, deduplicates, and records the charge. That decouples billing from the serving path entirely.

04

Architecture overview

05

Architecture overview

Serving path and money pathDiagram
flowchart LR
	SF["Search frontend"] --> AS["Ad server"]
	AS --> KI[("Keyword index shards")]
	AS --> PM["Click prediction model"]
	AS --> BP["Local pacing state"]
	BP <-->|"spend leases"| BS[("Budget service")]
	AS -. "impressions" .-> K[["Kafka: impressions and clicks"]]
	U["User clicks ad"] --> CS["Click server"]
	CS --> K
	K --> IV["Invalid click filter"]
	IV --> LED[("Billing ledger")]
	IV --> BS
	LED --> RPT[("Reporting warehouse")]
	ADV["Advertiser UI and API"] --> CM[("Campaign DB")]
	CM -. "change stream" .-> KI

There are two very different halves. The serving path answers "which ads, in what order, at what price" for every query, under a strict deadline, and is allowed to be approximate about budgets. The money path turns clicks into charges, is allowed to take minutes or hours, and must be exactly right. They share an event stream and the budget service, and nothing else.

Campaign edits land in a relational database and flow out through a change stream that updates the keyword index in every region within a minute or two. Ad servers are stateless apart from caches: they read index shards, call the prediction model, consult local pacing state, run the auction and log the impressions they served.

Keyword index

An inverted index from normalised query terms and phrases to ad groups, with targeting attributes alongside, so geo and device filters run during retrieval rather than after it. Sharded by ad group, fully replicated per region.

Click prediction model

Predicts the chance that this user clicks this ad in this slot for this query. A large sparse logistic or neural model trained continuously on impression and click logs.

Budget service

Holds authoritative spend per campaign, sharded by campaign id, and hands ad servers small spend leases plus a pacing rate so they rarely need to call it.

Click server and ledger

A tiny redirect service that logs each click durably and then redirects. Downstream, filters drop invalid clicks and an append-only ledger records every billable charge and every credit.

05

Deep dive: ranking, pricing and pacing

06

Deep dive: ranking, pricing and pacing

From a query to a shortlist

Retrieval has to go from 2 billion keywords to a few hundred plausible ads in a few milliseconds. The query is normalised (lowercased, spelling corrected, synonyms expanded) and looked up in the keyword index under each match type: exact, phrase and broad. Targeting attributes stored with each posting remove ads that cannot show here: wrong country, wrong language, paused, outside schedule, or flagged as out of budget by the local pacing state. A cheap first-stage score, built from historical click rate and bid, trims the survivors to about 300 before the expensive model runs.

Ad rank: bid times expected usefulness

If the highest bid always won, a casino ad would top a query about children's shoes. The auction instead ranks by ad rank = bid x quality, where quality is dominated by the predicted click rate and adjusted for relevance and landing page experience. Multiplying bid by click probability gives expected revenue per impression, so ranking by it maximises revenue and, because irrelevant ads are rarely clicked, it also keeps the page useful. An ad must also clear a reserve threshold of ad rank for its slot, which is why some queries show no ads at all.

What the winner pays

Search ads typically use a generalised second-price rule. Each advertiser pays the smallest amount that would still have kept their position: the ad rank of the ad just below, divided by their own quality, plus a minimum increment. A better ad therefore pays less for the same slot than a worse one, which rewards relevance directly.

# ranked: candidates sorted by bid * quality, highest first def price_slots(ranked, reserve, step=0.01): shown = [ad for ad in ranked if ad.bid * ad.quality >= reserve][:4] for i, ad in enumerate(shown): below = shown[i + 1].bid * shown[i + 1].quality if i + 1 < len(shown) else reserve ad.cpc = min(ad.bid, below / ad.quality + step) return shown

A worked case: ad A bids 40 with quality 0.05 (rank 2.0), ad B bids 60 with quality 0.025 (rank 1.5). A wins the top slot despite the lower bid, and pays 1.5 / 0.05 + 0.01, about 30.01 per click. Be precise in an interview: with one slot this is a Vickrey auction and bidding your true value is optimal, but with several slots GSP is not strictly truthful. That is fine in practice, and it is a good detail to know.

Pacing a budget across the day

A campaign with a 25,000 daily budget on popular keywords could spend it all by 9 a.m. if it entered every auction. Advertisers want presence all day, so the system throttles participation. Every minute, the budget service compares actual spend against a target curve shaped like the campaign's expected traffic for the day, and publishes a participation probability: 1.0 when behind, dropping toward 0.2 when ahead. During retrieval the ad server rolls a die per campaign and skips it if the roll is above that probability.

Exact accounting cannot happen on every auction, because a central counter per campaign would see thousands of writes a second for big advertisers. Instead each ad server holds a spend lease: permission to spend, say, 0.5% of the remaining budget before checking in. When its lease runs out it asks for another, and when the campaign is nearly exhausted, leases shrink so the overshoot is bounded by the leases outstanding. Clicks arrive after impressions, so a small overshoot is unavoidable; the platform absorbs it or caps it with a published tolerance.

Budget feedback loopDiagram
flowchart LR
	CL["Billable clicks"] --> SP["Spend so far per campaign"]
	SP --> CMP{"Ahead of target curve?"}
	CMP -->|"yes"| DN["Lower participation probability"]
	CMP -->|"no"| UP["Raise participation probability"]
	DN --> PUB["Publish rate and lease size"]
	UP --> PUB
	PUB --> AS["Ad servers throttle auctions"]
	AS --> CL

Counting clicks once, and only the real ones

The click server checks the token's signature and age, writes the event to Kafka, and redirects. The token's id is the deduplication key: a double tap or browser prefetch creates two events with the same id, and the billing consumer keeps the first. Streaming filters then remove clicks that are plainly invalid, such as rapid repeats from one device, known data centre IPs, or impossible timing between impression and click. A slower batch analysis runs hours later over the whole day, finds subtler patterns such as click farms spread across many IPs, and issues credits. The ledger is append-only: a credit is a new negative entry, never an edit.

06

Tempting shortcuts to avoid

!

Tempting shortcuts to avoid

ShortcutWhy it hurtsBetter
Highest bid always winsIrrelevant ads take the top slot, users learn to ignore ads, and click rates fall for everyone.Rank by bid times predicted usefulness and set a quality floor per slot.
Winner pays its own bid on search adsAdvertisers keep lowering bids to find the cheapest winning price, so bids churn and revenue becomes noisy.Charge the minimum price that holds the position, which removes most of that probing.
Check a central budget counter on every auctionHundreds of thousands of synchronous calls a second land on the busiest campaigns' shards.Spend leases and a pacing probability, refreshed every minute.
Compute the price when the click arrivesThe auction context is gone by then, and billing becomes coupled to serving.Price at auction time and sign it into the click token.
Bill raw clicks and refund later on requestAdvertisers discover the bot traffic themselves, and trust is gone before the refund arrives.Filter in the stream before billing, then credit automatically after batch review.
Edit ledger rows when correcting chargesInvoices stop matching history, and audits become archaeology.Append credits and adjustments as new entries.

07

Decisions and their costs

07

Decisions and their costs

Chosen
Generalised second-price with quality

Stable prices, rewards for relevance, and pricing that is easy to explain on an invoice. It is not perfectly incentive-compatible across several slots, and its revenue depends heavily on well-tuned reserve thresholds.

Standard for search ads
Alternative
First-price with bid shading

Simple and transparent per auction, and what most display exchanges moved to. Buyers then need their own shading algorithms to avoid overpaying, which shifts complexity onto them.

Common in display
Chosen
Probabilistic pacing with spend leases

No synchronous budget call per auction, smooth delivery through the day, and bounded overshoot. Spend is only approximately right at any moment and must be reconciled from clicks.

Scales with traffic
Rejected
Strict per-auction budget check

Exact on paper, but it adds a network hop to every auction and creates hot shards for large advertisers. It still cannot be exact, because clicks arrive after the auction.

Cost without the benefit
Chosen
Two-stage scoring

A cheap first stage cuts thousands of candidates to hundreds, and the expensive model only scores those. Some good ads are cut early, so the first stage needs its own monitoring for recall.

Fits the latency budget
Alternative
Score every candidate with the full model

Best possible ranking, at several times the compute. It might fit on low-traffic markets, but not at peak global load.

Too expensive at peak
Chosen
Stream filtering plus batch review for clicks

Obvious fraud is never billed, and subtle fraud is credited within a day. Reports change slightly after they first appear, which needs clear labelling of provisional and final numbers.

Fast and thorough
Alternative
Only real-time filtering

Numbers never change after they appear, but coordinated click farms that only show up in a day of data slip through and are billed.

Misses organised fraud

08

Failure modes

08

Failure modes

💸
Budget service unavailable

A bad deploy takes the budget service down for ten minutes. Ad servers cannot renew spend leases.

Ad servers keep serving on their current lease and last pacing rate, then stop serving a campaign when its lease is used up. Overspend is bounded by outstanding leases, and campaigns resume automatically when the service returns.
🧠
Click model serves garbage

A training pipeline bug ships a model that predicts nearly the same click rate for every ad. Rankings collapse to bid order and irrelevant ads rise.

Canary new models on a small slice and compare predicted against observed click rates and revenue per query. Roll back automatically when calibration drifts. Keep the previous model loaded for instant fallback.
🧾
Clicks billed twice

A consumer restarts after processing a batch but before committing its offset, and replays several thousand clicks.

The ledger write is keyed by click id with a uniqueness constraint, so a replay is a no-op. Exactly-once billing comes from idempotent writes, not from the queue.
⏱️
Ad server misses its deadline

A slow index shard pushes ad selection past 60 ms. Without a guard, search pages would wait for ads.

The search frontend never waits past its deadline and simply renders without ads. The ad server fans out to shards with hedged requests and runs the auction on whatever answered in time.
🤖
A click farm drains a competitor

Thousands of residential IPs click one advertiser's ads at a plausible pace, burning its budget by mid-morning.

Detect concentrated click rates per advertiser against its history, look for shared device and behaviour fingerprints, and credit affected spend. Pace budgets so a morning attack cannot consume the whole day.
🐌
Campaign changes propagate slowly

The change stream lags for an hour. A paused campaign keeps serving and an advertiser's urgent bid cut does not take effect.

Alert on index freshness per region. Push pauses and budget stops through a fast separate channel to ad servers, and do not bill clicks for a campaign after its recorded pause time.

09

How to run the interview

09

How to run the interview

1

Explain ranking with expected value. Bid times click probability is revenue per impression. Starting there makes quality feel inevitable, not arbitrary.

2

Work one pricing example out loud. Two ads, two qualities, one price. It shows you understand the formula rather than reciting a name.

3

Bring up pacing before you are asked. Many candidates forget that budgets need spreading. Probability throttling and leases are the senior answer.

4

Split serving from money. Approximate and fast on one side, exact and slower on the other, connected by events.

5

Treat click fraud as billing integrity. Advertisers pay for clicks; charging for bots is charging for nothing. Describe both the stream and batch layers.

6

Know where display differs. Real-time bidding sends bid requests to outside buyers under a 100 ms budget, which brings first-price auctions and much more traffic.

10

How the design grows

11

How the design grows

Stage 1: a few hundred advertisers
Sold placements

Advertisers buy a keyword for a fixed monthly fee, and a sales team manages the list. No auction, no pacing, no model.

Stage 2: competition for keywords
A simple auction

Advertisers bid per click and the highest bid wins. Prices become unstable and irrelevant ads creep in.

Stage 3: users start ignoring ads
Quality in the ranking, second-price charging

Ad rank adds predicted click rate, and prices drop to what holds the position. Users click more and advertisers churn less.

Stage 4: large budgets and bad actors
Pacing, leases and click review

Budgets are spread through the day, the budget service hands out leases, and invalid clicks are filtered in the stream and credited after batch review.

Stage 5: automation for advertisers
Automated bidding and new formats

Advertisers set a target cost per conversion and the system bids per auction on their behalf. Display, video and shopping ads join through the same index and auction framework.

Watch and read

Try next

Free to read · better with landed

Whiteboard this with landed

landed runs it as a live system design round on the whiteboard and grades your trade-offs.