Est. 2026Philosophy · Technology · WisdomLinkedIn ↗

PaddySpeaks

Where ancient wisdom meets the architecture of tomorrow

← All Articles
technology · interview prep

Ads Data Engineering Interview Prep

A senior / L5 handbook for the rounds that actually decide an ads DE loop — auctions, attribution, identity & consent, ads-specific SQL, platform-level stacks, measurement & visualization, plus ten full mock interview scenarios.

Part X0

Primer · Ads 101

Before Raj · before the clock starts · read this if any word below is unfamiliar

Ads 101 · the 60-second world.

The interview is a story about ads data. If the data world is new, the story will be hard to follow. This primer gives you the three things you need before Act 1: how the money actually moves, the five companies that shape the entire industry, and the eleven specific ways ads data rots in production. Fifteen minutes. Then Raj.

The 60-second primer · how a single ad actually happens
1
Advertiser wants attention
A brand — say, a shoe company — wants humans who match a profile (runners, age 25–40, recently searched "marathon") to see a 15-second video of its new shoe. It sets a budget (e.g. $50,000) and a target (e.g. "cost-per-click ≤ $1.20").
2
Platform finds the slot
A viewer opens Instagram / YouTube / Amazon / Netflix / Apple News. The platform has an empty ad slot. It knows a lot about the viewer (account history, device, recent activity). It needs to fill the slot in ~100 milliseconds, or the viewer scrolls past.
3
Auction clears
Every advertiser whose targeting matches submits a bid. The platform runs a second-price auction: highest bidder wins, pays one cent above the second-highest bid. The shoe company wins at $1.14. The slot is filled. A row is written to a Kafka topic.
4
Viewer does (or doesn't do) something
The viewer sees the ad — that's an impression. Maybe they click it — a click. Maybe they buy the shoes two days later — a conversion. Three tables, three grain levels, three attribution challenges. The platform's job is to stitch these together without losing rows and without double-counting.
5
Money moves, eventually
The shoe company is invoiced — usually monthly, for all winning bids that month. Refunds and IVT credits happen 30–60 days later. Partner SSPs report their cut 14 days late. By the time Finance closes the month, the pipeline's live number and Finance's closed number disagree by 5–15% — and that disagreement is the job.
Ads data engineering is the plumbing that makes all five beats observable, reconcilable, and auditable — while the volumes are two billion per day and the advertisers are asking where their dollars went.
The full business lifecycle · seven stages · how an ad actually runs end-to-end
The 60-second primer above is the shape. This is the full lap. Every ad that has ever been served passes through these seven stages — whether it's a $5M Super Bowl buy on Netflix or a $0.17 Sponsored Product click on Amazon. Know what happens at each stage, what data it produces, and how it breaks. Skim the cards. Read the gotchas.
1
Campaign Creation
Advertiser side · brand manager, agency, or self-serve seller
The advertiser sets up the campaign. Picks an objective (awareness / consideration / conversion / sales), sets a budget ($50K for the month), uploads creative (video, images, copy), defines targeting criteria (runners age 25–40 who searched "marathon"), picks a bid strategy (CPC ≤ $1.20, or "let the platform optimise"). The choice of objective silently drives every later stage — it picks the ranker, the attribution window, even which metrics the advertiser is allowed to see.
By the numbers · 2024
$1.1T
Global ad spend
71%
Is digital
$10M+
Top-advertiser annual budget
$200
Median self-serve daily budget
Objective distribution of new campaigns launched · StreamCo-tier CTV platform
Conversion · 52%
Consideration · 28%
Awareness · 20%
Half of spend is judged on a clean conversion event; the other half is judged on reach, attention, and brand lift — which the data team has to measure without a click.
Data produced
campaign_idad_set_idcreative_idtargeting_specbudget_centsbid_strategyobjective
Tools
Meta Ads Manager · Google Ads · Amazon Ads Console · Xandr Invest · internal self-serve portals
Gotcha · objective-rewrite trap: an advertiser who changes objective mid-flight invalidates the ranker's training signal. Some platforms block the change; others silently reset learning. Know which.
2
Targeting · Matching Ads to Users
Platform side · ad-serving layer · ~10ms budget
A viewer loads a page or app. The platform has ~10 milliseconds to find every active campaign whose targeting matches this viewer. Identity is resolved (logged-in account, cookies, device ID, household stitch for CTV), audience-segment membership is looked up (lookalike / interest / custom / retargeting), frequency caps are applied (has this user seen this ad 5× today?). The result: a candidate pool of maybe 100 ads that could be shown in this slot. Everything else about this impression is downstream of this filter.
The 10-millisecond budget · where the time goes
Identity lookup2ms
Segment match · bloom filter4ms
Freq-cap check2ms
Dispatch + log2ms
At 2B impressions/day × ~100 candidates per request, the platform runs 200 billion targeting lookups per day. A 1ms regression here moves the P99 monetisation.
~100
Candidate ads per slot
2B
Ad requests per day
200B
Targeting lookups / day
0.1%
Requests that time out → no ad served
Data produced
candidate_ad_ids[]viewer_identityaudience_matchesfreq_cap_staterequest_ts
Tools
Redis / in-memory feature stores · identity graph lookups · bloom filters for segment membership · device graph stitching
Gotcha · identity at the 10ms boundary: if the identity lookup times out, the platform must choose between serving a generic ad (worse monetisation) or dropping the slot (lost revenue). Every platform has its own fallback policy; it is almost never documented.
3
The Auction · Selecting the Winner
Platform side · auction engine + ranker · ~40ms budget
Each candidate ad is scored. The standard formula is ad_rank = bid × predicted_action_rate × quality_factor. The ranker is usually a deep-learning model that predicts pCTR and pConvert from hundreds of features (user history, creative embeddings, context). A second-price auction (GSP / VCG variants) clears: highest ad_rank wins, pays just enough to beat the second-highest ad_rank. Loser logs are written too — they are precious for ranker retraining. Privacy budgets weight the final score on iOS / Privacy Sandbox paths.
The auction · a real clearing · one viewer, one slot
rankadvertiserbid× pAction× quality= ad_rankpaid
1Nike · running shoe$2.400.0340.920.0751$1.92
2Asics · marathon gel$2.100.0290.940.0572
3Strava · Premium$1.800.0280.810.0408
4Garmin · watch$3.200.0110.720.0253
5Local gym · signup$0.900.0210.880.0166
Nike wins. Pays $1.92, not $2.40 — just enough to beat Asics's ad_rank of 0.0572 at Nike's quality-adjusted rate. That's the second-price savings ($0.48) that makes advertisers trust the auction.
~40ms
Auction clear time
99%
Slots filled at full reserve
28–40%
Platform take rate
20:1
Loser-log : winner-log volume
Data produced
winning_ad_idclearing_price_centspredicted_action_ratequality_scoreloser_logs[]auction_decision_id
Tools
C++ ranker services · Flink/Dataflow streaming for pCTR calibration · GPU inference clusters · Kafka for auction logs
Gotcha · auction logs are the most privacy-sensitive data in the stack. Every loser contains a user's interest signal. Retention, encryption, and export rules are strict. One leak = regulatory fine + consent-decree.
4
Ad Delivery · Showing the Ad
Ad server + client (browser / app / TV device)
The winning ad is served. The creative loads. A tracking pixel fires the moment it's rendered, reporting "impression served" back to the platform. For video, a VAST/VPAID wrapper tracks quartiles (25% / 50% / 75% / complete). Viewability is measured against the MRC standard — ≥50% of pixels on screen for ≥1 second. A pre-bid IVT filter flags obvious invalid traffic here (datacenter IPs, repeat abusers), stamping is_valid = FALSE on the impression. Everything downstream uses this stamp.
Delivery funnel · 2B ad requests → what actually gets billed
Ad requests arrived
2.00B
100%
Won an auction
1.97B
98.5%
Served to client (post-network)
1.85B
92.4%
Survived pre-bid IVT filter
1.74B
86.8%
MRC-viewable (50% · 1s)
1.25B
62.6%
Billable · clean spend
1.16B
58.1%
42% of ad requests never become revenue. Pre-bid IVT removes ~6%; viewability cuts another ~30%. The "bill-only-on-viewable" standard is why advertisers care so much about MRC certification.
2–8%
Client-vs-server count drift
~6%
Pre-bid IVT flag rate
72%
Industry viewability benchmark
MRC
50% · 1s · the standard
Data produced
impression_idrendered_tsviewable_flagvideo_quartile_eventsis_validspend_cents
Tools
CDN-delivered creatives · pixel + SDK tracking · MRC-certified viewability vendors (DoubleVerify, IAS) · VAST/VPAID for video · HUMAN for pre-bid IVT
Gotcha · client-side pixel vs server-side log disagreement. The pixel fires when the viewer's browser loads it; the server logs when it dispatched. These numbers differ by 2–8% — ad-blockers, network failures, slow pages. Pick one as the source of truth and tell every consumer which.
5
User Engagement · Capturing Interactions
Ad server + client · real-time event stream
Now the viewer does something — or nothing. If they click, a click row is written with the clicked ad_id, user_id, and timestamp. For video: pause, resume, skip, complete all stream back. For CTV there is usually no click — just exposure and completion. For every event, the platform re-resolves identity against the graph, applies consent rules (GDPR, CCPA, iOS ATT), and may drop or aggregate the event to satisfy privacy requirements.
Click-through rate by surface · industry benchmarks 2024
Search (Google)
6.30%
Sponsored Products (Amazon)
4.46%
Social (Meta Reels)
1.31%
Social (Meta Feed)
0.90%
Programmatic Display
0.46%
CTV (Netflix, Hulu)
— no click
Search wins because intent is explicit. CTV does not have the metric at all — you measure exposure, completion, and off-platform conversion. This is why CTV pipelines look different.
Consent-strip rate · % of events that cannot be attribution-joined
Segment width = share of global impressions in that privacy regime. Every percentage here is a row the attribution engine cannot touch at the user level.
Data produced
click_idclick_tsvideo_eventsresolved_identityconsent_stateregion
Tools
Kafka / Kinesis event streams · identity graph (device + household + account) · consent-management platforms (OneTrust, TrustArc) · SKAN framework for iOS
Gotcha · this is where privacy eats the data. 5–15% of EU rows come out consent-stripped and cannot be attribution-joined. 30–50% of iOS conversions land as aggregated SKAN postbacks with no user ID. Attribution rate looks like it's dropping; it is just being legally pre-filtered.
6
Conversion Measurement · Did the Ad Work?
Advertiser's site + platform's attribution engine · hours to weeks later
The viewer eventually does the thing the advertiser paid for — buys the shoes, installs the app, fills the lead form. The advertiser's site fires a pixel (or server-side event via Conversions API / SKAN postback / MMP callback). The platform's attribution engine looks back: is there an impression or click for this user within the attribution window? Under which model — last-click 7-day, data-driven, MTA linear, SKAN probabilistic? Credit is assigned. A conversion row is written. Partner SSPs report their slice on 14-day lag.
Attribution accumulation · % of conversions credited, by days-since-impression
Day 1
22%
Day 3
38%
Day 7
58%
Day 14
75%
Day 21
89%
Day 28
95%
Day 60
99%
"Yesterday's revenue" is only 22% finalised at close-of-day. A dashboard that looks the same two weeks later will show 75% — that isn't growth, it's late credit arriving. This is the engine of the Act 2 $0.9M gap.
Attribution models · typical credit allocation for a 4-touch journey
Last-click
000100
First-click
100000
Linear (MTA)
25252525
Time-decay
10152550
Data-driven
18221248
Same four touches, five very different credit splits. Switching attribution model can move an advertiser's reported ROAS by 30%+ overnight without a single real-world change.
Data produced
conversion_idevent_timeattribution_timeattribution_modelattribution_windowcredited_usdidentity_graph_version
Tools
Pixel + CAPI · SKAdNetwork postbacks · MMPs (AppsFlyer, Adjust, Branch) · internal attribution engines · Iceberg tables for bi-temporal storage
Gotcha · attribution is retroactive. A conversion today may credit an impression 27 days ago. The number of conversions for "yesterday" keeps growing for 28 days. Without bi-temporal storage (event_time + attribution_time) you cannot reproduce last week's dashboard and the CFO's question "what did we tell the board on the 30th" becomes unanswerable.
7
Reporting & Optimization Loop · Closing the Circle
Advertiser + platform + Finance · every minute, every month
The conversion row flows downstream. The advertiser dashboard updates in near-real-time (ROAS, CPA, impressions, spend). The platform's ranker is retrained every 15–60 minutes on the fresh conversions — today's conversions become tomorrow's predicted-action-rate features. Finance reconciles the ad ledger at month-end, posting refunds, IVT credits, FX revaluations, and partner-postback adjustments. The advertiser adjusts bids, pauses weak creatives, launches new ones. Every output of Stage 7 is an input of Stage 1 next round.
Feedback-loop latency · how fast each consumer sees the truth
Advertiser dashboard
1–5 min
Operational alerts (ROAS guardrails)
5–10 min
Ranker online retrain
15–60 min
Advertiser bid auto-adjust
1–6 hours
Partner SSP postback (Xandr etc.)
14 days
Finance month-end close
30 days
IVT vendor forensics credit
45–60 days
Seven consumers of the same data. Latency ranges over five orders of magnitude — from minutes to months. The platform's job is to make all seven agree, at their own cadences, on the same underlying truth.
15 min
Ranker retrain frequency
14 d
Partner postback lag
30 d
Finance close
1,200×
Ratio: slowest ÷ fastest loop
Data produced
dashboard_materializationsranker_feature_updatesledger_rowsadvertiser_bid_changescampaign_pause_events
Tools
dbt semantic layer · Druid / Pinot for real-time slice · Snowflake / BigQuery for Finance · Airflow / Maestro for monthly close · online learning pipelines for the ranker
Gotcha · the loop is where every kind of drift compounds. Yesterday's conversion becomes tomorrow's audience signal. A bug anywhere in Stages 4–6 feeds back into Stage 1 and makes the next round slightly worse. This is why the Act 2 landmine — mislabelling a dashboard column "Revenue" — ships the bug forward into every future campaign, for every future quarter, until someone reads this article.
Stage 7 → Stage 1 · the loop never stops
Today's conversion is tomorrow's audience. Today's ranker update is tomorrow's auction. Today's Finance reconciliation is tomorrow's invoice. The seven stages are not a line — they are a flywheel, and the flywheel spins 24/7, 260M times per day at StreamCo scale. Data engineering is the grease that keeps it from seizing.
Now you know the business. Every interview question, every corruption vector, every metric-vs-dimension debate — all of it is a story about one of these seven stages misbehaving. The Five Giants below differ in which stages they dominate. The eleven corruption vectors below are one-per-stage pathologies. Keep the seven stages in mind as you read the rest.
The five giants · 90% of the world's ad dollars flow through these
Meta
$135B · 2023 ads revenue
Inventory
Facebook feed, Instagram feed, Reels, Stories, Messenger. ~3B daily active humans.
Model
Auction on predicted action (click, conversion, video view). Deep-learning ranker picks the ad most likely to produce the action the advertiser wants.
Identity
Logged-in account → social graph → pixel/SDK on advertiser sites (the "Meta Pixel"). Identity is the product.
The unique wrinkle
After Apple's 2021 ATT policy, iOS attribution collapsed. Meta rebuilt on Aggregated Event Measurement — aggregated, delayed, noisier. Revenue dipped, then recovered as the modelling caught up.
Google
$237B · 2023 ads revenue
Inventory
Search (keyword auction), YouTube (video pre/mid-roll), Display Network (AdSense widgets on publisher sites), Google Shopping.
Model
The canonical keyword auction. Advertisers bid on search terms ("running shoes"). Quality Score weights bid × predicted CTR. This is the model every textbook is written about.
Identity
Google account, third-party cookies (deprecating), device ID, Floodlight pixel on advertiser sites. Privacy Sandbox is the post-cookie replacement.
The unique wrinkle
Search ads monetize intent (user explicitly typed "running shoes"), which is why CPCs are 10–50× higher than display. Measurement is almost trivial — the user clicked, you know.
Amazon
$47B · 2023 ads revenue
Inventory
Sponsored Products (top of search results on amazon.com), Sponsored Brands, Sponsored Display (retargeting), Amazon DSP (off-Amazon buys), Twitch, Fire TV, Prime Video ads.
Model
Keyword auction + purchase-intent signals. "User who bought a yoga mat" is a segment nobody else has. Brands pay premium for it.
Identity
Amazon account + purchase history. Cleanest identity graph in the industry because Amazon owns the conversion — the purchase itself.
The unique wrinkle
Attribution is deterministic. A shoe ad shown at 2pm → shoe bought at 2:45pm on Amazon → credited cleanly. No SDK, no pixel, no 7-day window guess.
Netflix · StreamCo
~$2B · launched Nov 2022
Inventory
Pre-roll + mid-roll in streaming content on the ad-supported tier. Limited, premium, sold by programmatic + direct deals.
Model
CPM-based (cost per thousand impressions), not CPC. TV-style reach and frequency is the buyer's mental model — because most buyers are TV-trained.
Identity
Netflix account → household → measured via Microsoft Xandr (the ad partner). No third-party cookies needed because Netflix already has the viewer.
The unique wrinkle
No click. A CTV ad cannot be clicked on most devices — attribution is by exposure plus off-platform conversion (did the viewer buy shoes in the 7 days after seeing the ad on their TV?). This is the hardest measurement problem in the industry right now. It is the problem this article's interview is about.
Apple
~$7B · fastest-growing
Inventory
App Store Search Ads (sponsored app rankings when a user searches the App Store), Apple News ads, Apple Stocks. Small footprint, extremely high intent.
Model
Keyword auction on app-store search terms. Bid per install. Very targeted, very expensive — a finance app install from Search Ads can cost $5–20.
Identity
SKAdNetwork. Apple's own attribution framework — advertiser gets an install confirmation but no user-level identity. Privacy-first by construction.
The unique wrinkle
Apple's ATT policy kneecapped every other mobile ad platform while they grew their own. The house always wins. Measurement is deliberately noisy (delayed postbacks, privacy thresholds) — the engineering challenge is doing statistics on the little you're allowed to know.
The five giants differ in inventory, identity model, and how they count money. They agree on exactly one thing: the hardest part is not the auction. The hardest part is measuring what happened after. Everything in the next fifteen acts is about that.
What corrupts ads data · eleven specific failure modes, one card each
The reason this job is hard is not scale. At 2B rows/day a well-designed pipeline is boring. The reason it's hard is that eleven different forces push the numbers off the truth simultaneously, and every one of them has its own team, its own lag, and its own idea of what the right answer is.
01
IVT · Invalid Traffic
Bots, click farms, datacenter traffic, repeat-cap breaches. Not humans. Up to 20% of raw impression volume at some placements. Industry-standard term: "IVT" (MRC glossary).
Caught in two waves: pre-bid filters catch ~94% (the is_valid=FALSE column); post-impression forensics by a vendor like DoubleVerify or HUMAN catches the rest — that's the ivt_credits_usd column on the Finance ledger that lags 30 days.
02
Attribution lag
Conversions do not happen at the same instant as the impression. Typical windows: 1-day click, 7-day click, 28-day view. Partner SSPs report 14 days late.
Means today's dashboard of "yesterday's revenue" is incomplete; it will keep growing for 28 days. The fix is bi-temporal storage — event_time + attribution_time — so every historical number is reproducible.
03
Identity drift
A user appears on phone, laptop, TV, in logged-out state, with three cookies and two anonymous IDs. The identity graph stitches these into one person. When the graph version changes, the stitch re-runs and revenue moves.
An identity-graph upgrade can shift $5M of attributed revenue overnight — that's the Act 5 incident. Pin the graph version on every output.
04
Revenue definition mismatch
One word — revenue — names four different numbers: billed, spend, attributed, settled. Every consumer (Finance, Marketing, Advertisers, Sellers) uses a different one without knowing they are.
This is the Act 2 landmine. The fix is labels-as-schema: every metric has an audience tag and a definition header. No default "revenue" table.
05
FX and currency
Advertisers pay in local currency. Reporting is in USD. The FX rate at event-time and the rate at invoice-time differ. Emerging markets swing ±5% in a month.
Pipeline has fx_rate_to_usd snapshotted at event time — reproducible. Finance applies an end-of-month revaluation — fx_revaluation_usd. Both are correct; they answer different questions.
06
Counting discrepancies
Client-side counts ("this pixel fired") vs server-side counts ("we billed this one") drift by 2–8%. Viewability threshold (MRC = 50% of pixels on screen for 1 second) removes another slice.
Every downstream table has to pick which count it trusts, and name it. A dashboard that mixes the two is a time bomb on a one-quarter fuse.
07
Clock skew
Kafka event-time (when the ad server said it happened) vs processing-time (when the row landed in the warehouse) can differ by seconds, occasionally minutes. Ad servers in different regions have different clocks.
Freq-capping logic fails around the hour boundary if the cap is 5-per-hour and two servers disagree on what "this hour" means. Watermarks + tolerances.
08
Dedup failures
Kafka is at-least-once by default. Consumer restarts replay the last 10 minutes. The same impression can be written twice.
Exactly-once is defence-in-depth: idempotent keys at the consumer + UPSERT at the warehouse. Appendix drill #6 is exactly this problem.
09
Dimension lag
An advertiser renames campaign "Spring 2025" to "Spring 2025 — V2" at 10:00. The fact table has the old campaign_id; the dim table has the new name. Dashboards straddle.
Slowly-changing dimensions (SCD-2) with effective dates. Dashboards must query as-of a timestamp to stay consistent with a snapshot.
11
Postback fatigue · SKAdNetwork and friends
Apple's SKAdNetwork and Android's Privacy Sandbox deliver attribution without user IDs — aggregated, delayed, noisy, rate-limited to a handful of conversion values per campaign. Postbacks can arrive days late or never arrive at all.
Mobile-app attribution is built on this. The DE stack has to absorb the noise at ingest and not let it leak into the "why is my number different" conversation at close.
Eleven sources of drift. Every scenario in the interview is one of them in a specific costume — and the Act 5 Black-Friday incident is identity drift + attribution lag layered on top of each other. If you can name the eleven cold, the interview is mostly pattern-match. If you can't, the interview is a cold swim.
Where $100 of ad spend actually ends up · industry-typical shares
Platform take
$32
SSP / exchange
$13
DSP fee
$8
Data fee
$5
IVT / waste
$7
Publisher payout
$35
A programmatic open-web buy; walled-garden (Meta, Google) collapses the middle layers and the publisher cut is higher. The $7 IVT slice is the biggest slice that is nominally free to reclaim — which is why Finance and Partnerships care about IVT-credit reconciliation more than any other line item in the ledger. That's why the Act 2 reconciliation exists.
Acronyms you'll see in the interview · memorise before Raj
IVTInvalid traffic. Bots + datacenter + repeat abuse. Target: ≤3%.
CPMCost per mille. Price per 1,000 impressions. CTV default.
CPCCost per click. Search and social default.
CPACost per action. Advertiser's conversion goal.
CTRClick-through rate. Clicks ÷ impressions.
ROASReturn on ad spend. Attributed revenue ÷ ad spend.
DSPDemand-side platform. Buys inventory for advertisers.
SSPSupply-side platform. Sells publisher inventory.
DMP / CDPData / customer data platform. Audience storage.
MMPMobile measurement partner (AppsFlyer, Adjust).
MTAMulti-touch attribution. Credit split across touchpoints.
LTVLifetime value. Predicted revenue per acquired user.
SKANSKAdNetwork. Apple's privacy-first attribution.
CTVConnected TV. Streaming ads. Netflix's problem.
GAMGoogle Ad Manager. Publisher ad server.
MRCMedia Rating Council. Viewability standard (50%/1s).
You are now ready

Now the room.

You know how a single ad happens. You know which five companies wrote the rules. You know the eleven ways the data lies. That is what the interview assumes you walked in with.

Raj is across the desk. The Slack storm is already three minutes old. Scroll.

The Brief is next. Then Act 1 opens at 00:00 on the interview clock.

↑ Back to top

Part X1

Five Rooms · By Company

Five rooms · five interviewers · one shape of round

The same round · different walls.

The main article runs Raj at StreamCo because you can only be in one room at a time. If you are interviewing at Meta or Google or Amazon or Apple instead, the opening ninety seconds rhyme but do not repeat. The interviewer is different. The Slack storm is different. The first question names a pain that is specific to that company's stack, model, and business. The shape of the test is identical.

Room 1
Meta
Priya · Staff DE · Ads Measurement · 8 yrs in Menlo Park
$135B ads · iOS-ATT era · 3B DAUs · Conversions API + Pixel
#incidents-ads-meas · 09:51 AM PT
CMO (Fortune-100 advertiser): Our iOS ROAS fell 40% overnight. We've paused $4M/day of spend. AEM postbacks from yesterday haven't arrived yet. Either your platform is broken or your measurement is lying. Pick one.
Warm-up · 4 questions before the hard one
Q1 · soft · 30s
"Tell me about a measurement bug you owned before. Two minutes. What broke, what you'd do differently."
Q2 · vocabulary · 30s
"What is Aggregated Event Measurement, and what specific problem does it solve that the Pixel doesn't?"
Q3 · mechanism · 60s
"Conversions API and the Pixel both report the same purchase. How do you dedupe? What field does the join key need to carry?"
Q4 · light scenario · 90s
"An advertiser's reported iOS conversions drop 5% after an iOS minor update. Name three non-bug reasons before you call engineering."
Q5 · the hard one · what Priya actually escalates to
"Advertiser is pulling $4M/day until we explain this. AEM is delayed up to 72 hours by design. How do you tell the advertiser, right now, whether the drop is real or a postback-delay artefact — without waiting for the postbacks to settle?"
Cares about
Aggregated Event Measurement (AEM), privacy budget, Conversions API ↔ Pixel reconciliation, probabilistic modelling on top of deterministic loss.
Landmine
Assuming user-level attribution still exists on iOS. It does not. Senior answer models the postback-delay distribution and gives the advertiser a range with a confidence interval, not a point number.
If you interview here, study
AEM campaign-tiering rules, SKAN vs AEM differences, Conversions API deduplication, how Meta models "dark conversions" that postbacks dropped.
Room 2
Google
Marcus · L6 TL · Search Ads Quality · 11 yrs · Mountain View
$237B ads · keyword auction · Quality Score · Privacy Sandbox migration live
#serving-ads-quality · 09:42 AM PT
Top-20 advertiser account manager: Our flagship keyword had Quality Score 8 for six quarters. Yesterday it dropped to 3. CPC doubled. Client is on the phone demanding an explanation. They will churn by end of week.
Warm-up · 4 questions before the hard one
Q1 · soft · 30s
"Tell me about an auction-quality bug you've debugged. What did you instrument, what did you find?"
Q2 · vocabulary · 30s
"What are the three ingredients of Quality Score? Don't define the ranker — just the inputs you can actually measure."
Q3 · mechanism · 60s
"Walk me through GSP vs VCG with a three-bidder example. Which one is incentive-compatible, which one is simpler, which one do we run, why?"
Q4 · light scenario · 90s
"An advertiser's CPC jumped 15% week-over-week on the same keyword, same bid. Name three causes before you suspect a Quality Score change."
Q5 · the hard one · what Marcus actually escalates to
"Quality Score moved from 8 to 3 overnight on a high-spend keyword. The ranker is a black box to you — you don't have read access to the model. Walk me through how you debug this, and what you tell the advertiser in ninety minutes."
Cares about
Quality Score input instrumentation (CTR, landing-page experience, ad relevance), Floodlight ↔ GA4 stitching, keyword auction internals, Privacy Sandbox topic API migration.
Landmine
Trying to reverse-engineer the ranker. You cannot. The senior move is to instrument the inputs — predicted CTR drift, landing-page speed regression, competing bidders entering the auction — and reason about which input changed.
If you interview here, study
GSP vs VCG auction math, Quality Score components, Privacy Sandbox (Topics / Protected Audience), Floodlight tag semantics, how auction-time logs and offline logs disagree.
Room 3
Amazon
Chen · Principal · Sponsored Ads DE · 9 yrs · Seattle
$47B ads · Retail + Sponsored Products + Amazon DSP · dual books (Ads + Retail)
#finance-retail-ads · 10:04 AM PT · Prime Day + 3
Retail Finance: Our GAAP close is $1.8M light on ad revenue for July 16. The Ads ledger says $47.2M; we're reconciling against $45.4M in Retail. Two different trustworthy pipelines. One is lying. Find it before 5 PM Pacific.
Warm-up · 4 questions before the hard one
Q1 · soft · 30s
"Tell me about an Amazon-scale data reconciliation you owned. Which two systems? Where was the seam?"
Q2 · vocabulary · 30s
"What's ACOS? What's TACOS? When does a Seller care about each one? Two sentences each."
Q3 · mechanism · 60s
"How does Sponsored Products attribution differ from Amazon DSP attribution? Which window, which identity, which surface?"
Q4 · light scenario · 90s
"Retail and Ads books disagree by $50K on a single small advertiser. Name three places to look before you escalate."
Q5 · the hard one · what Chen actually escalates to
"Sponsored Products auction logs are clean on our side. Retail's order-level pipeline is clean on their side. They disagree by $1.8M on a $47M day. The join is somewhere in the middle. Walk me through how you find the seam, and what you tell Retail Finance by end of day."
Cares about
Impression-to-purchase attribution joins across Sponsored + Retail, customer-ID space reconciliation (they don't quite align), Prime Day scale (10× normal volume), dual-book accounting between Ads and Retail orgs.
Landmine
Assuming customer_id on the ads side equals customer_id on the retail side. Historically they had different namespaces; the lookup table is the seam, and it lags 6 hours on Prime Day.
If you interview here, study
Sponsored Products auction, Amazon DSP vs sponsored differences, Retail ↔ Ads identity reconciliation, SP-API, purchase-attribution windows (14-day default), Prime Day runbook design.
Room 4 · The main article
Netflix · StreamCo
Raj · Staff Engineer · Ads Platform · 6 yrs · Los Gatos
260M subs · ad tier Nov 2022 · CTV without the click · Finance-ledger reconciliation
#incidents-ads-de · 09:47 AM PT
CFO Amelia: Q3 deck shows ad revenue at $9.1M for September. Our dashboard says $8.2M. Which is right? I need an answer by 10:30.
Warm-up · 4 questions before the hard one
Q1 · soft · 30s
"Tell me about the last ads pipeline you owned. Two minutes."
Q2 · vocabulary · 30s
"Quick — difference between an impression, a click, and a conversion?"
Q3 · mechanism · 60s
"Walk me through how a second-price auction clears, with a concrete example. Three bidders, do the arithmetic out loud."
Q4 · light scenario · 90s
"An advertiser tells you their CPC went from $1.20 to $1.80 overnight. Name three reasons before you reach for the dashboard."
Q5 · the hard one · what Raj actually escalates to
"You're the tech lead. It's 09:47. The director will DM you in thirty seconds. What do you say, and why?"
Cares about
CTV-without-click attribution, partner-SSP 14-day postback lag, Finance ledger reconciliation, four definitions of revenue (billed / spend / attributed / settled), Xandr integration.
Landmine
Treating "revenue" as one number. At a streaming company with a new ad tier, Finance, Marketing, Advertisers, and Partnerships each compute it differently. The label is the schema.
This is the one the article walks through
Scroll to The Brief → Act 1 for the full ninety seconds. Raj's round is the canonical one, so everything downstream — the architecture, the 2:17 AM incident, the CFO walkthrough — plays out in full against this specific setup.
Room 5
Apple
Leena · IC5 · Ads Measurement Science · 7 yrs · Cupertino
~$7B ads · App Store Search · SKAdNetwork · privacy-first by construction
#ads-measurement-escalations · 09:33 AM PT
Top-tier fintech app developer: SKAN attribution for our Search Ads install campaign dropped from 94% last quarter to 41% this month. We spent $12M in June and we think you've broken postbacks. Either fix it or we're shifting budget to Android.
Warm-up · 4 questions before the hard one
Q1 · soft · 30s
"What's the smartest measurement constraint you've worked with? Tell me about it as a design problem, not a complaint."
Q2 · vocabulary · 30s
"What is SKAdNetwork? Two sentences. Why did Apple build it instead of using the IDFA we already had?"
Q3 · mechanism · 60s
"Walk me through how a single SKAN postback gets generated and delivered. What encodes the conversion value? What randomises the timing?"
Q4 · light scenario · 90s
"A developer reports postback rate dropped 5% after an iOS minor release. Name three legitimate reasons before you accept 'bug'."
Q5 · the hard one · what Leena actually escalates to
"Advertiser says SKAN postbacks are broken. You've verified the pipeline is healthy. The 41% number is real; the 94% number was also real six months ago. What happened in between, and how do you explain it to the developer in writing by end of day?"
Cares about
SKAdNetwork internals, conversion-value encoding (6 bits, tiered), privacy-threshold suppression for small campaigns, postback randomization windows, how iOS version drift changes the effective postback rate.
Landmine
Treating SKAN like deterministic attribution. It isn't — privacy thresholds suppress postbacks when campaign cohorts are small, and iOS version fragmentation shifts the rate every quarter. The "drop" is usually the denominator changing, not the numerator.
If you interview here, study
SKAN 2.0 → 4.0 evolution, conversion-value bit packing, privacy thresholds (CR = crowd anonymity), AdServices framework, ATT policy edge cases, how Apple measures without measuring.
What does not change across all five rooms
The shape of the opening
A live incident. Real numbers. A clock. A named executive expecting a reply in under an hour. The round is never a whiteboard exercise pretending to be a puzzle — it is the on-call experience compressed into forty-five minutes, with the interviewer watching which instinct fires.
What is being scored
Refusals. Not outputs. The candidate who types SQL in the first thirty seconds loses. The candidate who names four definitions before touching a query wins. Priya, Marcus, Chen, Raj, Leena — all five are grading the same five decisions in five different costumes.
The landmine family
Every room's landmine is one of the eleven drift sources from the Primer — identity drift at Amazon (retail ↔ ads ID space), attribution lag at Meta (AEM postback delay), definition mismatch at Netflix (four revenues), black-box trust at Google (Quality Score inputs), threshold suppression at Apple (SKAN crowd anonymity). Same eleven, different dress code.
The right first sentence
Never a number. Always a frame. "Before I answer, here's what each system is measuring" — at every company. The thirty-second pause to establish what the question means buys the quarter at Netflix, the advertiser at Meta, the trust at Google, the reconciliation at Amazon, and the developer at Apple.
What they are actually hiring for
Judgement under real-time pressure when the data is lying and four audiences need four different true answers. The acronyms differ (AEM vs SKAN vs Quality Score vs ledger reconciliation) — the instinct being tested is the same.
The comparison table · how the five platforms actually differ
If you only remember one page from this article before an interview, make it this one. Metrics, dimensions, attribution window, and the canonical use case — side by side.
Company Primary metrics Primary dimensions Attribution model Canonical use cases
MetaFeed / Reels / Stories
CPM
CPC
CPA
ROAS
Video 3s / 75% / thru
CTR
On-Meta vs off-Meta conv
Campaign → Ad set → Ad
Objective (awareness / traffic / conversions / sales)
Placement (Feed / Reels / Stories / Messenger)
Audience (Lookalike / Interest / Custom)
Device (iOS vs Android — matters massively post-ATT)
7-day click + 1-day view default. iOS uses AEM — aggregated, up to 72h delay, privacy-budget-gated. Android still deterministic. CAPI (server) + Pixel (client) must be deduped via event_id. DTC conversion optimization, app installs, Lookalike scaling, retargeting via Custom Audiences, brand awareness on Reels, sales via Advantage+ Shopping.
GoogleSearch / YouTube / Display
Quality Score
Ad Rank
CPC
CPA
ROAS
Impression share
Abs Top IS
View-through conv
Campaign → Ad group → Ad / Keyword
Match type (exact / phrase / broad)
Network (Search / Display / YouTube / Shopping)
Device / Geography / Time-of-day
Audience (In-market / Affinity / Custom segments)
Data-Driven Attribution (DDA) default as of 2023, 30-day click window for Search, 1-day view for Display/Video, cross-device via Google account sign-in. Privacy Sandbox migrating Display away from 3P cookies. High-intent Search for bottom-funnel, YouTube pre/mid-roll for awareness, Display retargeting, Performance Max for auto-optimized full-funnel, Shopping for e-comm.
AmazonSponsored / DSP / Retail
ACOS
TACOS
New-to-brand %
SP impressions
SB clicks
DSP CPM
Retail attributed sales
Campaign → Ad group → Keyword / ASIN
Placement (Top of search / Product page / Rest)
Match type (broad / phrase / exact / auto)
Customer segment (Brand-new-to-brand / Repeat)
Category / Subcategory / ASIN
14-day purchase window (Sponsored Products default), 7-day view / 14-day click (DSP), deterministic because Amazon owns the purchase event. Retail ↔ Ads ID reconciliation is the seam — 6-hour lag on Prime Day. SKU-level sales via Sponsored Products, brand discovery via Sponsored Brands, off-Amazon retargeting via DSP, Prime Day amplification, Fire TV / Twitch CTV, subscribe-and-save retention.
Netflix · StreamCoCTV · this article's focus
CPM
Completion rate
Reach
Frequency
Unique households
Attributed off-platform conv
On-screen time
Campaign → Creative
Household segment (subscriber tier / geo / genre affinity)
Content genre / specific title
Daypart (prime / late night / weekend)
Device type (Smart TV / Mobile / Tablet)
Exposure → off-platform conversion via Xandr postbacks. Nielsen-panel reach & frequency. Partner SSP postbacks on 14-day lag. No click attribution — most CTV surfaces can't be clicked. Identity is the Netflix account + household stitch. Direct-to-advertiser CPM buys, programmatic CTV via Xandr exchange, brand-lift studies, TV-style reach & frequency planning, content-adjacency targeting (show-level).
AppleApp Store · SKAN
SKAN installs
Conversion value distribution
TTR (tap-through)
Search Ads impressions
CPP (cost per purchase)
Campaign (SKAN limit: 100 per app)
Keyword (Search Ads)
Creative set / Asset
Country / region (privacy threshold varies by scale)
Time-of-day / iOS version
SKAdNetwork postbacks, delayed 24–48h + randomization window. Conversion value encoded in 6 bits = 64 values. Privacy thresholds suppress postbacks when cohort sub-populations fall below the crowd-anonymity floor. Never user-level. App install campaigns (primary), Search Ads for App Store discovery, Apple News / Stocks for awareness, privacy-first attribution for regulated industries (finance / health).
Reading guide: a CTV buyer trained on Netflix cannot interview at Meta without re-learning what "conversion" means. A Google search engineer cannot interview at Apple without re-learning what "attribution" means. The rows look similar. They are not similar.
The twist library · how interviewers bend the same question
Every interviewer has a shape of question and three ways to bend it mid-round to test whether you're reasoning or reciting. These are the specific bends each company uses. Recognise the bend and you recognise what is actually being scored.
Meta
— with Priya
T1
The source-of-truth squeeze — "Pixel says 1,000 conversions. CAPI says 1,200. Ads Manager says 1,080. Which is the real number, and what do you tell the advertiser?" Testing: whether you know CAPI/Pixel dedup keys and AM's blended model.
T2
The ATT-cliff reframe — "Conversion count dropped 40% after iOS 14.5 shipped. Real drop or measurement drop? Give me a number for each in the next five minutes." Testing: whether you can model AEM postback-delay vs real-behaviour delta.
T3
The privacy-budget trap — "A small advertiser with $500/day spend sees half the postbacks they used to. Same campaign, same audience. What changed?" Testing: whether you know AEM's campaign-tier limits and privacy thresholds.
Google
— with Marcus
T1
The Quality Score black box — "QS dropped 8 → 3. CTR didn't change. Landing page didn't change. Find the third input that moved." Testing: whether you know Quality Score has three inputs (CTR, ad relevance, LP experience) and can instrument each, without trying to reverse-engineer the ranker.
T2
The DDA-vs-last-click switch — "We migrated to Data-Driven Attribution last week. ROAS is up 15%. Is the business actually better, or is it an attribution-window artefact?" Testing: whether you can separate measurement change from real lift.
T3
The Performance Max opacity — "PMax is eating 40% of our budget and we can't see which placements are converting. Design a query that gives the advertiser their attribution transparency back." Testing: auction-log joins against what Google will and won't expose at the API level.
Amazon
— with Chen
T1
The ACOS-vs-TACOS flip — "ACOS is down from 28% to 19% — great. But TACOS went up from 11% to 14%. Explain the difference to a Seller who doesn't know the acronyms." Testing: whether you can separate ad-efficiency from total-business-efficiency in plain English.
T2
The Retail-reconciliation gap — "Ads ledger: $47M. Retail books: $45.4M. Same day. Walk me through the three most likely sources of the $1.6M gap, and how you'd test each." Testing: whether you know the Retail↔Ads ID seam, the 14-day purchase-window boundary, and return-behaviour timing.
T3
The new-to-brand drift — "NTB % dropped from 34% to 22% over four weeks. Audience saturation, measurement drift, or real?" Testing: whether you can reason about cohort dynamics vs identity-graph changes.
Netflix · StreamCo
— with Raj (the main article)
T1
The CFO-vs-dashboard gap — "$9.1M vs $8.2M. Which is right?" Testing: whether you name four definitions (billed / spend / attributed / settled) before touching SQL.
T2
The CTV-without-click reframe — "An advertiser wants to know their ROAS on a CTV campaign. No one clicked. Define ROAS for them." Testing: whether you know exposure-to-off-platform-conversion attribution via Xandr + household ID.
T3
The 2:17 AM up-alert — "ROAS jumped 18% overnight. Bug, fraud, or correction? Pick one in thirty minutes." Testing: whether you can distinguish a measurement-change from a revert-able bug.
Apple
— with Leena
T1
The postback-rate phantom — "Postback rate fell from 94% to 41%. The pipeline is green. The developer thinks it's a bug." Testing: whether you know privacy thresholds, iOS version drift, and cohort-size effects on SKAN postback rates.
T2
The 6-bit saturation — "Your conversion value is encoded in 6 bits. LTV has 200 meaningful buckets. Design a mapping that preserves signal without leaking identity." Testing: whether you can think about lossy encoding as a design problem, not a bug.
T3
The MMP-vs-SKAN disagreement — "AppsFlyer reports 10,000 installs. SKAN reports 7,200. Advertiser wants to know which is the real number." Testing: whether you understand SKAN's deliberate suppression and MMPs' modelled-attribution gap-fill.
Across fifteen twists, the bend is always one of three: (1) "two pipelines disagree, reconcile," (2) "a number moved, tell me if it's real," (3) "explain this privacy/opacity constraint in a sentence." If you can pattern-match the bend in under ten seconds, the specific column names become substitutable and the round becomes the same round everywhere.
The tech platforms · what each does · who uses it where
The ads stack is built from ~12 pieces of infrastructure. Every ad company uses some subset — often the same subset — but wires them differently based on scale, latency, and privacy constraints. This is the cheat-sheet.
Platform Layer · what it does Meta Google Amazon Netflix Apple
KafkaLinkedIn OSS Ingest · durable, replayable event log. Topics per event type. Schema registry in front. Internal fork; trillions of events/day in the ads path. Produces bid logs, impressions, conversions. Rarely — Google runs proprietary pub-sub (Colossus-backed). Kafka appears at acquired units (Looker, Fitbit). MSK (managed Kafka) for Sponsored Ads auction logs. Kinesis dominates elsewhere in AWS. Primary ingest for ads events. This article's architecture lands on Kafka in Act 3 Layer 5. Internal pub-sub (Apple's own). Kafka not public-facing at scale.
KinesisAWS managed Managed streaming · zero-ops ingest. Better than Kafka on small teams; worse on replay >7 days. Default everywhere except where replay >7d is required. Firehose → S3 for cheap retention. Considered; rejected because 28-day attribution replay needs Kafka's topic retention.
FlinkAlibaba-originated Stream processing · stateful. Windowed aggregations, pattern detection, streaming joins with exactly-once. Stream processing for attribution + fraud detection. Heavy Flink shop. Dataflow (proprietary, Apache Beam OSS) is Google's Flink-equivalent. Same category, different implementation. Flink Kinesis Data Analytics for near-real-time pacing and IVT pre-bid flagging. Flink on Kubernetes for streaming identity-graph resolution + ROAS guardrails. Internal stream processor; Flink not public. Postback batching happens in batch, not stream.
SparkDatabricks stewardship Batch + structured streaming · large-scale ETL, ML feature pipelines, reprocessing. PyTorch on Spark for ranking-model feature generation. Used throughout ads ETL + ranker training. Dremel/BigQuery supplant for ad-hoc. EMR Spark for nightly reconciliation + backfills. Retail-scale. Iceberg-on-Spark for the silver/gold batch layer. Attribution backfills after graph upgrades. Internal; SKAN postback aggregation is Spark-equivalent.
IcebergNetflix OSS Table format · ACID on a data lake. Schema evolution, time-travel, hidden partitioning, snapshot isolation. Growing — being evaluated as the warehouse-lake format across ads. BigQuery is the warehouse; Iceberg appears at BQ-external-table adoption sites. Iceberg-on-Glue for Sponsored Ads fact tables. Schema evolution without downtime is the killer feature. Origin shop. Iceberg was born here. Every gold table in this article's architecture is Iceberg. Internal columnar format; not Iceberg.
Delta LakeDatabricks OSS Table format · ACID like Iceberg, tighter Databricks coupling, slightly different partition model. Some adoption on Databricks-hosted workloads. Not Retail-primary.
HudiUber OSS Table format · optimized for upserts and late-arriving data. Strong at CDC ingestion. Considered for Finance ledger CDC; lost to Iceberg for consistency with rest of stack.
BigQueryGoogle proprietary Serverless warehouse · petabyte SQL, minute-to-minute billing, external table federation. Primary analytics engine across Ads. Dremel is the guts.
SnowflakeSaaS warehouse Warehouse · multi-cloud, separation of storage and compute, zero-copy clones. Retail Finance reporting; not the ads primary. Ads leans on Redshift + Iceberg. BI + exec dashboards. Ads core pipelines are Iceberg + Spark; Snowflake is the consumer tier.
Presto / TrinoMeta OSS Query engine · federated SQL across warehouses and lakes. Interactive analytics. Origin shop. Presto was built at Meta. Primary interactive SQL. Trino/Athena analogues live alongside BigQuery. Athena (managed Trino) on S3. Interactive ad-hoc on raw logs. Trino on Kubernetes for ad-hoc SQL on Iceberg tables.
dbtSaaS transform Transform layer · SQL-native DAG, tests, lineage, metric YAMLs, CI for transformations. Internal analogue (not public dbt). Meta has its own SQL-DAG tooling. Internal tooling. dbt appears in partner-team adoption. Retail Finance on dbt; ads analytics also. Core ads pipelines are custom. Primary semantic layer in this article's architecture (Act 3 Layer 2). Metric YAMLs + CI tests.
AirflowAirbnb OSS Orchestration · scheduled DAGs for batch jobs, backfills, dependency management. Internal scheduler (Dataswarm). Airflow in peripheral teams. Composer (managed Airflow) on GCP; also internal schedulers. MWAA for batch ETL orchestration. Step Functions for event-driven pipelines. Maestro — Netflix-built orchestrator, public in 2024. Replaces Airflow at scale. Internal scheduler.
Druid / PinotMetamarkets / LinkedIn OSS OLAP · low-latency slice-and-dice on high-cardinality time-series. Dashboards over billions of rows. Presto + internal columnar. Pinot appears in adjacent teams. Proprietary. Druid for real-time advertiser dashboards. Pinot under evaluation. Druid for real-time ROAS dashboards and advertiser self-serve. Heavy Druid shop.
Xandr · DV360Ad-stack vendors Programmatic SSP/DSP stack · ad serving, auction-running, postbacks for partner attribution. Owns full stack in-house (Meta Ads Manager). Owns full stack (Google Ad Manager + DV360). Owns full stack (Amazon DSP). Xandr partnership is the backbone of CTV monetization. Postbacks are the 14-day lag in Act 2. Apple Search Ads is proprietary; SKAdNetwork is the SSP-equivalent framework.
Reading guide: three patterns jump out. (1) Everybody uses stream + batch side-by-side; there is no "one engine to rule them all." (2) OSS tools win at operations-heavy, slow-evolving layers (Kafka, Iceberg, Spark). Proprietary wins at query-path where latency margins matter (BigQuery, Dremel, Xandr). (3) The company that built a tool is almost always its heaviest user (Presto at Meta, Iceberg at Netflix, Dataflow at Google). If you are interviewing somewhere, skim the row for that tool — it tells you what the interviewer will default to when they ask "how would you implement this."
How to use the rest of the article

Whichever company you are interviewing at, the main arc (Brief + Acts 1–7 + Appendix) plays at StreamCo because that is the one you can sit inside for forty-five minutes end-to-end. Read it all. Then:

  • If you're interviewing at Meta — re-read Act 5 (Everything Breaks) substituting AEM postback delay for identity graph upgrade. The shape of the debug is identical; the specific column is different.
  • If you're interviewing at Google — Act 3 (Architecture) is still canonical; the four revenue tables become four Quality Score input streams. Act 4 (Defense) around auction-log vs offline-log reconciliation lands identically.
  • If you're interviewing at Amazon — Act 2 (reconciliation) is the money act. Substitute Retail ledger for Finance ledger, purchase-attribution window for partner-postback lag. Everything else holds.
  • If you're interviewing at Netflix · StreamCo — read straight through. You are the main character.
  • If you're interviewing at Apple — Act 5 is still a measurement-change, not a bug — the classification move is the same. Substitute SKAN privacy threshold for identity graph version. The paragraph you write to the developer is longer because the math is harder to explain.

Now keep scrolling. The Brief is next. Raj is waiting.

↑ Back to top

Part X2

Question Bank · soft → hard

The bank · 39 realistic questions · 13 tiers · soft start, hard end · in the order they actually arrive

What they actually ask.

No interviewer opens with "design a SKAN postback aggregator." They start soft — your last pipeline, your hardest metric — and build up. By question twenty they're testing real debugging instincts. By question thirty they're testing how you talk to a CFO with incomplete data. This bank mirrors that arc. Read it top to bottom; it is the realistic difficulty gradient you will actually walk through.

1
Ownership · the soft start
Tier · soft · ~5 minutes · they want you talking
  • Tell me about an ads or measurement pipeline you owned end-to-end.
  • What broke in that system, and how did you detect it?
  • What was the hardest metric to make trustworthy?
What they're checking: can you talk fluently about your own work without slides? Most candidates pass this. The handful who freeze here lose the round in question one.
2
Core metrics · no fluff
Tier · vocabulary · ~3 minutes · do you know the words
  • What is the difference between an impression, a click, and a conversion?
  • When can impressions increase but revenue stay flat?
  • What does CTR actually hide?
What they're checking: basic vocabulary plus one half-step deeper. The "what does CTR hide" question is the warm-up's first real edge — bot traffic, low-intent surfaces, viewability not measured.
3
Attribution · your sweet spot
Tier · concept · ~5 minutes · the most common follow-up zone
  • How does attribution differ between onsite ads vs external ads?
  • When would you trust last-click attribution — and when not?
  • What are failure modes of attribution systems?
What they're checking: can you reason about credit assignment as a design choice? Senior answers always name the attribution window, the model, and the identity assumption — three knobs, not one.
4
Streaming / TV context · Netflix-style
Tier · domain · ~5 minutes · CTV-specific only if it's relevant
  • How do you define an "impression" on a TV where clicks don't exist?
  • If a user watches an ad on TV and converts on mobile, how would you measure it?
  • What makes TV ads harder to measure than mobile ads?
What they're checking: do you grasp that CTV breaks the click-as-signal model? Senior answers move to exposure-plus-off-platform-conversion + household-level identity, not click-replacement.
5
Cross-device reality
Tier · concept · ~5 minutes · identity is now the constraint
  • How would you attribute a conversion across devices without a deterministic ID?
  • What are tradeoffs between accuracy and coverage in cross-device attribution?
  • How do you avoid double-counting conversions?
What they're checking: can you reason about probabilistic vs deterministic identity tradeoffs? Senior answers name household-level signals, login-state stitching, and idempotency keys — without claiming any of them is perfect.
6
Mechanism understanding · simple, not academic
Tier · mechanism · ~5 minutes · arithmetic out loud
  • Explain how a second-price auction works in ads.
  • Why might CPC increase even if demand is unchanged?
  • What inputs influence auction outcomes in practice?
What they're checking: do you actually understand the mechanism, or did you read about it? Doing the arithmetic with a concrete three-bidder example — out loud — separates "shipped" from "studied."
7
Light scenarios · thinking, not panic
Tier · scenario · ~6 minutes · ambiguity, three plausible answers
  • Impressions up 30%, clicks flat — what could explain it?
  • Conversions drop 10% after a UI change — what are three non-bug reasons?
  • Revenue steady, but CTR drops — why?
What they're checking: can you reason under ambiguity without panicking? Three plausible answers is the floor. "It's a bug" with no alternative is a fail. "Targeting expanded to a lower-CTR audience" — that's the pattern.
8
Real debugging · the real filter
Tier · scenario · ~10 minutes · this is where the round actually filters
  • CPC jumps from $1.20 → $1.80 overnight. What do you check first?
  • Spend drops 40% but traffic is stable — where could the issue be?
  • Two dashboards show different revenue for the same campaign — how do you debug?
What they're checking: do you have an actual debug path, or do you guess? The senior move is the same in all three: name the system that changed, instrument the inputs, isolate by date / segment / surface — before reaching for SQL.
9
Data integrity · pipelines
Tier · engineering · ~8 minutes · the system-design adjacency
  • Where can data loss happen in an ads pipeline?
  • How do you design idempotent pipelines for event ingestion?
  • What's the hardest part of reconciling ads data across systems?
What they're checking: do you know what reliability actually costs? Senior answers name specific failure modes (consumer restart, partition lag, dedup-key collision) and the corresponding mitigations — not generic "use Kafka."
10
Executive communication · don't mess this up
Tier · communication · ~5 minutes · the staff-level wedge
  • "Why did ROAS drop?" — answer in 30 seconds.
  • "Can we trust this metric?" — what do you say?
  • "Are we losing money right now?" — how do you respond with incomplete data?
What they're checking: can you switch register from engineering to executive without losing precision? The right answer is short, names the thing, names the uncertainty, and ends with a recommendation. Engineering vocabulary loses this round.
11
Amazon-style · money reconciliation
Tier · domain · ~6 minutes · only if relevant to the role
  • Why might ads revenue not match billing data?
  • Explain ACOS vs TACOS and when each matters.
  • Where would a $100K discrepancy hide in ads vs retail systems?
What they're checking: can you reason about dual-book accounting? The seam is almost always identity-space mismatch (Ads customer_id ≠ Retail customer_id) plus attribution-window edges (returns posted after the 14-day window).
12
Meta-style · measurement realism
Tier · domain · ~6 minutes · only if relevant to the role
  • What breaks attribution even without bugs?
  • Why might iOS traffic behave differently from Android?
  • How would you validate a new measurement system?
What they're checking: do you understand that measurement is itself a system that drifts? Senior answers reach for holdout tests, conversion-lift studies, and cohort-stability checks — not point-comparisons.
13
Edge thinking · only if they push
Tier · senior signal · ~5 minutes · the bonus round
  • How do you detect fraud or invalid traffic?
  • What happens if event timestamps are delayed or out of order?
  • How would you backfill missing conversion data?
What they're checking: not whether you have the answer — whether you have the instinct. Two-source verification for IVT, watermarks + tolerances for late events, append-only versioning for backfills. Cite the principle, not the tool.
How to use this bank

Read top to bottom once. Pick the three weakest tiers. Practice answering them out loud, with a timer. Tier 1 in two minutes; tier 13 in five. If a question makes you reach for a definition, that's the one to drill — not the ones you can recite.

The interview will not ask all 39. It will ask 4–6, drawn from tiers 1–8 most of the time, with one tier-10 communication question dropped in around minute 50. The acts you scroll into next are the round playing out at full speed against this bank.

Now scroll. The Brief is next.

↑ Back to top

Overview

The Brief · 00:00 · problem drops

The Brief · before you scroll any further

You are interviewing.

The rest of this article is not an article. It is the next forty-five minutes of your life.

The interview loop · one day
1
System Design · Ads Platform
45 min · Raj · live
2
SQL Deep Dive
45 min · later today
3
Coding · Python
45 min · later today
4
Behavioral · Staff bar
45 min · later today
5
Bar raiser · Director
30 min · later today
You are about to start Round 1. Every section below, from this point to the appendix, is inside that round. The clock does not reset. Raj does not leave the room. The Slack storm at 09:47 does not retract.
The setup · 90 seconds
You are
A senior data engineer candidate, 8 years in. Two ad-tech roles before this. Currently a senior DE at a streaming company half this size. This loop is for Staff, not Senior.
The company
StreamCo. 260M subscribers. 190+ countries. $17B ARR. Ad-supported tier is eighteen months old and on track to be the single largest growth lever of the next five years.
Your interviewer
Raj. Staff engineer. Ads platform team. Four rounds in the chair in the last two weeks. He has seen enough candidates freeze on this round to know the freeze is the thing being scored.
The clock
Starts at 09:47 when his Slack lights up. Ends at 10:32 when the director's calendar pings him. You have what's in between. Treat every section header as a timestamp, not a chapter title.
You are being watched. That is the whole round.
Raj is not evaluating whether you can build pipelines. Three weeks of onboarding will teach you that. He is evaluating whether, when the CFO's dashboard and Finance's close disagree by $0.9M at 9:47 in the morning on the last day of the quarter, you will know which number to reach for first and why. The rest of this article is him finding out.
What's actually on the table · eight things
  1. The next thirty seconds. A live reply to the director before he re-reads the CFO's message.
  2. The reconciliation. Four definitions of revenue, one query, one paragraph, one number.
  3. The architecture. A platform that makes this morning impossible by Friday of next week.
  4. The defense. Six volleys from Raj, each one a failure pattern he has watched candidates lose on.
  5. A page at 02:17 AM. ROAS up 18% overnight. Finance sees it at 9:00.
  6. A meeting at 08:30 AM the next day. The CFO. Two dashboards. Seventeen minutes.
  7. The last four minutes of the round. Raj's meta-question — the one that's actually scored.
  8. The appendix. Seven drills. If you can't solve them cold, none of the above carries in the room.
Rules for reading
  • Read linearly. The sections are hours of one day, not topics. Skipping to SQL is skipping to minute 35 of a 45-minute conversation.
  • Read every "What you say" card out loud. If you can't land the line at conversational pace, you can't land it in the room.
  • Solve the drills before reading the answers. The appendix is not a reference. It is a pretest.
  • Every sidebar marked "The Landmine" is a specific way candidates lose this round. They do not lose on knowledge. They lose on instincts. The landmines are the instincts.
Running Incident · do not lose track of this
09:47 AM
Revenue gap · $0.9M
Dashboard: $8.2M · Finance: $9.1M · same month, same label
00:00You see the gap
00:30Director DMs
43:00CFO on Zoom
53:00Finance close starts
youare here
This incident is the spine of the whole article. Every act below is a different minute inside it. You cannot skip any section and still answer it.
⚠ FREEZE MOMENT · read once · do not keep scrolling yet

Thirty minutes. CFO on Zoom in twenty-five. What do you check first?

Before you keep reading — answer out loud. Not the "right" answer. Your answer. Commit to it. Write it in the margin if you have to. What do you type into the director's DM in the next thirty seconds?

Instinct A"Dashboard is wrong, Finance is right — I'll re-run the numbers."Cost if wrong: two quarters of trust.
Instinct B"Let me reconcile and get back to you."Cost: CFO asks who, when, and gets a shrug.
Instinct C"Give me until 10:40. I'll reconcile and come back with a recommendation, not a number."Cost: the morning. But you have the quarter.

Pick one. Now. Then scroll. Act 1 grades you.

The first thing that happens · 09:47 AM

Raj's screen: #incidents-ads-de. The CFO's message is already three minutes old. She wants an answer by 10:30. You have not drawn a single box yet. Scroll.

→ Act 1 opens in thirty seconds. The clock is already running.

↑ Back to top

Part 00

Round 0 + 1 · 0–5 min · Drop + Clarify

Round 0 → Round 1 · minutes 0–5 · the drop · the interviewer is watching whether you panic or structure
You just read the Brief. Raj is across the desk. His laptop is open. Yours is not yet. The Slack storm at 09:47 has already hit — but Raj does not show it yet. He warms you up first. Most interviewers do.
Warm-up ramp · the first 90 seconds · soft blows before the hard one
Raj does not open with the brutal question. No senior interviewer does. He starts on the easiest question he can ask and watches whether you can talk fluently about your own work, the basic vocabulary, and one straightforward scenario. Four questions. Each one a step harder than the last. Then the drop.
Q1soft · 30 seconds
Raj: "Tell me about the last ads pipeline you owned. Two minutes."
Grading: can you talk fluently about your own work without a slide deck? Can you name what you built, what broke, and what you'd do differently? Most candidates pass this. The handful who freeze here lose the whole round in question one.
Q2vocabulary · 30 seconds
Raj: "Quick — what's the difference between an impression, a click, and a conversion?"
Grading: do you know the words? An impression is the ad being rendered. A click is the user choosing to act on it. A conversion is the action that closes the loop on the advertiser's goal. Anyone with a year in ads gets this. If you don't, the round is over politely.
Q3mechanism · 60 seconds
Raj: "Walk me through how a second-price auction clears, with a concrete example."
Grading: "Three bidders at $2.40, $2.10, $1.80. Highest ad_rank wins, pays $0.01 more than the runner-up's ad_rank at the winner's quality factor — say $1.92. The winner saves $0.48; the auction is incentive-compatible." Can you do the arithmetic out loud? This separates "I've read about ads" from "I've shipped ads."
Q4light scenario · 90 seconds
Raj: "An advertiser tells you their CPC went from $1.20 to $1.80 overnight. Name three reasons."
Grading: a competing advertiser entered the auction at higher bids; the advertiser's Quality Score dropped (CTR fell, landing-page slowed); a budget-pacing change pushed them into more expensive auction windows. Can you reason about ambiguity without panicking? Three plausible answers are the floor; one and "it's a bug" is a fail.
You are now ninety seconds in. Raj has heard you talk, watched you do arithmetic, watched you reason about a real-world fork. You are warmed up. He is calibrated. The marker pushes across the desk.
⏱ Q5 · 01:30 · the warm-up ends · the hard one lands now
"Design a system to report ads revenue across platforms."
— and then silence. Different room than thirty seconds ago.

That's it. No scale number. No latency target. No source-of-truth hint. No mention of IVT, attribution, identity, Finance, or the CFO. Just the sentence above, and a marker being pushed across the desk toward you.

Raj is not waiting for an architecture diagram. He is watching the next five seconds. Do you panic, or do you structure?

What is actually being graded in the first five seconds
❌ Panic
Start drawing Kafka on the left. Ask "what tech stack should I use?" Name three ad platforms. Each of these ends the round on minute three — Raj just hasn't told you yet.
✓ Structure
Name four questions out loud before you draw anything. What does "revenue" mean here? What latency? What scale? What's the source of truth? Then commit to asking the most-blocking one first.
The entire article you are reading below is the structured version of what happens when you don't panic. Round 1 starts when you open your mouth.

You are in Round 1.

❌ Most candidates fail here
by answering the question as asked. "Which is right, $8.2M or $9.1M?" has no correct answer because the numbers mean different things. Candidates who commit to one and then defend it in the next four minutes lose the round in minute one without knowing it.

The interviewer is Raj — Staff Engineer, ads platform team at StreamCo. He has been in the chair on your side of this conversation four times in the past two weeks. He has seen enough candidates freeze on this round to know that the freeze is the thing being scored.

The system-design brief · 45-minute round

Design a unified ads metrics platform at StreamCo scale. 260M subscribers. ~2B impressions/day. Four downstream consumers: Finance, Marketing, Advertisers, Sellers. Four weeks to ship v1.

You start sketching. Kafka on the left. Three minutes pass. Then Raj shares his screen.
#incidents-ads-de · 09:47 AM · live, not a hypothetical
CFO Amelia
Q3 deck shows ad revenue at $9.1M for September. Our dashboard says $8.2M. Which is right? I need an answer by 10:30.
CMO Darren
Three campaigns auto-paused. ROAS dashboard tanked overnight.
Partnerships · Priya
Two content partners say their royalty numbers jumped 14%. They want a call.
The incident, visually
Dashboard says
$8.2M
ads_warehouse, Sep total
vs$0.9M gap
Finance says
$9.1M
Q3 exec deck total
Two numbers, same label, same month. Pick one in thirty seconds and you lose the quarter.
What Raj asks

"You're the tech lead. It's 09:47. The director will DM you in thirty seconds. What do you say, and why?"

🛑 STOP SCROLLING · commit an answer before you read A, B, or C

In fifteen seconds, say your answer out loud. Not the "right" answer. Your answer — what you would actually type into the director's DM right now, with Raj watching, with the clock at 00:33.

No peeking. Commit. Then read A / B / C and find out whether the interview would have continued.

fifteen seconds · count them

If you could not produce a sentence — that is the signal. The three candidates Raj has seen freeze on this exact second lost the round here, before A / B / C existed. The round is scored on whether the reply came, not whether it was perfect.

A"Both are right. They measure different things."

Probably correct. Also premature — no receipts. Reads as hedging to a CFO. Cost: low if right, catastrophic if slightly wrong.

B"Give me until 10:40. I'll reconcile and come back with a recommendation."

Buys 53 minutes. Commits to a specific deliverable. Authority through specificity. Cost: the whole morning goes to this. Accepted.

C"Dashboard is the source of truth. $8.2M."

Fast, confident, wrong. If Finance arrives independently at $9.1M in the close, the data team is gone for two quarters. Cost: career-ending in the medium term.

What you say

"Until 10:40. There are at least four definitions of 'ad revenue' at StreamCo scale. I reconcile, I come back with one number and one recommendation. That's the first thirty seconds. The rest depends on which definition the dashboard is actually computing."

Raj nods once. Timer reads 4:31. He says:

Raj: "Walk me through the reconciliation. Four definitions, the query, the paragraph to the director at 10:40. Go."

↑ Back to top

Part 01

Round 1 · 5–15 min · Clarify or Fail

Round 1 · minutes 5–15 · clarify or fail · most candidates lose the interview here
Raj just said: "Walk me through the reconciliation. Four definitions, the query, the paragraph to the director at 10:40. Go." The whiteboard behind you still has $8.2M vs $9.1M from ninety seconds ago. Your laptop is open in your lap. Keep going.
Skip this act and you cannot name what revenue means at StreamCo · reconcile the $0.9M gap · write the 10:34 paragraph · answer Raj's first follow-up. The rest of the round stops making sense.

Four numbers, one word.

❌ Most candidates fail here
by opening SQL before opening their mouth. They want to look technical. They start writing SELECT SUM(revenue) FROM ... before asking which revenue, which table, which window. The query they ship reconciles nothing because it was answering a question nobody asked. Clarification is the round. Query is scaffolding.

Raj is watching. Forty minutes of round left. He asked for the reconciliation. You produce it out loud, in SQL, while naming the four revenues StreamCo actually runs on — because at this scale they do not collapse into one.

You begin. Speaking to Raj, keyboard in your lap, you say:

You: "At StreamCo scale there are four numbers that get called 'revenue' on ad dashboards. They sit in four systems, produced by four different pipelines, and they don't agree because they shouldn't. I'll name all four before I touch the query. Otherwise the query answers the wrong question."

Raj nods. You draw four boxes on the sketch pad you have shared to your screen.

Billed revenue
Owner: Finance
What advertisers have been invoiced for. Produced by the billing system — at StreamCo, the Salesforce ledger. Lags reality by the billing cycle — typically a month. Excludes anything invoicing hasn't swept yet.
Updates: month-end + 3-day settlement.
Spend revenue
Owner: Ads pipeline
Sum of auction-clearing prices × served impressions, in the advertiser's contracted currency, converted to USD at the delivery-time FX. Produced near-real-time by the impression pipeline. Knows about impressions the billing system doesn't yet know about. Doesn't know about refunds or chargebacks.
Updates: streaming, ~5-minute lag.
Attributed revenue
Owner: Ads measurement
Revenue credited to specific campaigns under an attribution model — last-click 7-day, last-touch, MTA linear, whatever the advertiser's contract calls for. Depends on the window, the touch model, and the identity graph. Almost always differs from spend revenue because it excludes unattributable spend and absorbs post-attribution adjustments.
Updates: nightly batch for 28-day; streaming for 24-hour.
Settled revenue
Owner: Finance
The number Finance closes the month on. Post-refund, post-chargeback, post-IVT-recovery credits, post-partner-attribution-lag, post-currency-revaluation. Sourced from the ledger after all adjustments have posted. This is what the 10-Q filing is built from.
Updates: monthly, with quarter-end revisions.

You turn back to Raj and continue:

You: "The CFO's $9.1M is almost certainly settled revenue. The dashboard's $8.2M — without seeing the query, my bet is spend revenue, because that's what the ads pipeline produces natively. Which means the gap is roughly $900K of stuff that happens after spend — partner attribution that posts on a 14-day lag, IVT credits Finance catches that we flag later, refunds, and currency revaluation. I can't know which until I run the reconciliation. Let me show you the query."

Raj pulls up the shared editor on his side. You start typing.

The $0.9M Gap, Decomposed
Spend revenue (pipeline)
$8.21M
+ Partner attribution lag
+$0.74M
14-day postback lag from affiliate networks
+ Finance IVT-credit delta
+$0.16M
Finance catches IVT the pre-bid filter doesn't
+ FX revaluation
+$0.02M
Impression-time vs settlement-time FX rates
= Settled revenue (Finance)
$9.13M
Residual after all four adjustments: −$0.01M — within rounding. No pipeline bug. Close the ticket.

The query finishes at 10:21. You screenshot the result. Raj watches you do it without comment. You then write the paragraph the director has been waiting for.

You → Director (10:34): "Both numbers are correct for their purpose. Dashboard's $8.21M is spend revenue — auction-clearing × served impressions. Finance's $9.12M is settled revenue — spend plus $740K partner-attribution that posts to the ledger on a 14-day lag, plus a $160K IVT-credit delta, plus $20K FX revaluation. For the board prep: use $9.12M and label it ad-supported revenue · settled basis · September 2025. I'll follow up this afternoon with a proposal to put the definition on every dashboard so we don't have this conversation again next month."

You close the editor. You look up at Raj. The timer reads 21:14.

Raj reads the paragraph twice. He says:

Raj: "Good. Send the director that paragraph. Actually, send it — walk me through what you'd type in the Slack DM." You paste the paragraph into a stub window. Raj reads it again. He nods once. Then: "OK. The director has her answer. Amelia has her number. The partners still need a call this afternoon; assume that's the account manager's problem, not yours today. So the incident, for our purposes, is closed. We are back in the brief."

He changes his screen back to the whiteboard you had drawn 17 minutes ago. The half-finished architecture is still there — Kafka at the left, a few scattered boxes, the words attribution underlined twice. He looks at it. He looks back at you.

Raj: "You have thirty-four minutes. Build me the platform that prevents this morning from happening again. I want to hear the same rigor you just used on the reconciliation applied to the design. Start wherever you want, but I'll tell you now — your first move is not going to be Kafka. It's going to be one sentence. Take a breath and then tell me what that sentence is."

The whiteboard is yours. The incident is your brief. The clock keeps running.

Parallel rooms · the same moment, four other interviewers
If you are not interviewing at Netflix · StreamCo, the reconciliation question lands in your chair differently. Same shape of test — different columns, different ledgers, different words.
Meta · Priya
"Ads Manager shows the advertiser $2.1M. Our Conversions API log says $2.4M. Pixel reports $1.9M. Finance books at $2.3M. Which is the number you put in the QBR, and why?"
Google · Marcus
"Sales Ops pulled revenue by Quality Score tier for the 11:00 forecast call. It does not match what the ads-revenue pipeline is emitting. Reconcile. You have thirty minutes and the Sales VP joins at 10:50."
Amazon · Chen
"The Ads ledger posted $47.2M for Q2. Retail ledger posted $45.4M. Seven figures of difference, same quarter, same customers. Before you write SQL — what are the four definitions of 'ad-attributed sale' we could be measuring?"
Apple · Leena
"The advertiser's MMP (AppsFlyer) reports 10,000 installs attributed to our Search Ads campaign. Our SKAN postbacks show 7,200. The developer thinks we're losing data. Name four reasons the two numbers can legitimately disagree before you accept the 'bug' framing."
Different pipelines. Same first move: name the definitions before you touch the query. Anyone who opens SQL in the first thirty seconds fails the round in all five rooms.

↑ Back to top

Part 02

Round 2 · 15–30 min · Design

Round 2 · minutes 15–30 · first design pass · they are looking for clean thinking, not perfection
You just sent the 10:34 paragraph to the director. On the interview clock it is 21:14. Raj has wiped the board, capped the marker, and slid it over to your side of the desk. He has not said anything. The silence is the brief.
Skip this act and you cannot draw the platform Raj is grading · name the three contracts that prevent the $0.9M gap next month · defend Act 4 · explain to the CFO why Friday's fix is real. The architecture is the receipt for the incident, not a vision document.

Build a platform that makes this morning impossible.

❌ Most candidates fail here
by jumping to tools before naming the enemy. "I'll use Kafka, Flink, Iceberg, dbt, Airflow, Snowflake, Druid..." — fifteen logos on the board in two minutes. Raj is not grading breadth of tool knowledge. He is grading whether your architecture is a receipt for the incident — whether each box traces to a harm the layer prevents. A tool without a harm is decoration.

Clock reads 21:14. Raj wipes the whiteboard. He does not say a word. He caps the marker, sets it down where you can reach it, and leans back. This is the part of the round where most candidates start drawing boxes and arrows. This morning is fresh in your head. You do not reach for the marker yet.

What Raj asks

"Now design the platform so September doesn't happen again. Four weeks. v1. 260M subscribers, ~2B impressions/day, four downstream consumers. I want layers, I want contracts, I want the one thing in your design that kills today's bug. Go."

Before you pick up the marker you say one sentence. It is the sentence that frames every box you are about to draw. You want this sentence in Raj's head before a single rectangle appears on the board, because every architectural decision you make after this is a consequence of it.

What you say

"The enemy is not latency. It is not cost. It is silent label drift between audiences who each think they are reading the same number. Every layer I draw has one job: make that drift impossible, or make it obvious within five minutes."

Now you draw. Not a wiring diagram — a layer stack. Five layers, top to bottom. Each layer does one thing. Each layer has a contract with the layer above and below. You draw them as stacked rectangles.

5
Ingest · bronze
What ships: Kafka topics per source (impressions, clicks, bids, partner-postbacks, finance-ledger-CDC). Schema registry in front of every producer. Raw append-only landing tables with one row per event, never mutated.
Because of this morning: the affiliate-partner postback stream is a first-class source, not a nightly SFTP. 14-day attribution lag becomes visible at ingest, not at close.
Rejected: single "events" firehose with JSON payloads. Schema-on-read is schema-never.
4
Conform · silver
What ships: canonical fact_impression, fact_click, fact_conversion. Currency normalised to USD at event time. IVT flag materialised here. Identity graph join resolved here, once, with lineage columns.
Because of this morning: IVT is marked at silver with a resolution stamp. When Finance's $0.21M credit arrives two days later we can diff against our $0.05M flag and explain the $160K delta in one query.
Rejected: letting every team join identities themselves. That is how you get four identity graphs and four answers.
3
Metrics · gold
What ships: four revenue tables, named exactly. gold.revenue_spend, gold.revenue_attributed, gold.revenue_billed, gold.revenue_settled. Each has a definition header row, a grain contract, and an owning team.
Because of this morning: there is no table called gold.revenue. That name is reserved and will never be created. Anyone who writes SELECT revenue FROM … gets a query-parser error with a link to the definitions page.
Rejected: a single wide "revenue" table with a revenue_type column. Defaults will be chosen. The default becomes the bug.
2
Semantic · contracts
What ships: a semantic layer (dbt + a metrics API) where every dashboard, notebook, and spreadsheet pulls from named metrics — not from raw tables. Every metric has a YAML spec: source table, grain, definition, owner, audience.
Because of this morning: the CFO's dashboard cannot have a column labelled Revenue without a metric binding. The label is the schema now. Enforced by CI.
Rejected: BI-tool-level definitions (Tableau calculations). They are invisible to engineering, untested, and drift in ninety days.
1
Consume · four audiences
What ships: four purpose-built surfaces. Finance gets settled-basis dashboards + a reconciliation workbench. Marketing gets attributed-basis ROAS. Advertisers get spend-basis self-serve + billing export. Sellers get attributed + pacing.
Because of this morning: no audience ever sees a number unless its audience tag matches the metric's audience tag. Finance cannot accidentally pull a spend-basis number.
Rejected: one unified dashboard with role-based filters. That is how the September bug shipped.

The stack is on the board. Raj has not interrupted. You have four minutes of air, so you spend them on the three contracts that make the stack real. Without them the boxes are furniture.

Decision fork · streaming vs batch at the ingest layer
Raj will push on this before Act 4 begins. Which way do you default, and what breaks?
→ Full streaming (Kafka + Flink)
What breaks: cost triples. Backfills get hard (you can replay Kafka but reprocessing six months of attribution on a new graph version melts the Flink cluster). Late-arriving partner postbacks (14-day SSP lag) land after state has already been snapshotted.
Choose when: the consumer surface genuinely needs <5-min freshness (advertiser self-serve ROAS, frequency-cap enforcement, real-time bidding feedback).
→ Batch-default, streaming where needed
What breaks: advertiser dashboards lag 15–60 min. If the advertiser is the one paying, this is a complaint. If Finance is the one paying, this is fine.
Choose when: Finance reconciliation is the primary consumer, late-arriving data is part of the model, and cost efficiency matters more than real-time freshness (this article's default).
The senior answer is almost always batch-default + stream-only where latency is a contract, not a nice-to-have. Candidates who go streaming-first on instinct lose three of the six volleys in Act 4.
Contract 1
Append-only attribution
Every conversion row carries two timestamps: event_time (when it happened) and attribution_time (when we credited it). We never UPDATE. Late postbacks arrive as new rows. Historical dashboards are reproducible at any prior attribution_time.
Kills the class of bug where a Tuesday-morning rerun silently rewrites last Thursday's revenue number.
Contract 2
Definition header on every gold table
Row 0 of every gold.* table is a definition row: metric_name, grain, currency_basis, attribution_model, audience, owner_team, ticket_for_changes. Schema migration to change any of these requires a signed change request from the owning team.
Kills the class of bug where a label on a dashboard calcifies into institutional meaning that nobody can challenge.
Contract 3
Identity graph contract test
CI-enforced test: the identity graph version used to produce today's attributed revenue is pinned in the gold table's metadata. If the graph changes, a backfill job runs and emits a new attribution_time cohort — the old numbers are preserved, the new numbers are labelled.
Kills the class of bug where an identity-graph upgrade silently shifts $5M of revenue between campaigns and nobody can explain why a quarter later.

The SQL shape of the append-only attribution table is the single hardest thing to get right, so you write it on the board:

-- gold.revenue_attributed · append-only, bi-temporal
CREATE TABLE gold.revenue_attributed (
  conversion_id         UUID        NOT NULL,
  advertiser_id         BIGINT      NOT NULL,
  campaign_id           BIGINT      NOT NULL,
  event_time            TIMESTAMP   NOT NULL,  -- when the conversion happened
  attribution_time      TIMESTAMP   NOT NULL,  -- when we credited it
  attribution_model     VARCHAR(32) NOT NULL,  -- last_click_7d, mta_linear, ...
  identity_graph_version VARCHAR(16) NOT NULL, -- pinned for reproducibility
  credited_usd          NUMERIC(18,4) NOT NULL,
  is_superseded         BOOLEAN     NOT NULL DEFAULT FALSE,
  PRIMARY KEY (conversion_id, attribution_time)
)
PARTITION BY RANGE (attribution_time);

-- "what did we show Finance on the 10:37 email on 2025-09-30?"
SELECT SUM(credited_usd)
FROM   gold.revenue_attributed
WHERE  event_time >= '2025-09-01' AND event_time < '2025-10-01'
  AND  attribution_time <= '2025-09-30 10:37:00'
  AND  is_superseded = FALSE;

That last query is the one that makes the platform trustworthy. Any number Finance ever saw is reproducible by plugging the attribution_time ceiling into the same query. "What did we tell you, and when?" becomes a SQL answer instead of a Slack archaeology project.

You → Raj: "Five layers. Three contracts. The one thing in the design that kills today's bug is Contract 2 — no gold table without a definition header, no dashboard without a metric binding, label-is-schema enforced by CI. The rest protects us from the next three versions of today."

Raj picks up the marker. Not to draw. To point.

Raj: "Fine. Now I challenge it. Every layer. Start with ingest. Why Kafka. Why not Kinesis. Why not direct-to-Iceberg. Go."
Parallel rooms · the architecture brief at the other four
Raj asks you to build a platform that prevents this morning. The other four interviewers ask you to build a platform that prevents their morning. The shape of the ask is identical — five layers, contracts, reserved names. The specific prevention target is not.
Meta · Priya
"Design the events pipeline that keeps Conversions API and Pixel deduped at 10B events/day, absorbs AEM postback delay without lying to the advertiser, and gives the privacy team a clean audit line. Four weeks. Draw it."
Google · Marcus
"Design the serving-log ↔ offline-log reconciliation platform for Search. Auction decisions are logged one place, conversions another, no user ID in between on iOS. How does Quality Score ground-truth get built?"
Amazon · Chen
"Design the Retail ↔ Ads attribution bridge. Two identity spaces. Prime Day at 10× scale. Finance sign-off by Friday. Show me the schemas that survive the customer-ID reconciliation lag."
Apple · Leena
"Design the SKAN postback aggregator that produces advertiser-facing install counts from Apple's 6-bit, delayed, rate-limited, privacy-thresholded stream — without making the numbers look fabricated. And make it explainable in a single paragraph to a developer."
Five layers, three contracts, reserved names, CI-enforced label-is-schema. The tactic changes per company. The principles do not.

↑ Back to top

Part 03

Round 3 · 30–40 min · Defend

Round 3 · minutes 30–40 · pressure phase · the interviewer is sharpening the knife
Your architecture has been on the board for four minutes. Raj picked up the marker — not to draw, to point. Nineteen minutes of round left. The next nineteen minutes are him trying to break every layer you just shipped. Stay in the chair.
Skip this act and you cannot tell a tactic (Kafka vs Kinesis) from a principle (append-only attribution) · answer the CFO-Friday trap · hold the line on the wide-table landmine. Candidates who fold on principles under pressure lose the round even when the tactics are right.

Every box gets punched. You defend or you fold.

❌ Most candidates fail here
by defending the tactic instead of the principle. Raj pushes on Kafka; the candidate defends Kafka for ten minutes. Raj pushes on the four tables; the candidate negotiates down to one. Every concession on a principle is a lost point; every concession on a tactic is a signal you can scope. Learn the difference in under forty seconds or the round is scored down before Act 5 begins.

Clock reads 24:03. Raj has the marker. He is not drawing — he is pointing. He will spend the next nineteen minutes trying to break the architecture you just put on the board. Each challenge is a real bet he has watched candidates lose. You have one correct shape of answer for every question: name the alternative, name the trade, name the harm the trade prevents.

There is no clean way to script this. So here's how it actually goes: six volleys, a clock, and a running counter of whether you are converting pressure into signal or leaking it.

24:03
Volley 1 · Ingest
Raj: "Kafka. Why. We're on AWS. Kinesis is one click. You just burned a headcount on cluster ops for a four-week v1."
What you're really being asked
Whether you picked a tool or repeated a resume bullet. Kinesis is the correct default at StreamCo's stack. Kafka has to earn its seat.
You: "Agreed on ops cost. Kinesis for v1, move to MSK only if we hit one of two things: (a) we need >7-day replay to re-run attribution after an identity-graph upgrade — Kinesis caps at 365 days but the replay tooling is painful; (b) Finance's CDC topic needs compacted semantics, which Kinesis doesn't do natively. Both are quarter-two problems. v1 ships on Kinesis."
Converted. Named the v1 pragmatic choice and the two specific triggers for migration.
26:41
Volley 2 · Semantic layer
Raj: "dbt + metrics API. We have a platform team that will build a semantic service. Why are you introducing a second vendor into the stack?"
What you're really being asked
Whether you've thought about build-vs-buy at the organisational layer, not just the tech layer. A homegrown semantic service is a two-year project with three PMs. dbt is week one.
You: "I'd use whatever the platform team ships if it were shipping in four weeks. It isn't — typical semantic-service builds run four to six quarters. dbt gives us metric YAML, CI tests, lineage, and column-level docs on day one. If the platform team lands their service in Q3, we migrate — the dbt YAMLs are the spec they'd need anyway. v1 ships on dbt. I'll write the migration doc so it's not a lock-in."
Converted. Reframed as tactical bridge, named the artefact that makes migration cheap.
29:18
Volley 3 · Four revenue tables
Raj: "Four tables. The warehouse team is already screaming about join costs. Why not one wide gold.revenue with a revenue_type column? Same data, half the joins."
What you're really being asked
Whether you'll fold under a cost argument. This is the landmine from Act 3 dressed up as a warehouse-team complaint. The whole incident is a consequence of the one-wide-table pattern.
You: "The September incident is the wide-table pattern. One column labelled Revenue, four consumers, one default, nine hundred thousand dollars of drift. I'll take the join cost. More: I'll instrument the joins and report compute quarterly so the warehouse team can argue with numbers, not vibes. But I won't merge the tables. That's the one I'd quit over."
Converted. Refused the shortcut, named the concrete cost of folding, offered a measurable counter-commitment.
32:05
Volley 4 · Append-only attribution
Raj: "Append-only. Bi-temporal. Every late postback a new row. Storage is 3x. Query planner chokes on the is_superseded filter. This is over-engineered for a four-week v1."
What you're really being asked
Whether you can scope the contract down without surrendering the principle. The principle is reproducibility of any prior answer. The tactic is negotiable.
You: "Fair on storage — I'll partition by attribution_time month and cold-tier anything >90 days to S3 Glacier IR. Cuts hot-storage 80%. On the planner: we add a materialised view gold.revenue_attributed_current that filters is_superseded = FALSE once; consumers hit the view. Keeps the principle, hides the complexity. The one thing I won't do is UPDATE — that's the bug we're here to prevent."
Converted. Negotiated the tactic, held the principle, named the one line.
35:47
Volley 5 · Buy vs build
Raj: "Mixpanel, Amplitude, Adjust, Kochava — any of them will sell you this stack in a week. Why are we building?"
What you're really being asked
Whether you've actually looked at the vendor landscape or you're build-biased by default. Bad answer: "vendor lock-in." Good answer: the specific capability vendors don't give you.
You: "Three reasons and one caveat. (1) Finance reconciliation needs joins with the ledger; no ad-analytics vendor exposes that surface. (2) At 2B/day we'd be in custom-contract territory — the sticker price triples, and we still build the Finance layer ourselves. (3) Attribution IP becomes the advertiser contract; outsourcing it means renegotiating every contract when the vendor's model changes. Caveat: IVT detection is a buy. Human doesn't add edge; vendor signal beats ours today. So: build metrics, buy IVT, integrate at silver."
Converted. Named specific limits of buy, conceded the one place buy wins, proposed the seam.
39:22
Volley 6 · The trap
Raj: "Last one. Your CFO loved your paragraph this morning. She's the exec sponsor now. She asks you on Friday: can you have all four revenue tables in production by end of next week? What do you say?"
What you're really being asked
This is not an architecture question. It is a stakeholder-management question dressed as one. The wrong answer is "yes." The wrong answer is also "no." The right answer names the cost of yes in her currency, and offers a smaller honest win.
You: "Four tables in one week is technically doable and organisationally reckless. We'd ship schemas without the semantic bindings — every dashboard would migrate on a hot table, and the first one that fails the definition check on Monday morning takes down the CFO's board prep. What I'll commit to: one table — gold.revenue_settled — in production by next Friday, with the semantic binding and the definition header. That's the one you need for the close. The other three follow on a published four-week schedule. You get the number you need in seven days and a plan you can show the board for the rest."
Converted. Refused the scope, offered a specific smaller win, aligned to her actual workflow.
Decision fork · the late-events question
Raj will eventually ask the same question every interviewer asks: "what about late events?" Here is what each answer breaks.
→ Drop late events past T+24h
What breaks: partner SSP postbacks (14-day lag) silently disappear. ~5% of attributed revenue vanishes from settled. Finance reconciliation gap reopens monthly. The Act 2 incident is now a recurring incident.
Choose when: never, in a reconciling system. Acceptable only for fraud-detection paths where late = useless.
→ Append-only bi-temporal storage
What breaks: storage costs ~3× (mitigable via cold-tier partitioning past 90d). Query planner needs a materialised view to hide the is_superseded filter. Backfills become a versioning job, not a rewrite.
Choose when: Finance is a consumer. Reproducibility-of-prior-answers is a requirement. Identity-graph upgrades are foreseeable. (All true here.)
The append-only answer is the only one that survives Act 5. If you choose the drop-late-events path now, you lose the round in fifteen minutes when Raj jumps the clock to 02:17 AM.
The scoreboard · 6 of 6
6
Challenges faced
6
Converted
0
Folded
3
Conceded tactics
0
Conceded principles
19:19
Minutes on the clock
The pattern is the score. Every volley: concede the tactic, hold the principle, name one specific thing. Candidates who fold on the principle to buy time lose the round even if the tactics are right.

Clock reads 43:22. Raj caps the marker. The forty-five-minute round ends in eighty-eight seconds. You think you are done. You are not.

Raj: "Good. Real question now. It's 2:17 AM, three months after your platform ships. PagerDuty wakes you. ROAS for your top-five advertisers is up 18% overnight. Finance is going to see this number at 9:00 AM. Walk me through the next thirty minutes."
Parallel rooms · defense questions at the other four
Raj picks at every layer of your architecture. The other four pick at the layers that matter in their stack. Same drill — concede tactic, hold principle, name the harm the principle prevents.
Meta · Priya
"Why didn't you buy Amplitude or Segment for your event pipeline? AEM is 'good enough' for most advertisers. The build costs us 20 engineers-years. Defend build over buy for the 80% case, not the 20% case."
Google · Marcus
"BigQuery runs us $40M/year at current scale. Why are you not on Dremel internally? And why Spark for ranker features when TensorFlow Extended exists inside the walls? Cost is my first question; correctness is the constraint."
Amazon · Chen
"Why Redshift and not Snowflake? Retail Finance lives on Snowflake. Your dual-book reconciliation is a cross-team join every day. You just added two hops and a service account. Justify or re-draw."
Apple · Leena
"Why Spark for SKAN aggregation instead of an internal Apple-built aggregator? Compliance will ask, privacy-review will ask, and both rooms will require a written answer. Walk me through the approval path before you walk me through the stack."
The tactic challenges differ by stack; the principle challenges are the same five words everywhere — name the alternative, name the trade, name the harm.

↑ Back to top

Part 04

Round 5 · 50–55 min · System Breaks

Round 5 · minutes 50–55 · system breaks · the real senior signal
You are still in the chair. Raj has not left the room. He just said: "It's 2:17 AM, three months after your platform ships. PagerDuty wakes you. ROAS for your top-five advertisers is up 18% overnight. Walk me through the next thirty minutes." Close your eyes. Go back to your bedroom. Narrate it.
Skip this act and you cannot tell a bug from a correction at 02:43 AM · answer Raj's "why didn't you revert" question · explain a measurement change to the CFO the next morning. This is the staff-vs-senior line, and the whole round hinges on which side of it your instinct lands on.

Black Friday. 2:17 AM. ROAS up 18%.

❌ Most candidates fail here
by reverting first. They find root cause in twenty minutes, feel heroic, push the revert, and go back to bed. Raj waits until the interview after and asks: "why did you revert a correction?" A number moving for a reason is not a bug. The staff-vs-senior line runs through this exact decision.

The platform shipped in late October. The CFO's Friday dashboard has run clean for ten weeks. You are asleep. PagerDuty does not care.

PagerDuty · P1 · 02:17:04 PST
[ads-revenue-guardrails] ROAS · top-5 advertisers · +18.3% WoW · threshold +4% · auto-escalate to on-call
Acked by you at 02:17:41 · Laptop open at 02:18:22

The guardrail is your own. You wrote it in week six, after Raj's 2:17 AM question in the interview turned out to be a real hour at this job. The alert fires on WoW ROAS delta >4% across the top-five advertisers. Eighteen percent is not a drift. Eighteen percent is a bug, a miracle, or a fraud. Finance sees this number at 09:00. You have roughly thirty minutes before the director wakes up and sixty before that meeting starts.

The window · 30 minutes to root cause · 43 minutes to a paragraph Finance can read
02:17Page fires
02:22Guardrail drill-down
02:27Narrow to one advertiser
02:32Attribution-time query
02:37Identity graph version diff
02:42Root cause
03:00Paragraph out

The thing about debugging your own platform is that you know exactly where to look. The architecture is not a mystery. The bug will be in exactly one of four places, and the platform was designed to tell you which one.

Four hypotheses · rule them out in order of blast radius
H1
Upstream data drop
An ingest partition died and we're measuring a smaller-but-richer subset. ROAS looks up because the cheap-clicks partition is missing.
Check: bronze.fact_impression row count by hour. Compare to 7-day median. Expected hourly volume at 02:00 PST is ~72M. Actual: 71.4M. Ruled out.
H2
Attribution backfill re-ran
Somebody kicked the attribution job. Late postbacks from last month flooded in with fresh attribution_time stamps. Revenue doubles on the same conversions.
Check: gold.revenue_attributed — count attribution_time BETWEEN '2025-11-29' AND '2025-11-30' where event_time < '2025-11-01'. Expected: <1%. Actual: 0.4%. Ruled out.
H3
Identity graph version change
Identity team shipped a graph upgrade that resolves more cross-device journeys. More attributed conversions per spend dollar = higher ROAS.
Check: identity_graph_version distinct values in the last 24h. Expected: 1. Actual: 2 — v2025.10 flipped to v2025.11 at 20:04 PST. Hot.
H4
Actual advertiser behaviour
Black Friday. Top-five advertisers bid aggressively. Conversion rates actually lifted.
Check: baseline ROAS for those advertisers across last three Black Fridays. Expected lift: +3% to +6%. Observed today: +18.3%. Partially real, insufficient to explain.

H3 is hot. You write the query your architecture made cheap:

-- ROAS decomposed by identity graph version, last 24h
SELECT
  identity_graph_version,
  COUNT(DISTINCT conversion_id)                 AS n_conversions,
  SUM(credited_usd)                             AS attributed_usd,
  SUM(credited_usd) / NULLIF(s.spend_usd, 0)    AS roas
FROM   gold.revenue_attributed r
JOIN   gold.revenue_spend      s USING (advertiser_id)
WHERE  r.advertiser_id IN (/* top 5 */)
  AND  r.attribution_time >= NOW() - INTERVAL '24 hours'
  AND  s.spend_date = CURRENT_DATE
GROUP BY identity_graph_version, s.spend_usd
ORDER BY identity_graph_version;
The two rows · 02:39 AM
identity_graph_versionn_conversionsattributed_usdROAS
v2025.10142,318$1.82M3.04
v2025.11168,904$2.19M3.59
Same spend. 26,586 more conversions credited. +$370K attributed on identical ad dollars. The identity team's graph upgrade is crediting cross-device journeys the old graph could not resolve. ROAS is up because the measurement changed, not because the ads got better.
The 2:43 AM temptation
Ship the fix. Go back to bed.
You have root cause. The obvious move: revert the identity-graph pin, re-run attribution on v2025.10, watch ROAS settle back to +3%, close the page, sleep. Thirty-eight minutes from page to all-clear. Heroic. Also wrong.
The identity-graph upgrade is correct. It crediting more conversions means we were under-attributing for months. Reverting tonight means: (1) advertisers who got tonight's improved numbers see them vanish by 9 AM, (2) Finance has a +$370K to +$0 to +$? oscillation across three days of close, (3) the identity team gets blamed for shipping a fix. You didn't debug a bug. You surfaced a correction. That is a different conversation.

You do not revert. You do not go back to bed. You write the paragraph.

You → Finance + Director + Identity Lead (02:58 AM): "Tonight's ROAS lift for top-five advertisers is +18.3% WoW. Decomposition: ~+4% is real Black Friday lift, ~+14% is a measurement change. The identity team shipped graph v2025.11 at 20:04 PST, which resolves cross-device journeys the previous graph missed — +26,586 conversions credited, +$370K attributed, same spend. The platform is doing the right thing. Recommendation: do not revert. For Finance's 9 AM view, I'm publishing gold.revenue_attributed_v10only as a parallel table so the November close runs on the old graph; December onwards runs on v11, which I'll flag in the board prep as a known measurement improvement worth ~5% reattributed revenue. I'll draft an advertiser-facing note by end of day Friday. The alert was correct to fire — an 18% WoW lift should always wake someone up."
The replies · 03:04 AM to 03:17 AM
Director
Copy. Parallel table works. Walk Amelia through this at 8:30, not 9:00.
Identity Lead
Thank you for not reverting. I thought we were going to lose a quarter on this.
CFO Amelia
This is the second time your team has given me a number I can actually take to the board. Go back to bed.

03:21 AM. You close the laptop. You lie down. You stare at the ceiling for forty minutes. You are still awake at 4:02 when the 4:00 AM batch runs green. You already know the thing the last hour actually tested, and it is not the thing Raj asked.

Raj (much later, in the interview room): "Why didn't you revert?"
Parallel rooms · the 2:17 AM page at the other four
Every ads platform has a 2:17 AM story. Only the surface changes. The question is always the same — is this a bug or a correction?
Meta · Priya
"02:47 AM PT. A deep-learning ranker shift drops CPM 9% across iOS overnight. AEM postbacks are 48 hours behind so you cannot see conversion impact yet. West-coast advertiser-success team wakes up in one hour. Ship a revert or write a paragraph?"
Google · Marcus
"04:33 AM ET. A Floodlight tag rollout to 40% of Display advertisers started double-firing last night. Reported revenue is up 8% in advertiser dashboards. Thirty minutes before the daily exec check-in. Real lift or double-count?"
Amazon · Chen
"Prime Day · 18:30 UTC. DSP impression counts spiked 30% in the last ten minutes but Sponsored Ads auction logs did not. Is it real inventory or a pipeline divergence? Retail Finance asks in fifteen minutes — they have a $3M anomaly flag open already."
Apple · Leena
"03:12 AM PT after an iOS 17.4 release. SKAN postbacks are dropping from previously healthy apps. Is it the OS, a privacy-threshold change, or a bug in our decoder? You have two hours before developers open tickets at scale."
Same five words every time: bug, correction, revert, paragraph, parallel. Every room grades the same classification decision. Reverting a correction is a regression disguised as heroism in all of them.

↑ Back to top

Part 05

Round 6 · 55–60 min · Recovery

Round 6 · minutes 55–60 · recovery · can you survive failure on camera
You are still in the interview chair. Raj asked how you recovered; you're telling him. Five hours after the 02:17 page, you walked into Amelia's office with a laptop and two tabs. The next seventeen minutes are what Raj is actually scoring in this Act — not the code, the conversation.
Skip this act and you cannot narrate a dashboard to an executive · separate advertiser view from CFO view · answer Amelia's "would Q3 have been higher" question. The platform is not done if you still have to be in the meeting to explain the chart.

08:29 AM. The CFO's office. Two dashboards, one conversation.

❌ Most candidates fail here
by answering with engineering. The CFO asks "which number do I trust?" and the candidate explains bi-temporal attribution, graph-version pinning, and the parallel table. All true. All wrong register. The CFO needs a sentence she can read to the board. Engineering is what made the sentence possible; engineering is not what the sentence says.

Five hours after the page, you walk into Amelia's office with a laptop and two browser tabs open. One is the dashboard the platform ships to her every morning. The other is a parallel view you built at 03:40 AM to explain tonight. The meeting is seventeen minutes. You do not open your laptop for the first three.

The scene · 08:29 AM PST

Amelia, the director, and one board-prep analyst. Two screens. Black coffee on the table. No deck. She says: "Walk me through this like I'm the advertiser, not the CFO. I already trust the number — I need to explain it to someone who doesn't."

The meeting is not about the platform. It is about whether the platform can narrate itself. If the dashboard cannot answer "why is tonight's number what it is" without you in the room, the platform is not done, no matter how many tests pass. So you open the laptop. You start with the picture she sees every morning.

The CFO's morning view · conversion funnel

This is what Amelia sees at 07:45 AM, every day, on her phone. One chart, top of the dashboard. Five steps. It answers: how did the whole ad tier perform yesterday, shape-wise. It does not answer a question about dollars — that's the second section below. This chart answers whether the shape of the day was normal.

The funnel · from impression to repurchase

Dashboard · Conversion Funnel

From impression to return purchase — live drop-off

    above benchmark below benchmark on par Tap a tab to switch attribution window.

    The first thing you point at on the screen is the drop-off between two specific steps. Not the overall number. The shape. Shape reveals measurement changes before totals do.

    The tab control at the top is the feature that matters this morning. Switching attribution window from 7-day to 1-day to 28-day shifts the tail of the funnel. Last night's identity-graph change affects 28-day attribution most — by design, more cross-device journeys close within 28 days than within 1. Shape tells the story without dollars.

    ROAS by attribution window · the reason you're here

    Dashboard · Attribution

    Same campaign. Same reality. Five different ROAS numbers.

    1-day click$12.0K · 2.4×
    1-day view$18.0K · 3.6×
    7-day click$23.0K · 4.6×
    7-day click + 1-day view$29.5K · 5.9×
    28-day click$27.0K · 5.4×
    The attribution window is not a number — it's a policy. A senior analyst always answers "which window" before reporting ROAS. Standardize across advertisers; show all three (1d / 7d / 28d) side by side.

    This is the chart you built overnight. It is not in Amelia's normal view. You pulled it forward because the only way to explain tonight's +18% is to show ROAS across attribution windows side by side, with the identity-graph version as a visible cohort.

    You say one sentence while it loads: "The 28-day window is where the graph change shows up. 7-day barely moves. 1-day is flat. The platform is measuring more accurately — not more optimistically." Amelia looks at the 28-day bar for thirty seconds. She asks one question. It is the question every CFO asks eventually, and the platform has to have a clean answer.

    Amelia: "If this graph upgrade had shipped before Q3 close, would the September number we reported to the board have been $9.12M or higher?"

    You were ready for this one at 03:40 AM. You built the parallel table for exactly this question.

    You: "Higher — by approximately $190K. September under v2025.11 closes at $9.31M instead of $9.12M. That $190K is real revenue; we under-attributed it at close. I'd recommend we don't restate the Q3 number — the $9.12M was correct under the measurement we had — but we flag the $190K in the board footnote as an identified improvement, and the advertisers affected get a measurement-update note from the partnerships team. I've drafted both. They're in your inbox."

    She reads the email for ninety seconds. She does not say anything while she reads. When she looks up, the first thing she says is not about the numbers.

    Amelia: "How did you know the shape of what I'd ask?"

    You answer honestly.

    You: "Because the question you asked in September — 'which is right, $8.2M or $9.1M' — was the same shape. In September we could not answer it without a week of reconciliation. This morning we answered it in forty-three minutes because the platform is designed around that specific question. The parallel table, the graph version pin, the up-alert that woke me up at 2:17 — all of that exists because of the 10:37 email on September 30th."

    She nods once. The meeting ends at 08:42. The board prep gets the $9.31M footnote. The advertisers get the note on Tuesday. Nobody mentions the word "incident."

    The advertiser view · install-to-event funnel

    After the CFO meeting you walk down to the advertiser-success team. They have their own dashboard — the one the platform ships to every self-serve advertiser. This is a different surface for a different audience, and the question it answers is different too. Is my campaign working, in my vocabulary.

    The difference between this view and the CFO view is the thing that took you three months to learn at StreamCo: audiences do not translate. Amelia does not want to see "D30 retention by cohort." An advertiser does not want to see "settled revenue, USD basis." The same ad platform produces both surfaces from the same silver tables, bound through the semantic layer, and neither audience is ever confused about which number is which — because the audience tag on the metric is part of the query plan.

    The sales-cycle view · lead funnel + cycle time

    The third surface is for advertisers running lead-generation campaigns. The shape of a lead campaign is not a funnel; it is a funnel with a time axis, because the bottleneck is days-to-close, not percent-converting. A lead campaign with a 40% MQL-to-close that takes 90 days is worse than a 25% MQL-to-close that takes 14 days, and no one-number ROAS captures this.

    This dashboard is the one advertisers screenshot and send to their CFOs. The platform's job is to make sure they don't have to re-label anything before they paste it into the deck — the axis labels, the window, the attribution model are all on the chart itself.

    There is one more dashboard below — the campaign-level drilldown. It is a tool, not a narrative surface. You include it because every advertiser eventually asks "but what about my campaign," and the platform needs a no-ceremony way to answer that.

    Campaign drilldown · the header card

    Campaign · Q2 Retargeting — Urban 25-44

    Period: Apr 1 – Apr 22, 2026

    78
    Performance score
    Spend
    $142,300
    12% WoW
    Impressions
    8.4M
    8% WoW
    Clicks
    124K
    3% WoW
    CTR
    1.47%
    0.12pp WoW
    CPA
    $42
    4% WoW (bad)
    ROAS
    3.8×
    0.3× WoW
    On track
    ROAS up 8.5% vs trailing-28d. Account auto-bid is pacing well on the evening slot.
    Warning
    Frequency cap breaches. 14% of the reached audience is seeing >10 impressions in 7d. Spend is compounding on saturated cohorts.
    Action needed
    Creative fatigue. Hero creative CTR has decayed 22% in 14 days. Schedule rotation Tuesday.

    Single campaign, scorecard at the top, trend below, benchmark row for context. Self-serve advertisers bounce between this and the funnel chart above; that pattern drove the layout decisions.

    Parallel rooms · the next-morning conversation at the other four
    Raj's Act 6 is a seventeen-minute walkthrough with Amelia at StreamCo. The other four interviewers ask about the same kind of meeting — executive, time-boxed, dashboard-centred — with different audiences in different rooms.
    Meta · Priya
    "Next morning. The advertiser pulled half their spend overnight because of your 02:47 page. You have a 20-minute Zoom with them at 09:30. Same Ads Manager they saw yesterday, plus one new chart you built overnight. Walk them through recovery — without saying the word 'bug' or the word 'ranker'."
    Google · Marcus
    "09:30 call with the Top-20 Sales exec. Explain the Floodlight double-fire incident in business terms for a non-engineer, then walk through the three safeguards you added overnight. The exec represents $400M of annual spend. Pick your three."
    Amazon · Chen
    "11:00 sync with Retail Finance, Amazon DSP leadership, and Sponsored Ads PM. Three dashboards. Dual-books reconciliation. Walk all three orgs through the corrected numbers and the dual-book reconciliation addendum your team will publish by Friday."
    Apple · Leena
    "10:00 Developer Relations call. The top-tier fintech developer is on the line with their head of growth. Explain the SKAN postback anomaly, the iOS 17.4 interaction, and the two lines of config that would prevent recurrence — without referencing any Apple-internal infra by name."
    Different audiences, different rooms, one thing in common: the meeting is the deliverable, not the dashboard. You are being graded on the conversation, not the SQL that produced the chart.


    Dashboard · Growth Accounting

    Dashboard · Growth Accounting (L28)

    Where does MAU actually come from — and where does it leak?

    0
    NewNever active before this period
    ResurrectedDormant ≥ 28d; active again this period
    RetainedActive last period and this period
    ChurnedActive last period, not this period
    Formula: Activet = Retained + New + Resurrected  ·  Activet-1 = Retained + Churned. Net = New + Resurrected − Churned.

    Dashboard · Where the Money Is Left on the Table

    Dashboard · Media Waste Waterfall

    $142,300 gross spend → $89,720 effective spend. Where does the other $52,580 go?

    Gross spend
    $142,300
    Invalid traffic (IVT, bots, fraud)
    −$9,000
    Pre-bid + post-bid filtering reduces to ~2%; expected loss on open exchange.
    Viewability failures
    −$7,000
    Ads rendered but not measurably visible. Pay-on-view or MRC-viewable pricing would recover this.
    Over-frequency (cap breaches)
    −$12,800
    Largest single leak. 14% of the reached audience saw >10 impressions in 7d. Enforce cap in the bidder, not post-hoc.
    Wrong-geo / wrong-lang spill
    −$3,500
    IP→geo resolver miss rate at the edges. Tighten geo predicate on the bid filter.
    Creative fatigue (CTR decay post-day 10)
    −$10,380
    Hero creative CTR down 22% in 14 days. Rotate at day 10, not day 20.
    Late-funnel attribution loss (iOS SKAN)
    −$5,000
    SKAN postback timeout on iOS installs + privacy thresholding. Supplement with server-side CAPI.
    Duplicate attribution (cross-network)
    −$4,900
    Same install credited to two networks because last-touch windows overlap. Reconcile with MMP.
    Effective spend (working media)
    $89,720
    Recoverable waste
    $52,580
    37% of gross spend
    Top fix by $ (90-day)
    Frequency cap
    Est. recovery: $10,200 / month
    Fastest fix (7-day)
    Creative rotation
    Est. recovery: $6,800 / month

    Dashboard · Campaign Improvement Opportunities

    Dashboard · Priority Matrix · Impact × Effort

    Six moves you can make next. Bubble size = monthly $ recovery.

    High impactLow impact
    Low effortHigh effort
    Do now
    Schedule
    Nice-to-have
    Defer / deprioritize
    Frequency cap $10.2K/mo
    Creative rotation $6.8K/mo
    Geo resolver $3.5K/mo
    Server-side CAPI $5.0K/mo
    Viewable pricing $2.1K/mo
    Cross-net dedupe $1.6K/mo
    Sum of monthly recovery across all six: $29.2K/mo · $350K annualized Assumes spend mix constant; each fix treated independently.

    ↑ Back to top

    Part 06

    Round 7 · final minute · Signal

    Round 7 · the signal · the last sixty seconds · what was actually being scored
    You are still in the chair. The 02:17 story is on the whiteboard. The CFO meeting is on the whiteboard. Raj caps the marker for the second time. He leans forward, which is how you know the round is not over.
    Skip this act and you will never know what was actually scored. The last four minutes of the round are where the grade gets written, not in the forty minutes before. If you only read one act, read this one.

    The last four minutes.

    ❌ Most candidates fail here
    by answering with the activity instead of the grade. "You were testing whether I can build an ads platform" — describes what happened, not what was measured. Flattery loses. Rehearsed humility loses. Only the inversion wins: name what you almost did and chose not to do. The revert you didn't ship. The flag you refused. The default you rejected.

    Clock reads 43:22. Raj caps the marker for the second time. The 2:17 AM story is on the whiteboard. The CFO meeting is on the whiteboard. There are no more scenarios. You think the round is done. Raj leans forward, not back, which is how you know it is not.

    What Raj asks · 43:22

    "One more. Quick one. You've built the platform. You've defended it. You've debugged it at two in the morning. You've walked the CFO through it. Look at what you did tonight — 02:17 to 02:58 — and tell me what you think this interview was actually testing."

    You do not answer for six seconds. This is not thinking time. This is the moment the round pivots, and you need the pivot in your own head before you put it into his.

    The two framings
    What you thought it was testing
    • Whether you can design an ads platform at StreamCo scale.
    • Whether you can defend architectural choices under pushback.
    • Whether you can debug production incidents.
    • Whether you can explain numbers to executives.
    • Whether you know enough SQL, enough Kafka, enough dbt.
    True. Also not the grade.
    What it was actually testing
    • Whether you know when not to trust your own pipeline.
    • Whether you can tell a bug from a correction at 2:43 AM.
    • Whether you'll refuse a default when defaults are the enemy.
    • Whether you'll write a paragraph before you write a query.
    • Whether you'll hold a principle when a tactic is easier.
    The grade.

    You say it as one sentence because the question asked for one.

    What you say · 44:18

    "The interview wasn't testing whether I can build pipelines. It was testing whether I know when not to trust them — including the ones I built myself."

    Raj does not nod. He also does not write anything down. The absence is the signal. He reaches for the laptop on the side of the desk and closes it — not yours, his. The round is effectively over. He spends the remaining thirty seconds on one thing.

    The quiet truth · the seven things the last forty-five minutes actually taught
    1
    The label is the schema
    A column named Revenue is a schema decision even when no schema file names it. The debt calcifies in ninety days. Every dashboard earns its label through a definition binding. Act 1 taught you this at 09:47. Act 3 turned it into Contract 2.
    2
    A paragraph before a query
    Finance does not want a number. Finance wants a number with a recommendation attached. Every time you are tempted to reply with SQL, write the sentence first and check whether the SQL still needs to exist. It usually does. Sometimes it doesn't.
    3
    Refuse the default
    One wide table with a default filter is the shortcut that ships the bug. Four tables with no default is the architecture that prevents it. The extra JOIN is the price of never having the September conversation again.
    4
    Concede tactics, hold principles
    Kafka-vs-Kinesis is a tactic. Append-only is a principle. Learn to tell the difference in under forty seconds or the defense round scores you down. Tactics are the paint; principles are the load-bearing wall.
    5
    A correction is not a bug
    When the number moves for a reason, the fix is a paragraph and a parallel table, not a revert. Reverting a correction is a regression disguised as heroism and it costs you a quarter in March. The staff-vs-senior line runs through this single decision.
    6
    Alert on up, not only on down
    Every on-call alert for "it went down" needs a twin on "it went up." Double-counting, measurement changes, and fraud all look like good news until the quarter closes. Symmetric alerts preserve dignity.
    7
    The dashboard narrates itself
    If you have to be in the meeting to explain the chart, the platform is not done. The follow-up question is a design spec, not an improvisation. Design the conversation; the chart follows.

    He lets the last principle sit for eight seconds. Then he stands up, which is the exit move he has made on every good candidate in the last year. The interview ends in an unremarkable sentence.

    Raj: "Good round. Send you feedback through the recruiter. Nothing to schedule — we're aligned."

    You do not ask what aligned means. You already know. The round scored you on the only thing that matters at this level: whether you know the enemy in your own system. You named it in Act 1 — silent label drift between audiences who think they are reading the same number — and every act after that was one more test of whether you'd forget it under pressure.

    You did not forget it. The offer comes three days later.

    The interview is not testing if your pipeline works.
    It is testing if you know when it doesn't —
    and what you do next.
    Quiet truth · 2026

    Ads data engineering at this scale is not about moving data. It is about the architecture that keeps four audiences from telling each other different truths in the same word.

    Raj did not hire you because your Kafka topics were elegant. He hired you because when the number moved at 02:17, you knew which number had moved and why — and when the CFO asked at 08:30, you answered with a paragraph instead of a query. The platform is a consequence of those two sentences.

    — The senior DE you interviewed as, quieter than the one you walked in being.

    The running incident · closed

    Scroll back to The Brief. Look at the $0.9M gap. You know what to say now.

    That sentence is the whole interview.

    Parallel rooms · the meta-question at the other four
    Raj asks "what was this interview actually testing." The other four ask the same thing with different words. The shape of the answer is identical — name what you almost did and chose not to do. The round is scored on refusals, not outputs.
    Meta · Priya
    "After all of that — what did you learn this morning that you did not know when you sat down? One sentence."
    Google · Marcus
    "What's the one decision you made in the last forty-five minutes that you would change if you did the round again? Not the rehearsed answer. The actual one."
    Amazon · Chen
    "What's the one decision you made today that you are certain about? Explain why that certainty survives the next 18 months of this platform getting bigger."
    Apple · Leena
    "What do you hope we do not hire you for?"
    Five interviewers, five last questions, one grading rubric: self-awareness under real-time pressure. Candidates who answer with flattery or with the activity ("you were testing if I can build a pipeline") are marked down. Candidates who name a refusal — the revert they didn't ship, the flag they refused, the default they rejected — pass.

    ↑ Back to top

    Part 07

    Round 4 · Appendix · Deep Dive · SQL

    Round 4 · deep dive · SQL, schema, partitioning · depth beats breadth here · drills for this round
    Round 1 is behind you. Raj stood up. You walked out. Rounds 2 and 3 are on the calendar for later today — SQL and coding. This appendix is the drills for those rounds. Close the answer tags, set a timer, and do not read a line of reference until the timer runs out.

    The coding bank.

    Seven drills. Twelve minutes of SQL each. Twenty minutes of Python each. If you can solve them without reading the answer first, the narrative half of this article will hold under real pressure. If you cannot, work the drills until you can — the rest of the article is scaffolding around this set.

    How to use this appendix

    Close the answer <details>. Set a timer. Solve on paper or in a blank editor. Do not check the reference until the timer runs out, even if you are stuck. Then compare — not to see if you got the same answer, but to see what edge cases your solution missed.

    The bank · seven drills

    01
    The September reconciliation
    SQL · 12 min · Hard

    Given silver.fact_impression (spend_cents, is_valid, fx_rate_to_usd, impression_ts) and finance.ad_revenue_ledger (month, invoiced_usd, refunds_usd, ivt_credits_usd, partner_attribution_adjustment_usd). Produce one row reconciling pipeline spend to Finance settled revenue for September 2025 with an unexplained_gap_usd column.

    Edge cases to catch: timezone of impression_ts (is Sep the PST month or UTC month?), is_valid = FALSE treatment, signs of refunds and credits (subtract) vs adjustments (add).

    Show the reference solution
    WITH pipeline AS (
      SELECT
        SUM(spend_cents * fx_rate_to_usd) / 100.0 AS spend_revenue_usd,
        SUM(CASE WHEN is_valid = FALSE
                 THEN spend_cents * fx_rate_to_usd ELSE 0 END) / 100.0
                                                  AS pipeline_ivt_flagged_usd
      FROM silver.fact_impression
      WHERE impression_ts >= TIMESTAMP '2025-09-01 00:00:00 America/Los_Angeles'
        AND impression_ts <  TIMESTAMP '2025-10-01 00:00:00 America/Los_Angeles'
    ),
    ledger AS (
      SELECT
        SUM(invoiced_usd) - SUM(refunds_usd) - SUM(ivt_credits_usd)
        + SUM(partner_attribution_adjustment_usd) AS settled_revenue_usd,
        SUM(refunds_usd)                          AS finance_refunds_usd,
        SUM(ivt_credits_usd)                      AS finance_ivt_credits_usd,
        SUM(partner_attribution_adjustment_usd)   AS finance_partner_adj_usd
      FROM finance.ad_revenue_ledger
      WHERE month = DATE '2025-09-01'
    )
    SELECT p.*, l.*,
           (l.settled_revenue_usd - p.spend_revenue_usd)
           - (p.pipeline_ivt_flagged_usd - l.finance_ivt_credits_usd)
           + l.finance_refunds_usd
           - l.finance_partner_adj_usd             AS unexplained_gap_usd
    FROM pipeline p CROSS JOIN ledger l;

    What junior solutions miss: timezone boundary — the Finance ledger's "September" is PST, not UTC. Missing this puts 3 hours of Oct 1 UTC impressions in the pipeline side and leaves the gap unexplained by 0.5%.

    02
    Bi-temporal "what did we tell them, and when"
    SQL · 10 min · Medium

    Table gold.revenue_attributed is append-only with event_time, attribution_time, credited_usd, is_superseded. Reproduce exactly the September total Finance saw at 10:37 AM on 2025-09-30, regardless of any rows inserted afterwards.

    Edge: is_superseded as of that moment, not now. You cannot trust today's is_superseded flag.

    Show the reference solution
    -- Sum rows that (a) existed by 10:37 and (b) were not superseded by any row
    -- also created by 10:37.
    WITH snapshot AS (
      SELECT *
      FROM   gold.revenue_attributed
      WHERE  attribution_time <= TIMESTAMP '2025-09-30 10:37:00'
    )
    SELECT SUM(r.credited_usd) AS finance_view_1037
    FROM   snapshot r
    WHERE  NOT EXISTS (
      SELECT 1 FROM snapshot s2
      WHERE  s2.conversion_id   = r.conversion_id
        AND  s2.attribution_time > r.attribution_time
    );

    Why this is the senior answer: naïvely filtering WHERE is_superseded = FALSE uses today's supersession flags. A row that was current at 10:37 but was superseded three days later would be (wrongly) excluded from the snapshot.

    03
    Last-click attribution with 7-day window
    SQL · 15 min · Hard

    Given fact_click (user_id, campaign_id, click_ts) and fact_conversion (user_id, conversion_ts, conversion_usd), credit each conversion to the latest click within 7 days preceding it. Un-attributable conversions (no click in window) return NULL for campaign.

    Edge: ties on click_ts (same second), and conversions with zero matching clicks still need to appear in output (LEFT JOIN, not INNER).

    Show the reference solution
    SELECT
      c.user_id,
      c.conversion_ts,
      c.conversion_usd,
      last_click.campaign_id AS attributed_campaign
    FROM fact_conversion c
    LEFT JOIN LATERAL (
      SELECT campaign_id
      FROM   fact_click k
      WHERE  k.user_id   = c.user_id
        AND  k.click_ts <= c.conversion_ts
        AND  k.click_ts >  c.conversion_ts - INTERVAL '7 days'
      ORDER BY k.click_ts DESC, k.campaign_id DESC  -- deterministic tiebreak
      LIMIT  1
    ) last_click ON TRUE;

    Why the tiebreak column matters: two clicks at the same second from different campaigns would non-deterministically attribute without the secondary ORDER BY. Candidates who skip this ship a test suite that passes locally and fails flakily in CI.

    04
    Frequency capping — streaming, 24h rolling
    Python · 20 min · Medium

    Implement should_serve(user_id, now_ts) -> bool backed by Redis. Cap: at most 5 impressions per user in any rolling 24h window. Redis ops available: ZADD, ZCOUNT, ZREMRANGEBYSCORE, EXPIRE.

    Edge: race between two ad servers for the same user at the same instant — without a transaction, both decide "yes, serve." And: memory bloat if old entries are never trimmed.

    Show the reference solution
    import time
    import redis
    
    r = redis.Redis()
    WINDOW_SEC = 24 * 3600
    CAP = 5
    
    def should_serve(user_id: str, now_ts: int | None = None) -> bool:
        now = now_ts if now_ts is not None else int(time.time())
        key = f"freq:{user_id}"
        cutoff = now - WINDOW_SEC
    
        # Atomic check-then-add in a pipeline with WATCH to avoid the race.
        with r.pipeline() as pipe:
            while True:
                try:
                    pipe.watch(key)
                    pipe.zremrangebyscore(key, 0, cutoff)
                    count = pipe.zcount(key, cutoff, "+inf")
                    if count >= CAP:
                        pipe.unwatch()
                        return False
                    pipe.multi()
                    pipe.zadd(key, {f"{now}:{user_id}:{count}": now})
                    pipe.expire(key, WINDOW_SEC + 60)  # trim memory
                    pipe.execute()
                    return True
                except redis.WatchError:
                    continue  # raced with another server; retry

    What junior solutions miss: (1) the WATCH/MULTI transaction — without it, concurrent ad servers both read count = 4 and both insert, landing at 6. (2) the EXPIRE — without it, inactive users accumulate forever and the Redis cluster OOMs at 3 AM.

    05
    Audience overlap — two segments, Jaccard
    Python · 15 min · Medium

    Given two iterables of user_id (possibly 10M+ each, cannot fit both in memory). Compute Jaccard similarity |A ∩ B| / |A ∪ B|. Approximation acceptable if it beats exact at scale.

    Edge: duplicates within each iterable (same user appearing twice) should not double-count. Memory is the constraint, not time.

    Show the reference solution
    # Exact — for datasets that fit in memory
    def jaccard_exact(a, b):
        sa, sb = set(a), set(b)
        inter = len(sa & sb)
        union = len(sa | sb)
        return inter / union if union else 0.0
    
    # Approximate — MinHash LSH for the 10M+ case
    from datasketch import MinHash
    
    def jaccard_minhash(iterable_a, iterable_b, num_perm=256):
        m_a, m_b = MinHash(num_perm=num_perm), MinHash(num_perm=num_perm)
        for u in iterable_a: m_a.update(str(u).encode())
        for u in iterable_b: m_b.update(str(u).encode())
        return m_a.jaccard(m_b)  # approx ±1% at num_perm=256

    Why the approximate version is the senior answer: at 10M per segment, set() costs ~800MB per side. Two segments ≈ 1.6GB resident before you've started computing. MinHash is O(n) streaming with 256 × 8-byte fingerprints per segment — 2KB, regardless of cardinality. Cite the precision/recall trade explicitly in the answer.

    06
    Dedup impressions — idempotent streaming
    Python · 20 min · Hard

    Impression events arrive on a Kafka topic with impression_id, event_ts. Consumer restarts replay the last 10 minutes. Write a consumer that produces exactly-once writes to the warehouse, without external transactions.

    Edge: impression_id collisions across days (IDs only unique-per-day), clock skew between producers, and the consumer crashing mid-batch.

    Show the reference solution
    from datetime import datetime
    
    SEEN_TTL_SEC = 86400  # impression_id uniqueness window
    
    class Dedupe:
        def __init__(self, redis_client):
            self.r = redis_client
    
        def key(self, event_ts: int, impression_id: str) -> str:
            # Day-bucket prevents cross-day collision.
            day = datetime.utcfromtimestamp(event_ts).strftime("%Y%m%d")
            return f"seen:{day}:{impression_id}"
    
        def process(self, event) -> bool:
            k = self.key(event["event_ts"], event["impression_id"])
            # SETNX returns 1 iff we're the first writer. TTL ensures eventual
            # memory release; slightly longer than the replay window protects
            # against consumer restart within the same bucket.
            if self.r.set(k, b"1", nx=True, ex=SEEN_TTL_SEC):
                write_to_warehouse(event)
                return True
            return False  # duplicate; skip
    
    def write_to_warehouse(event):
        # Idempotent upsert in the warehouse too — belt and braces.
        warehouse.upsert(
            table="silver.fact_impression",
            key=("impression_id", "event_ts"),
            row=event,
        )

    What junior solutions miss: (1) day-bucketing the dedup key (IDs collide across days at some providers), (2) warehouse-side UPSERT as a second line of defence — the Redis check is fast but not durable across region failover, and exactly-once in ads-DE is always defence-in-depth.

    07
    Growth accounting — retained / resurrected / new / churned
    SQL · 12 min · Medium

    Given fact_daily_active (user_id, active_date), produce a daily growth-accounting table with columns date, new, retained, resurrected, churned. Definitions: new = first ever active date; retained = active yesterday and today; resurrected = active today, not yesterday, but active in the last 28 days; churned = active yesterday, not today.

    Edge: dates with zero activity shouldn't silently drop. Use a calendar spine.

    Show the reference solution
    WITH first_seen AS (
      SELECT user_id, MIN(active_date) AS first_date FROM fact_daily_active GROUP BY user_id
    ),
    today AS (
      SELECT a.active_date, a.user_id,
             EXISTS (SELECT 1 FROM fact_daily_active y
                     WHERE y.user_id = a.user_id
                       AND y.active_date = a.active_date - INTERVAL '1 day') AS active_yday,
             EXISTS (SELECT 1 FROM fact_daily_active r
                     WHERE r.user_id = a.user_id
                       AND r.active_date BETWEEN a.active_date - INTERVAL '28 days'
                                             AND a.active_date - INTERVAL '2 days') AS active_last_28
      FROM fact_daily_active a
    )
    SELECT d.date,
      COUNT(*) FILTER (WHERE fs.first_date = d.date)                        AS new_users,
      COUNT(*) FILTER (WHERE fs.first_date < d.date AND t.active_yday)      AS retained,
      COUNT(*) FILTER (WHERE fs.first_date < d.date AND NOT t.active_yday
                        AND t.active_last_28)                               AS resurrected,
      (SELECT COUNT(*) FROM fact_daily_active y
       WHERE y.active_date = d.date - INTERVAL '1 day'
         AND NOT EXISTS (SELECT 1 FROM fact_daily_active z
                         WHERE z.user_id = y.user_id AND z.active_date = d.date))
                                                                            AS churned
    FROM (SELECT generate_series(DATE '2025-01-01', DATE '2025-12-31', INTERVAL '1 day')::date AS date) d
    LEFT JOIN today t      ON t.active_date = d.date
    LEFT JOIN first_seen fs ON fs.user_id = t.user_id
    GROUP BY d.date
    ORDER BY d.date;

    Sanity check: retained + resurrected + new = DAU(today), and DAU(yday) - churned = retained. If those identities don't hold, the SQL is wrong.

    Tech round drill sprint · 8 SQL + 8 Python · L3 → L5

    The seven detail-drills above are the deep work. Below are sixteen short sprints — the patterns the SQL/Python rounds actually test, framed against ads scenarios. Each one names the L-tier, the underlying pattern, and a reference solution with one line on what junior solutions miss. Read them as a warm-up rotation — answer mentally, then check.

    L3
    Top-3 spending campaigns per advertiser
    Pattern · window functions · ROW_NUMBER() OVER (PARTITION BY ...)
    Prompt: "Given fact_campaign_daily(advertiser_id, campaign_id, spend_usd, day), return the top 3 spending campaigns per advertiser for the last 7 days. Tie-break: lower campaign_id wins."
    Senior solution + what junior misses
    SELECT advertiser_id, campaign_id, total_spend
    FROM (
      SELECT advertiser_id, campaign_id,
             SUM(spend_usd) AS total_spend,
             ROW_NUMBER() OVER (
               PARTITION BY advertiser_id
               ORDER BY SUM(spend_usd) DESC, campaign_id ASC
             ) AS rk
      FROM fact_campaign_daily
      WHERE day >= CURRENT_DATE - INTERVAL '7 days'
      GROUP BY advertiser_id, campaign_id
    ) t
    WHERE rk <= 3;

    Junior misses: the secondary ORDER BY campaign_id. Without it, ties are non-deterministic and the result flickers between runs. Senior writes the deterministic tiebreak before the interviewer asks.

    L3
    Advertisers with no impressions yesterday
    Pattern · anti-join · LEFT JOIN ... WHERE r.id IS NULL
    Prompt: "List active advertisers (dim.advertiser) who served zero impressions yesterday. Active means dim.advertiser.is_active = TRUE."
    Senior solution + what junior misses
    SELECT a.advertiser_id, a.name
    FROM   dim.advertiser a
    LEFT JOIN silver.fact_impression i
           ON i.advertiser_id = a.advertiser_id
          AND i.impression_ts >= CURRENT_DATE - INTERVAL '1 day'
          AND i.impression_ts <  CURRENT_DATE
    WHERE  a.is_active = TRUE
      AND  i.advertiser_id IS NULL;

    Junior misses: putting the date filter in the WHERE instead of the ON. That converts the LEFT JOIN to an inner join and drops every advertiser without an impression — exactly the rows you wanted. The filter belongs on the join, not after it.

    L3
    Dedupe duplicate impression events
    Pattern · keep-latest dedup · ROW_NUMBER() ... ORDER BY ingested_ts DESC
    Prompt: "bronze.raw_impression may contain the same impression_id twice (consumer replay). Keep only the latest ingested_ts per impression_id."
    Senior solution + what junior misses
    WITH ranked AS (
      SELECT *,
             ROW_NUMBER() OVER (
               PARTITION BY impression_id
               ORDER BY ingested_ts DESC
             ) AS rn
      FROM bronze.raw_impression
    )
    SELECT * EXCEPT (rn) FROM ranked WHERE rn = 1;

    Junior misses: using DISTINCT ON in Postgres-only flavour, or GROUP BY impression_id + MAX(ingested_ts) which loses the rest of the columns. ROW_NUMBER keeps the whole row and works on every dialect.

    L4
    Week-over-week revenue change per advertiser
    Pattern · LAG() over partitioned weekly aggregates
    Prompt: "Compute weekly attributed-revenue per advertiser for the last 12 weeks, plus a wow_pct column showing change from the prior week."
    Senior solution + what junior misses
    WITH weekly AS (
      SELECT advertiser_id,
             DATE_TRUNC('week', event_time) AS wk,
             SUM(credited_usd)              AS rev
      FROM   gold.revenue_attributed
      WHERE  event_time >= CURRENT_DATE - INTERVAL '84 days'
      GROUP BY 1, 2
    )
    SELECT advertiser_id, wk, rev,
           LAG(rev) OVER (PARTITION BY advertiser_id ORDER BY wk) AS prev_rev,
           (rev - LAG(rev) OVER (PARTITION BY advertiser_id ORDER BY wk))
           / NULLIF(LAG(rev) OVER (PARTITION BY advertiser_id ORDER BY wk), 0) AS wow_pct
    FROM   weekly
    ORDER BY advertiser_id, wk;

    Junior misses: the NULLIF(prev_rev, 0) guard. A new advertiser whose first week is week N has prev_rev = 0; without the guard the query divides by zero and silently fails or returns Inf. Senior writes the guard reflexively.

    L4
    Pivot revenue · rows to columns by surface
    Pattern · CASE pivot · conditional aggregation
    Prompt: "Given fact_revenue(advertiser_id, surface, day, revenue_usd) where surface ∈ (feed, reels, stories, ctv), return one row per (advertiser_id, day) with one column per surface."
    Senior solution + what junior misses
    SELECT advertiser_id, day,
           SUM(CASE WHEN surface = 'feed'    THEN revenue_usd ELSE 0 END) AS feed_usd,
           SUM(CASE WHEN surface = 'reels'   THEN revenue_usd ELSE 0 END) AS reels_usd,
           SUM(CASE WHEN surface = 'stories' THEN revenue_usd ELSE 0 END) AS stories_usd,
           SUM(CASE WHEN surface = 'ctv'     THEN revenue_usd ELSE 0 END) AS ctv_usd
    FROM   fact_revenue
    GROUP BY advertiser_id, day;

    Junior misses: using FILTER (WHERE surface = ...) Postgres-specific syntax which is cleaner but won't run on Spark/BigQuery/Snowflake. CASE is portable. Always ask which engine before reaching for FILTER or PIVOT.

    L4
    Sessionize a viewer's ad exposures · 30-min gap
    Pattern · gap-and-island · LAG + cumulative SUM over window
    Prompt: "Group a viewer's fact_impression rows into sessions where a session ends after 30 minutes of inactivity. Output: user_id, session_id, start_ts, end_ts, impression_count."
    Senior solution + what junior misses
    WITH gaps AS (
      SELECT user_id, impression_ts,
             CASE WHEN impression_ts - LAG(impression_ts)
                    OVER (PARTITION BY user_id ORDER BY impression_ts)
                  > INTERVAL '30 minutes' THEN 1 ELSE 0 END AS is_new_session
      FROM   silver.fact_impression
    ),
    sessions AS (
      SELECT user_id, impression_ts,
             SUM(is_new_session) OVER (
               PARTITION BY user_id ORDER BY impression_ts
               ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
             ) AS session_id
      FROM   gaps
    )
    SELECT user_id, session_id,
           MIN(impression_ts) AS start_ts,
           MAX(impression_ts) AS end_ts,
           COUNT(*)           AS impression_count
    FROM   sessions
    GROUP BY user_id, session_id;

    Junior misses: the unbounded-preceding window frame. Without it the cumulative SUM resets per row and you get session_ids that don't accumulate. The frame is the whole point.

    L5
    Bi-temporal as-of revenue · what did Finance see at 10:37
    Pattern · append-only + correlated NOT EXISTS for snapshot isolation
    Prompt: "gold.revenue_attributed is append-only with event_time + attribution_time. Reproduce exactly the September total Finance saw at 10:37 AM on 2025-09-30, regardless of any rows inserted afterwards."
    Senior solution + what junior misses
    WITH snap AS (
      SELECT *
      FROM   gold.revenue_attributed
      WHERE  attribution_time <= TIMESTAMP '2025-09-30 10:37:00'
        AND  event_time >= '2025-09-01' AND event_time < '2025-10-01'
    )
    SELECT SUM(credited_usd) AS finance_view_1037
    FROM   snap r
    WHERE  NOT EXISTS (
      SELECT 1 FROM snap s2
      WHERE  s2.conversion_id   = r.conversion_id
        AND  s2.attribution_time > r.attribution_time
    );

    Junior misses: filtering on today's is_superseded flag instead of computing supersession at the snapshot moment. A row that was current at 10:37 but was superseded three days later would be wrongly excluded. Bi-temporal questions test whether you understand "as-of-X" vs "current."

    L5
    SCD Type 2 · advertiser-rate change history
    Pattern · slowly-changing dimensions · effective_from / effective_to
    Prompt: "dim.advertiser_rate stores agency-rate history with (advertiser_id, rate_pct, effective_from, effective_to). Join it to fact_impression so each impression picks up the rate that was active at impression_ts."
    Senior solution + what junior misses
    SELECT i.*,
           r.rate_pct
    FROM   silver.fact_impression i
    JOIN   dim.advertiser_rate r
           ON r.advertiser_id = i.advertiser_id
          AND i.impression_ts >= r.effective_from
          AND (i.impression_ts <  r.effective_to OR r.effective_to IS NULL);

    Junior misses: the OR effective_to IS NULL branch for the currently-active row. SCD2 conventionally stores open intervals as effective_to = NULL; without the OR, every impression for an advertiser whose rate is current gets dropped from the join.

    L3
    Aggregate spend by hour · pure Python
    Pattern · collections.defaultdict · streaming aggregation without pandas
    Prompt: "Given an iterator of (impression_ts, spend_cents) tuples (could be 100M rows), return total spend per hour. Cannot use pandas. Memory must stay bounded by the number of distinct hours, not row count."
    Senior solution + what junior misses
    from collections import defaultdict
    from datetime import datetime
    
    def spend_by_hour(rows):
        out = defaultdict(int)
        for ts, cents in rows:
            # Truncate to hour (cheaper than .replace if ts is already ISO string)
            hour = ts.replace(minute=0, second=0, microsecond=0) if isinstance(ts, datetime) else ts[:13]
            out[hour] += cents
        return dict(out)

    Junior misses: using a regular dict with if key not in out: out[key] = 0. Works, slower, and easier to typo. defaultdict(int) is the idiom every senior reaches for here. Bonus: it's also thread-safer if the loop ever moves to producer/consumer.

    L3
    Retry decorator for a flaky SSP API
    Pattern · decorator · exponential backoff · jitter
    Prompt: "Wrap an SSP postback fetcher with a retry decorator: 5 attempts, exponential backoff (1, 2, 4, 8, 16 seconds), with jitter. Surface the last exception if all attempts fail."
    Senior solution + what junior misses
    import time, random, functools
    
    def retry(attempts=5, base=1.0, max_delay=30.0):
        def decorator(fn):
            @functools.wraps(fn)
            def wrapper(*args, **kwargs):
                last_exc = None
                for i in range(attempts):
                    try:
                        return fn(*args, **kwargs)
                    except Exception as e:
                        last_exc = e
                        if i == attempts - 1:
                            break
                        sleep = min(base * (2 ** i), max_delay)
                        sleep += random.uniform(0, sleep * 0.1)  # 10% jitter
                        time.sleep(sleep)
                raise last_exc
            return wrapper
        return decorator
    
    @retry(attempts=5)
    def fetch_ssp_postback(url): ...

    Junior misses: jitter. Without it, a thousand workers retrying the same SSP at the same rhythm DDOS the SSP and you get banned. Even 10% jitter de-syncs the herd. Senior writes it before being asked.

    L3
    Counter · top-10 advertisers by impression volume
    Pattern · collections.Counter · streaming top-K
    Prompt: "Given an iterator of impression_id, advertiser_id rows, return the top 10 advertisers by impression count. Single pass, bounded memory."
    Senior solution + what junior misses
    from collections import Counter
    
    def top_10_advertisers(rows):
        c = Counter(advertiser_id for _, advertiser_id in rows)
        return c.most_common(10)

    Junior misses: reaching for sorted(...)[:10] on a 50M-element dict — that's O(n log n) when most_common(k) is O(n log k) using a heap internally. At ad scale the heap path is 5–8× faster.

    L3
    Generator · chunked S3 file processing
    Pattern · yield · constant-memory streaming
    Prompt: "Process a 50GB compressed JSONL of impressions from S3 in chunks of 10,000 rows, yielding each chunk to the caller. Memory must stay flat regardless of file size."
    Senior solution + what junior misses
    import gzip, json
    from itertools import islice
    
    def chunked_jsonl(stream, chunk_size=10_000):
        """Stream JSONL records in chunks. Constant memory."""
        while True:
            chunk = list(islice(stream, chunk_size))
            if not chunk:
                return
            yield [json.loads(line) for line in chunk]
    
    # Usage:
    with gzip.open('s3://path/impressions.jsonl.gz', 'rt') as f:
        for batch in chunked_jsonl(f):
            process(batch)

    Junior misses: calling f.readlines() first — that materialises the whole file in memory and dies on a 50GB input. islice on the file object reads lazily. Senior reaches for the lazy iterator on instinct.

    L4
    Sliding-window max · ROAS spike detection
    Pattern · collections.deque · monotonic queue · O(n)
    Prompt: "Given a stream of (timestamp, ROAS) tuples, return the running max ROAS over the last 5 minutes. Must be O(n), not O(n·k)."
    Senior solution + what junior misses
    from collections import deque
    
    def rolling_max_roas(stream, window_seconds=300):
        """Monotonic deque: indices ordered by ts, values monotonically decreasing."""
        dq = deque()  # holds (ts, roas)
        for ts, roas in stream:
            # Drop items outside the window (left of dq)
            while dq and dq[0][0] < ts - window_seconds:
                dq.popleft()
            # Drop items smaller than current roas (right of dq) — they can never be max again
            while dq and dq[-1][1] <= roas:
                dq.pop()
            dq.append((ts, roas))
            yield ts, dq[0][1]  # dq[0] is always the current window's max

    Junior misses: implementing this with max(values_in_window) per row — that's O(n·k) and at 2B impressions/day per advertiser is the difference between 30 seconds and 30 hours. The monotonic deque makes it O(n) amortised.

    L4
    Token-bucket rate limiter for advertiser API
    Pattern · token bucket · burst-tolerant rate limiting
    Prompt: "Limit advertiser API calls to 100 requests per second, but allow short bursts up to 200. Implement can_proceed(advertiser_id) → bool."
    Senior solution + what junior misses
    import time
    from dataclasses import dataclass
    
    @dataclass
    class Bucket:
        tokens: float
        last_refill: float
    
    class RateLimiter:
        def __init__(self, rate=100, burst=200):
            self.rate = rate            # tokens per second
            self.burst = burst          # max tokens
            self.buckets: dict[str, Bucket] = {}
    
        def can_proceed(self, advertiser_id: str) -> bool:
            now = time.monotonic()
            b = self.buckets.get(advertiser_id) or Bucket(self.burst, now)
            # Refill
            elapsed = now - b.last_refill
            b.tokens = min(self.burst, b.tokens + elapsed * self.rate)
            b.last_refill = now
            if b.tokens >= 1:
                b.tokens -= 1
                self.buckets[advertiser_id] = b
                return True
            self.buckets[advertiser_id] = b
            return False

    Junior misses: using time.time() instead of time.monotonic(). time.time() can go backwards on NTP adjustments — a candidate who hasn't lost a Saturday to that bug uses it; a senior reaches for monotonic().

    L4
    Topological sort · ETL DAG ordering
    Pattern · Kahn's algorithm · in-degree queue
    Prompt: "Given a dict {job: [upstream_jobs]} describing an ads-ETL DAG, return a valid execution order. Raise CycleError if there's a cycle."
    Senior solution + what junior misses
    from collections import defaultdict, deque
    
    class CycleError(ValueError): pass
    
    def topo_sort(deps: dict[str, list[str]]) -> list[str]:
        in_deg = defaultdict(int)
        children = defaultdict(list)
        nodes = set(deps)
        for job, parents in deps.items():
            for p in parents:
                children[p].append(job)
                in_deg[job] += 1
                nodes.add(p)
    
        q = deque(n for n in nodes if in_deg[n] == 0)
        order = []
        while q:
            n = q.popleft()
            order.append(n)
            for child in children[n]:
                in_deg[child] -= 1
                if in_deg[child] == 0:
                    q.append(child)
    
        if len(order) != len(nodes):
            raise CycleError(f"cycle involving {nodes - set(order)}")
        return order

    Junior misses: tracking nodes separately from deps.keys(). A leaf job that's only ever a parent (never a key) is missing from deps — without adding parents to nodes the in-degree count is wrong and the cycle check produces false positives. Subtle and common.

    L5
    Async fan-out · parallel postback fetch from N SSPs
    Pattern · asyncio.gather · bounded concurrency · partial-failure tolerance
    Prompt: "Fetch attribution postbacks from 8 SSP partners in parallel. Bound concurrency to 4 simultaneous calls (the smallest SSP rate-limits at 4). Return all successful results plus a list of failures — don't let one bad SSP poison the whole batch."
    Senior solution + what junior misses
    import asyncio
    import aiohttp
    
    async def fetch_one(session, ssp, sem):
        async with sem:  # bounded concurrency
            try:
                async with session.get(ssp.url, timeout=10) as resp:
                    resp.raise_for_status()
                    return ('ok', ssp.name, await resp.json())
            except Exception as e:
                return ('err', ssp.name, repr(e))
    
    async def fetch_all_postbacks(ssps):
        sem = asyncio.Semaphore(4)
        async with aiohttp.ClientSession() as session:
            results = await asyncio.gather(
                *(fetch_one(session, s, sem) for s in ssps),
                return_exceptions=False,  # we already wrapped in fetch_one
            )
        successes = [r for r in results if r[0] == 'ok']
        failures  = [r for r in results if r[0] == 'err']
        return successes, failures

    Junior misses: the Semaphore. Without it, all 8 fire in parallel and the smallest SSP returns 429s. Senior solution has bounded concurrency + per-task exception capture so one bad SSP does not poison the batch.

    Senior pattern across all 16 drills

    Three things separate junior from senior in tech rounds: (1) deterministic edge-case handling on day one of writing the query (the secondary ORDER BY, the NULLIF, the OR effective_to IS NULL); (2) reaching for the idiom rather than recreating the algorithm (Counter, defaultdict, asyncio.gather, monotonic deque); (3) naming the production failure mode that the obvious solution would hit (DDOS without jitter, OOM without lazy iter, false cycle from missing nodes, NTP drift from time.time). The drills above all hide one of those three signals. Practise until you write the senior version on first pass.

    If you can do these from a blank buffer, under timer, without reaching for the docs, the round will play out the way the seven acts did.

    Seven drills, seven acts, one lesson. The lesson is the one Raj was testing in Act 7: the job is mostly the things you don't build. The drills are the things you need to be fluent in so you can spend the round thinking about the things that don't make it into code. Walk in warm.

    Final note, to the reader: you are not inside the article. The article is a transcript of one possible round. Your round will differ in surface detail and resemble it in exactly one way — the interviewer will be testing the refusals, not the outputs. The seven principles in Act 7 are the refusals. Memorise those, practice the drills, and the seven-act shape will find itself in the room.

    ↑ Back to top