(a) Second-price auction (Vickrey). The winner pays the second-highest bid + $0.01, not their own bid. This is truthful: the optimal strategy is to bid your true value. No incentive to game down.
-- Auction logic (simplified)
quality_score = predicted_CTR * relevance * landing_page_quality
ad_rank = bid * quality_score
-- Sort candidates by ad_rank descending
-- Winner = highest ad_rank
-- Winner pays: (second_ad_rank / winner_quality_score) + 0.01
-- This ensures winner never pays more than their bid
Quality score = predicted CTR (ML model on historical click data) x relevance (query-to-keyword semantic match) x landing page quality (load speed, mobile-friendly, content relevance). This means a $2 bid with 10x quality beats a $10 bid with 1x quality. Google's entire ad incentive structure flows from this formula.
Why quality score matters economically. Without quality score, the platform maximizes short-term revenue (highest bidder wins) but destroys long-term revenue (users stop clicking ads because they're irrelevant, CTR drops industry-wide, advertisers see lower ROI and reduce spend). Quality score aligns all three parties: users get relevant ads, advertisers get clicks from interested users, platform maximizes total click volume and thus total revenue.
-- Example: two advertisers bidding on "running shoes"
Advertiser A: bid=$5.00, pCTR=0.08, relevance=0.9, landing=0.85
quality = 0.08 * 0.9 * 0.85 = 0.0612
ad_rank = 5.00 * 0.0612 = 0.306
Advertiser B: bid=$2.00, pCTR=0.15, relevance=0.95, landing=0.90
quality = 0.15 * 0.95 * 0.90 = 0.1283
ad_rank = 2.00 * 0.1283 = 0.257
Winner: Advertiser A (rank 0.306 > 0.257)
A pays: (0.257 / 0.0612) + 0.01 = $4.21 (not their $5 bid)
(b) Real-time bidding (RTB) for display ads. For display/programmatic ads, the ad server sends bid requests to multiple demand-side platforms (DSPs) simultaneously. Each DSP has < 100 ms to evaluate the impression, check campaign targeting, and return a bid. The ad exchange runs a second-price auction across all DSP responses. This is parallelized: fan-out to 10+ DSPs, take first responses within the deadline, discard slow ones.
-- RTB timeline (100 ms budget)
T+0ms : page-view triggers bid request
T+5ms : ad exchange fans out to 12 DSPs in parallel
T+10-80ms: DSPs evaluate targeting, check budgets, return bids
T+85ms : exchange collects responses (timeout at 80ms, late = no bid)
T+90ms : second-price auction across all received bids
T+95ms : winning creative URL returned to page
T+100ms : ad rendered in user's browser
Key engineering challenge: the fan-out must be non-blocking. Each DSP connection uses a persistent HTTP/2 stream. Slow DSPs are dropped after 80 ms — they lose the auction by default. This creates a strong incentive for DSPs to optimize latency.
Header bidding. Publishers sometimes run a client-side pre-auction (header bidding) before calling the ad exchange. Multiple demand sources bid in parallel in the browser, and the highest bid is passed to the exchange as a floor price. This gives publishers more control over yield but adds latency (50-200 ms client-side).
(c) Budget pacing. Advertiser sets $1000/day budget. Without pacing, high-traffic morning hours consume the entire budget by 10 AM — missing afternoon conversions entirely.
-- Probabilistic throttling
expected_remaining_queries = estimate_queries(now, end_of_day)
remaining_budget = daily_budget - spent_so_far
target_cpc = remaining_budget / expected_remaining_queries
-- For each auction:
participation_probability = target_cpc / actual_cpc
if random() < participation_probability:
enter_auction()
else:
skip() -- save budget for later
The pacer recalculates every few minutes. If spend is ahead of schedule, participation probability drops. If behind, it rises. Goal: spend exactly $1000 by midnight, spread evenly across the day.
Budget accounting at scale. With 10M auctions/sec, you cannot check a central budget counter atomically per auction. Instead, each ad server maintains a local budget shard. Central budget service allocates "spend allowances" to each server (e.g., $10 per 5-minute window). Server spends from its local allowance without coordination. When allowance exhausted, request more from central. Slight overspend possible (~2-5%) but no distributed lock needed.
(d) Click fraud detection. ~15-20% of raw clicks are invalid: bots, click farms, competitor sabotage, accidental double-clicks. Detection pipeline:
- Real-time filters: IP frequency cap (same IP, same ad, 10+ clicks/hour = fraud). User-agent anomalies. Click-to-conversion time < 1 second = bot.
- Batch ML model: runs hourly on click logs. Features: click timing distribution, mouse movement patterns, geo-IP mismatch, session depth (bot clicks but never browses).
- Conversion validation: clicks that never convert at a rate > 3 standard deviations from baseline flagged for review. Refund issued to advertiser.
- IP frequency capping: same IP clicking the same ad more than N times per hour is capped. Clicks beyond the cap are logged but not billed. Threshold varies by vertical (e-commerce: 3/hr, B2B: 5/hr).
Fraud refund pipeline. Clicks initially billed optimistically.
Batch ML runs hourly and flags invalid clicks.
At end-of-day, flagged clicks are reversed from advertiser's bill.
Advertiser sees "invalid click adjustment" line item in their invoice.
Google reports ~10% of clicks as invalid and auto-refunded.
Auction Sequence — Query to Ad ShownMermaid
sequenceDiagram
participant U as User
participant AS as Ad Server
participant CS as Candidate Selector
participant AE as Auction Engine
participant BP as Budget Pacer
participant CT as Click Tracker
participant B as Billing
U->>AS: search query "cheap flights"
AS->>CS: find eligible campaigns
CS-->>AS: 800 candidates
AS->>BP: filter budget-exhausted
BP-->>AS: 600 candidates (200 paused)
AS->>AE: run auction (600 ads)
AE-->>AS: winner + CPC = $1.23
AS-->>U: render ad + click URL
U->>CT: clicks ad (302 redirect)
CT-->>U: redirect to landing page
CT->>B: click event (validated)
B->>B: charge $1.23 to campaign
Candidate selection pipeline. The keyword index is an inverted index mapping keywords to campaign IDs. For a query "cheap flights to NYC," the system:
- Tokenize + expand: "cheap flights to nyc" + synonyms ("affordable," "budget") + broad match ("flights nyc," "airfare new york")
- Index lookup: union of all matching campaign IDs from the inverted index (~5000 candidates)
- Targeting filter: remove campaigns that don't match user's geo, device, time-of-day, audience segment (~1000 remain)
- Budget filter: remove campaigns with exhausted daily budget or paused by advertiser (~800 remain)
- Score + rank: compute Ad Rank for remaining candidates; take top K for auction slots
This pipeline must complete in < 50 ms (leaving 50 ms for rendering). The inverted index is sharded by keyword hash and replicated across data centers for low-latency reads.
Interview answer
"Each query triggers a sub-100ms auction. Candidate selection narrows 10M campaigns to ~1000 via keyword index + targeting. Auction ranks by Ad Rank = bid x quality score (pCTR x relevance x landing quality). Winner pays second-highest price (Vickrey). Budget pacer uses probabilistic throttling to spread daily spend evenly. Clicks tracked via redirect URL, validated in real-time (IP cap, bot filter) and batch ML. Invalid clicks refunded. Display ads use RTB: fan-out to DSPs in parallel, same second-price auction."