PaddySpeaks · Systems at the Whiteboard · Nº 05

The Inventory Problem

Model Netflix's ad-supported tier — every ad slot, frequency caps, contractual pacing, and the make-good penalty when a guaranteed buy comes up short. The single decision that separates a senior answer: store the empty slots too. A full working through — data flow, schema, the invariant, streaming Python, the nightly pacing close, the analytics SQL, and the dashboard.

§ 01 — THE QUESTIONA contract wearing an impression's clothes

Programmatic advertising sells what it can. Connected-TV advertising sells what it promised — and pays a penalty when it misses. That one difference turns an analytics warehouse into a finance system, and it is the whole question.

Interview Prompt

"Design a model for Netflix's ad-supported tier — capture every ad-slot opportunity, filled or unfilled, every ad served, frequency caps per user, and the contractual pacing guarantees per advertiser: ten million impressions by month-end, with a make-good if we under-deliver."

LEVEL · SENIOR / STAFFDURATION · 45 MINFORMAT · WHITEBOARD

The trap in this prompt is that it sounds like a logging problem. Count the ads, sum the revenue, build a dashboard — a junior answer ships an fct_ad_impressions table and stops. But the prompt buried three forces in one sentence, and they pull in different directions. Maximize fill rate, so no slot goes to waste. Honor frequency caps, so a viewer does not see the same Ford spot eight times in one film. And hit the committed volume per advertiser per month, because the contract has teeth. A model tuned for any one of these starves the others, and the only artifact that lets you reason about all three at once is the one a logging mindset never builds: a row for the ad that didn't play.

So before any boxes and arrows, the working frame for the session — three grains, three stakeholders, three tempos:

THE SLOT GRAIN
Every opportunity, filled or not. One row the instant a viewer's session opens an ad break. Most rows are won and monetized; the ones with a null creative are the most valuable rows in the warehouse, because they are the only record of revenue you could have earned and didn't.
THE CAP GRAIN
Frequency state per viewer per campaign. The ad server reads this before every decision — it is the small, hot, write-heavy table that says "this profile has already seen Ford twice today." Get it wrong and you either annoy viewers or leave reach on the table.
THE CONTRACT GRAIN
Pacing and obligation. A daily snapshot of delivered-versus-committed per campaign, and — when the month closes short — a make-good row that books real dollars Netflix owes. This is the grain finance audits, and it is why the model has to survive quarter-end, not just render a chart.
An impression is something that happened. An unfilled opportunity is something that didn't. A warehouse that only stores what happened cannot answer the one question the business is actually asking.

Scoping out loud

Scope is the first scored dimension, and most candidates skip it. Said out loud: in scope is the inventory and contractual model — opportunities, impressions, caps, pacing, make-goods — and the analytics that hang off them. Out of scope, explicitly: the real-time bidding auction itself (treated as an upstream service that hands us a winner or a null), the creative-rendering and ad-stitching pipeline, billing and invoicing systems (we hand finance a liability, not an invoice), and identity resolution across devices (we assume a resolved profile_id). The caveat: the model must not preclude programmatic — guaranteed buys and auction wins share the same opportunity row, distinguished by a fill reason, not a separate table.

Then the envelope math, volunteered rather than extracted. Mid-2025-shaped numbers for the ad tier:

QuantityEstimateConsequence
Ad-supported profiles~94 MThe audience the caps and pacing run against
Avg ad load~4–5 min / hrSets slots per viewing hour
Avg slots per session~6Pre-roll + a handful of mid-rolls
Opportunity rows / day≈ 1.5–2 BThe number that shapes the whole architecture
Fill rate~85–92 %So ~10–15% of rows are the precious null-creative ones
Unfilled rows / day~150–250 MDoubles nothing — it's the diagnostic surface
Active campaigns~10³–10⁴The cardinality of pacing & make-good math
Make-good exposure$ millions / quarterA balance-sheet liability, not a metric

Notice where the weight sits. The opportunity table is the firehose — a couple of billion rows a day, partitioned by date so it stays queryable — but the row that earns the architecture its keep is the unfilled one. Two billion rows is an engineering problem you solve with partitioning and columnar storage. The hundred-and-fifty million empty rows are a modeling decision, and refusing to make it is what fails the interview.


§ 02 — DATA FLOWFrom ad break to balance sheet

One opportunity row is born at the ad server, enriched if it fills, rolled up nightly into pacing, and — at month close — distilled into a liability. The spine is an append-only opportunity log feeding three derived layers that each run at their own tempo.

DECISION PATH · PER AD BREAK · <40MS BUDGET SETTLEMENT PATH · NIGHTLY & MONTH-CLOSE CTV PLAYER ad break opens · SSAI AD DECISION SERVER read caps + pacing pick creative OR emit unfilled row OPPORTUNITY LOG append-only · key: campaign_id filled & unfilled, both kept FREQUENCY STATE · KV (profile × campaign × day) counter + TTL · read pre-decision IMPRESSION BEACONS quartile pings · completion OPPORTUNITY LAKE Parquet · partitioned by date cap check · <5ms before every fill OPPORTUNITY LAKE yesterday's partition PACING ROLL-UP delivered vs committed pacing_index · days_left PACING SNAPSHOT 1 row / campaign / day MONTH-CLOSE JOB committed − delivered > 0? MAKE-GOOD LEDGER owed_impressions · owed_$ finance liability SOLID — data lands here · DASHED — request-time read · The empty slot is a first-class row, not an absence.
FIG. 1 — Decision path runs in milliseconds and writes both fills and nulls; settlement path runs nightly and monthly, turning rows into dollars owed.

Three properties of this picture do most of the interview's work. First, the ad decision server writes a row whether or not it fills the slot — the unfilled branch is not an error path, it is the diagnostic that later answers "why did we leave money on the table?" Second, the frequency-state store is the only hot, low-latency read on the decision path: a tiny counter keyed by (profile × campaign × day) that the server consults before every fill, and the only place in the design where staleness costs money in real time. Third, pacing and make-goods are derived, not live — a daily roll-up and a month-close job — because a contract is measured against a calendar, not a clock, and pre-aggregating the snapshot keeps the account-manager dashboard fast.

The Failure Philosophy, In One Rule

When in doubt, do not fill. The frequency-cap read is the one place a stale value is dangerous, so the cap check fails closed: if the counter store is unreachable or ambiguous, the server declines the impression and emits an unfilled row with reason cap_check_unavailable. Over-delivering against a cap means a viewer sees the same spot too many times and the campaign's frequency promise is broken; under-delivering means an empty slot we can always diagnose later. One of those is a degraded experience we logged; the other is a contract violation we can't take back.


§ 03 — DATA MODELFive facts, and the one that's empty on purpose

The schema falls out of the grain question. A wide append-only opportunity fact at the slot grain; a thin impression fact for the slots that filled; a daily pacing snapshot; a hot cap-state table; and a make-good ledger that finance treats as a liability.

The opportunity fact — where the null is sacred

This is the spine. One row per ad-slot opportunity, written by the decision server. The served_creative_id is nullable by design: a null is not missing data, it is a fully-formed fact that the slot existed and went unfilled, qualified by an unfilled_reason. The mirror of that column, fill_reason, records how a filled slot was won — a guaranteed buy, an auction, or a house ad — so guaranteed and programmatic inventory live in one table, distinguished by a value rather than a schema.

DDL · THE SLOT GRAIN (APPEND-ONLY)
-- One row per ad-slot opportunity in a viewing session. -- Filled or unfilled — BOTH are kept. Partitioned by date; the -- null-creative rows are the entire reason this table exists. CREATE TABLE fct_ad_opportunities ( opportunity_id BIGINT NOT NULL, profile_id BIGINT NOT NULL, -- resolved ad-tier profile title_id BIGINT NOT NULL, session_id BIGINT NOT NULL, slot_type TEXT NOT NULL CHECK (slot_type IN ('pre_roll','mid_roll','post_roll')), slot_position_sec INT NOT NULL, -- offset into the title slot_duration_sec SMALLINT NOT NULL, predicted_cpm NUMERIC(7,2), -- what the slot was worth served_creative_id BIGINT, -- NULL == UNFILLED fill_reason TEXT -- non-null iff filled CHECK (fill_reason IN ('won_auction','guaranteed_buy','house_ad')), unfilled_reason TEXT -- non-null iff unfilled CHECK (unfilled_reason IN ('frequency_capped', 'no_eligible_demand','content_unsuitable', 'cap_check_unavailable')), opportunity_ts TIMESTAMPTZ NOT NULL, -- the model's own conscience: a slot is filled XOR explained. CONSTRAINT filled_xor_unfilled CHECK ( (served_creative_id IS NOT NULL AND fill_reason IS NOT NULL AND unfilled_reason IS NULL) OR (served_creative_id IS NULL AND unfilled_reason IS NOT NULL AND fill_reason IS NULL)) ) PARTITION BY RANGE (opportunity_ts);

The filled_xor_unfilled constraint is doing real work, and it is worth saying out loud in the room: it makes the table physically incapable of holding an ambiguous row. Every slot is either won-and-explained or empty-and-explained — never both, never neither. That is what turns fill rate from a fragile derived metric into a trivial COUNT(*) FILTER, and it is the difference between a model that lies quietly at quarter-end and one that can't.

The impression fact — the subset that played

Filling a slot is a promise to play; an impression is the proof it played. The impression fact is a thin child of the opportunity, keyed by opportunity_id, carrying the quartile-completion signal the beacons report and the recognized revenue — the money that actually counts toward delivery, which is not always the same as the money that was predicted.

DDL · THE SERVED GRAIN
CREATE TABLE fct_ad_impressions ( impression_id BIGINT PRIMARY KEY, opportunity_id BIGINT NOT NULL -- FK → fct_ad_opportunities REFERENCES fct_ad_opportunities (opportunity_id), started_ts TIMESTAMPTZ NOT NULL, completed_ts TIMESTAMPTZ, completion_pct NUMERIC(5,2) NOT NULL DEFAULT 0, -- 0/25/50/75/100 quartiles skipped BOOLEAN NOT NULL DEFAULT FALSE, recognized_cpm NUMERIC(7,2) NOT NULL, -- billable rate, post-verification revenue_usd NUMERIC(10,4) NOT NULL ); -- Only ~85-90% of opportunities ever land a row here. That gap, -- joined back to the unfilled_reason on the parent, IS the report.

The contractual layer — snapshot, then ledger

Pacing is not computed on the fly. It is a daily snapshot per (campaign × day) carrying a pacing_index — where 1.0 means exactly on track — so an account manager's morning dashboard reads one small pre-aggregated table instead of scanning two billion opportunity rows. When the flight closes short, the make-good obligation is born: a row in a ledger that finance carries as a balance-sheet liability until it is fulfilled with bonus inventory next period.

DDL · CONTRACT & CAP STATE
CREATE TABLE fct_pacing_daily ( campaign_id BIGINT NOT NULL, day DATE NOT NULL, delivered BIGINT NOT NULL, -- impressions today committed_today BIGINT NOT NULL, -- even-pace daily target cumulative_delivered BIGINT NOT NULL, total_committed BIGINT NOT NULL, -- the contract number pacing_index NUMERIC(6,3) NOT NULL, -- 1.000 = on track days_remaining SMALLINT NOT NULL, PRIMARY KEY (campaign_id, day) ); -- The liability. One row per under-delivered (campaign × month). -- This is finance's table, not the dashboard team's. CREATE TABLE fct_makegood_obligations ( campaign_id BIGINT NOT NULL, original_period DATE NOT NULL, -- the flight that fell short makegood_period DATE, -- where the owed inventory lands owed_impressions BIGINT NOT NULL, owed_value_usd NUMERIC(14,2) NOT NULL, status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','scheduled','fulfilled')), PRIMARY KEY (campaign_id, original_period) ); -- Frequency state: the hot, tiny table the ad server reads before -- every fill. Lives in a KV store in production; modeled here for the -- analytics mirror. Grain is what the server queries: per day, per cap. CREATE TABLE fct_frequency_state ( profile_id BIGINT NOT NULL, campaign_id BIGINT NOT NULL, day DATE NOT NULL, impressions_today SMALLINT NOT NULL DEFAULT 0, impressions_week SMALLINT NOT NULL DEFAULT 0, cap_today SMALLINT NOT NULL, -- e.g. 3 cap_week SMALLINT NOT NULL, -- e.g. 10 PRIMARY KEY (profile_id, campaign_id, day) );

The dimensions are conventional and mostly SCD2 — dim_advertisers, dim_campaigns, dim_creatives, dim_titles (content ratings drive ad eligibility), dim_profiles (the tier column gates the whole audience) — plus one that earns special mention: dim_campaign_flights, which holds the start_date, end_date, and total_committed_impressions that the entire pacing-and-make-good machinery measures itself against. The flight is the calendar the contract lives on.


§ 04 — THE CORE INVARIANTThe cap check, and the slot that is always accounted for

This system's correctness lives in two places: a counter the ad server increments atomically so a frequency cap is never silently breached, and a structural promise that every opportunity is conserved — filled plus unfilled equals offered, always.

Walk the decision. An ad break opens; the server has a slot to fill. It pulls the candidate campaigns, and for each one it must answer a question the marketplace cannot get wrong: has this profile already hit its cap? That read-then-decide is a race — two ad breaks for the same viewer can open within milliseconds on two servers — so the increment has to be atomic, and it has to be conditional on staying under the cap. The guard is the lock.

SQL · THE ATOMIC HEART OF THE CAP
-- Claim one impression against the frequency budget. Succeeds for -- exactly one concurrent caller per remaining unit under the cap. UPDATE fct_frequency_state SET impressions_today = impressions_today + 1, impressions_week = impressions_week + 1 WHERE profile_id = :profile_id AND campaign_id = :campaign_id AND day = CURRENT_DATE AND impressions_today < cap_today -- the guard IS the lock AND impressions_week < cap_week; -- rowcount = 1 → the slot is yours, serve the creative. -- rowcount = 0 → capped: skip this campaign, try the next, or emit -- an unfilled row with reason 'frequency_capped'.

In production this counter lives in a key-value store, where the same logic is an atomic INCR guarded by a compare against the cap; the SQL above is the mirror that makes the invariant legible. The point is identical either way: the cap is enforced by a single conditional write, not by reading the count and trusting it. Two simultaneous ad breaks racing for a viewer's last allowed Ford impression resolve with exactly one winner — and the loser does not error, it falls through to the next campaign or writes an honest empty row.

Read-then-write is a bug at this scale. The cap is enforced by the write that refuses to exceed it, or it is not enforced at all.INVENTORY RULE Nº 1 — THE GUARD IS THE LOCK

The opportunity lifecycle

SLOT OPENED CAP CHECK DEMAND SELECT FILLED · creative / UNFILLED · reason

Every slot exits to exactly one terminal state, and both terminals are recorded. That is the second invariant, and it is a conservation law: for any campaign, day, or title, opportunities = filled + unfilled, with no remainder. Because the opportunity fact's filled_xor_unfilled constraint enforces it at write time, the accounting can never drift — there is no third bucket where lost slots hide. Fill rate, the most-watched number in the building, is therefore exact and cheap, and "money on the table" is just the unfilled side of the same ledger.

SQL · THE CONSERVATION CHECK (RUNS IN CI ON A SAMPLE)
-- An opportunity is filled XOR explained-unfilled. This must return -- zero rows, forever. If it ever doesn't, the model is lying. SELECT opportunity_id FROM fct_ad_opportunities WHERE opportunity_ts >= CURRENT_DATE - 1 AND ( (served_creative_id IS NOT NULL AND unfilled_reason IS NOT NULL) OR (served_creative_id IS NULL AND unfilled_reason IS NULL) );

§ 05 — INGESTION & STREAMSThe decision server, in Python

Three small programs carry the decision path: the slot resolver that fills or refuses, the impression-beacon consumer that promotes a fill into a verified play, and the cap-check primitive they both lean on. Each is small; the judgment is in what they refuse to do.

1 · The slot resolver — fill, or write the empty row

The resolver's most important behavior is the one a junior version omits: it always emits a row. Every branch — capped, no demand, unsuitable content, cap store down — ends in a write. There is no path where a slot quietly evaporates, because an evaporated slot is exactly the revenue leak the model exists to measure.

PYTHON · AD DECISION SERVER — RESOLVE ONE SLOT
import time, uuid async def resolve_slot(ctx, slot) -> dict: """Decide one ad slot. The contract: ALWAYS return an opportunity row. A filled slot carries a creative + fill_reason; an unfilled slot carries an unfilled_reason. Never None, never a silent drop — the empty row is the product.""" base = { "opportunity_id": uuid.uuid4().int >> 64, "profile_id": slot.profile_id, "title_id": slot.title_id, "session_id": slot.session_id, "slot_type": slot.slot_type, "slot_position_sec": slot.position, "opportunity_ts": time.time(), "served_creative_id": None, "fill_reason": None, "unfilled_reason": None, "predicted_cpm": None, } # Content gate first: a TV-MA title is ineligible for some buys. eligible = await ctx.eligible_campaigns(slot.title_id, slot.profile_id) if not eligible: return {**base, "unfilled_reason": "no_eligible_demand"} # Walk demand best-first; the FIRST campaign that clears its cap wins. for campaign in eligible: # ranked by predicted_cpm × pacing_need ok = await claim_impression(ctx.kv, slot.profile_id, campaign.id) if ok is None: # cap store unreachable → FAIL CLOSED return {**base, "unfilled_reason": "cap_check_unavailable"} if ok: # claimed a unit under the cap return {**base, "served_creative_id": campaign.pick_creative(slot), "fill_reason": campaign.fill_reason, # guaranteed_buy / won_auction "predicted_cpm": campaign.cpm} # ok is False → this profile is capped for THIS campaign; try next. # Demand existed but everyone was capped for this viewer. return {**base, "unfilled_reason": "frequency_capped"}

One carve-out, always stated: house ads are the floor, not a branch here. Some designs backfill an unfilled premium slot with an owned-and-operated promo so the screen is never black; that is a policy layer above this resolver, and it changes the fill_reason to house_ad rather than suppressing the unfilled accounting. The diagnostic still sees that no paying demand cleared — we just chose not to show a blank.

2 · The cap-check primitive — atomic, fail-closed

PYTHON · CLAIM ONE IMPRESSION AGAINST THE CAP
async def claim_impression(kv, profile_id, campaign_id) -> bool | None: """Atomic increment-under-cap. Returns True (claimed), False (capped), or None (store unreachable → caller fails closed). The whole frequency promise rides on this being one round-trip, not a read followed by a write.""" key = f"freq:{profile_id}:{campaign_id}:{_today()}" cap = CAPS[campaign_id].daily try: # Lua / server-side: INCR then compare, all under one lock. # Returns the post-increment value, or -1 if it would exceed cap. n = await kv.eval(CLAIM_LUA, keys=[key], args=[cap, DAY_TTL]) except (ConnectionError, TimeoutError): return None # do NOT guess the count: refuse to fill return n != -1 CLAIM_LUA = """ local n = tonumber(redis.call('GET', KEYS[1]) or '0') if n >= tonumber(ARGV[1]) then return -1 end -- at cap: reject n = redis.call('INCR', KEYS[1]) if n == 1 then redis.call('EXPIRE', KEYS[1], ARGV[2]) end return n """

3 · The beacon consumer — a fill becomes a play

A filled opportunity is a promise; the player's quartile beacons are the receipt. The consumer folds 0/25/50/75/100 pings into one impression row, and — the senior move — it only recognizes revenue past the contractual completion threshold, because a viewer who skipped at the two-second mark did not deliver the impression the advertiser bought.

PYTHON · BEACON CONSUMER → fct_ad_impressions
from collections import defaultdict RECOGNIZE_AT_PCT = 50 # most CTV buys recognize at the 50% quartile async def fold_beacons(stream, sink): """Collapse quartile pings per opportunity into one impression row. Idempotent by construction: a duplicated 75% ping cannot raise completion above the max already seen, so at-least-once delivery is safe — we never need exactly-once from the transport.""" state: dict[int, dict] = defaultdict(lambda: {"pct": 0, "skipped": False}) async for ping in stream: s = state[ping.opportunity_id] s["pct"] = max(s["pct"], ping.quartile_pct) # monotone, dup-safe s["skipped"] = s["skipped"] or ping.skipped if ping.quartile_pct == 100 or ping.is_break_end: cpm = ping.contract_cpm if s["pct"] >= RECOGNIZE_AT_PCT else 0 await sink.write({ "opportunity_id": ping.opportunity_id, "completion_pct": s["pct"], "skipped": s["skipped"], "recognized_cpm": cpm, # 0 below threshold "revenue_usd": cpm / 1000.0, # one impression = CPM/1000 }) del state[ping.opportunity_id]

§ 06 — AGGREGATIONThe nightly pace and the monthly reckoning

Two derived jobs turn rows into accountability. A nightly roll-up computes each campaign's pacing index against an even-delivery target; a month-close job reads the flights that ended and mints the make-good liability for the ones that fell short.

The pacing index is the contract's pulse. The even-pace target for a flight is simply total_committed ÷ flight_days; cumulative delivery divided by what should have been delivered by now gives an index where 1.0 is on track, below is behind, above is ahead of plan. The subtlety is that pacing must not over-correct: a campaign at 0.6 with twenty days left has time, while the same index with three days left is a five-alarm fire. So the job emits both the index and days_remaining, and the alerting threshold is a function of both — pacing is read with the calendar, never alone.

PYTHON · NIGHTLY PACING ROLL-UP
def build_pacing_snapshot(spark, run_date): """Pre-aggregate (campaign × day) so account managers read one small table, not two billion opportunity rows. The pacing_index is the whole point: 1.0 == exactly on the even-delivery line.""" delivered = (spark.table("fct_ad_impressions") .where(F.col("recognized_cpm") > 0) # only recognized impressions count .join(spark.table("fct_ad_opportunities"), "opportunity_id") .where(F.to_date("opportunity_ts") == run_date) .groupBy("campaign_id").agg(F.count("*").alias("delivered"))) flights = spark.table("dim_campaign_flights") snap = (delivered.join(flights, "campaign_id") .withColumn("flight_days", F.datediff("end_date", "start_date") + 1) .withColumn("days_elapsed", F.datediff(F.lit(run_date), "start_date") + 1) .withColumn("committed_today", F.col("total_committed_impressions") / F.col("flight_days")) .withColumn("days_remaining", F.col("flight_days") - F.col("days_elapsed")) # cumulative is carried forward; shown here as the day's contribution .withColumn("should_have_by_now", F.col("committed_today") * F.col("days_elapsed")) .withColumn("pacing_index", F.col("cumulative_delivered") / F.greatest(F.col("should_have_by_now"), F.lit(1)))) return snap.select("campaign_id", F.lit(run_date).alias("day"), "delivered", "committed_today", "cumulative_delivered", F.col("total_committed_impressions").alias("total_committed"), "pacing_index", "days_remaining")

The month-close job runs once, the morning after a flight ends. For every campaign whose flight closed in the period, it compares cumulative delivery to the commitment; any shortfall becomes a make-good row, priced at the contracted CPM, and lands in finance's ledger as a pending liability. Nothing about this is approximate — a make-good is a real obligation to deliver bonus impressions next period or refund, and it sits on the balance sheet until status moves to fulfilled.

PYTHON · MONTH-CLOSE — MINT THE MAKE-GOOD LIABILITY
def close_flights(spark, close_date): """Run the morning after a flight ends. Under-delivery becomes a booked liability — owed impressions AND owed dollars — not a metric on a slide. Idempotent: keyed by (campaign, original_period), so a re-run upserts the same obligation rather than double-booking.""" closed = (spark.table("fct_pacing_daily") .where(F.col("day") == close_date) .where(F.col("days_remaining") == 0) # flight ended yesterday .withColumn("shortfall", F.col("total_committed") - F.col("cumulative_delivered"))) owed = (closed.where(F.col("shortfall") > 0) .join(spark.table("dim_campaigns"), "campaign_id") .select( "campaign_id", F.trunc(F.lit(close_date), "month").alias("original_period"), F.lit(None).cast("date").alias("makegood_period"), F.col("shortfall").alias("owed_impressions"), (F.col("shortfall") / 1000.0 * F.col("contract_cpm")) .alias("owed_value_usd"), F.lit("pending").alias("status"))) owed.write.mode("merge").saveAsTable("fct_makegood_obligations")
The pacing index may move every night. The make-good, once booked, is a number finance carries on the balance sheet — and the only way to retire it is to actually deliver the impressions.INVENTORY RULE Nº 2 — THE OBLIGATION IS REAL MONEY

§ 07 — ANALYTICS SQLThree questions the model was built to answer

The opportunity fact and the contractual layer are where the system explains itself. Three queries an interviewer loves, because each carries a classic pattern on its back — conditional aggregation, a window-LAG onset detector, and a liability roll-up.

Where did the money leak? — conditional aggregation

The flagship report, and the reason the empty rows exist. Fill rate is a single COUNT(*) FILTER, and the unfilled reasons fan out into a diagnosis: no_eligible_demand is the sales team's problem, frequency_capped is the cap-policy team's, content_unsuitable is eligibility rules. One scan tells you which lever to pull.

SQL · FILL RATE & THE MONEY-ON-THE-TABLE BREAKDOWN
SELECT slot_type, count(*) AS opportunities, count(*) FILTER (WHERE served_creative_id IS NOT NULL) AS filled, round(100.0 * count(*) FILTER (WHERE served_creative_id IS NOT NULL) / nullif(count(*), 0), 1) AS fill_rate_pct, count(*) FILTER (WHERE unfilled_reason = 'no_eligible_demand') AS no_demand, count(*) FILTER (WHERE unfilled_reason = 'frequency_capped') AS cap_blocked, count(*) FILTER (WHERE unfilled_reason = 'content_unsuitable') AS content_block, -- the dollar framing: unfilled rows priced at what they were worth round(sum(predicted_cpm) FILTER (WHERE served_creative_id IS NULL) / 1000.0, 2) AS est_revenue_lost FROM fct_ad_opportunities WHERE opportunity_ts >= CURRENT_DATE - 1 GROUP BY slot_type ORDER BY est_revenue_lost DESC;

Which campaigns are about to breach? — pacing with a runway test

The account manager's morning alert. The pattern is a guarded read of the snapshot — index below a threshold and little runway left — that computes the catch-up rate now required. Note that "behind" alone is not actionable; "behind with no days left" is.

SQL · CAMPAIGNS AT RISK OF UNDER-DELIVERY
SELECT p.campaign_id, c.advertiser_id, p.cumulative_delivered, p.total_committed, round(100.0 * p.cumulative_delivered / nullif(p.total_committed, 0), 1) AS pct_delivered, p.pacing_index, p.days_remaining, -- what daily rate closes the gap in the time that's left? CEIL((p.total_committed - p.cumulative_delivered) / nullif(p.days_remaining, 0)) AS daily_needed FROM fct_pacing_daily p JOIN dim_campaigns c USING (campaign_id) WHERE p.day = CURRENT_DATE AND p.pacing_index < 0.95 AND p.days_remaining < 5 -- behind AND out of runway ORDER BY p.pacing_index ASC; -- worst first → priority bump

What do we owe, and when did caps start eating reach? — liability + LAG

Finance wants the standing liability; the cap-policy team wants to know the moment a campaign started bleeding fills to frequency caps. The first is a straight roll-up of the obligation ledger. The second is the classic onset detector: LAG over an hourly cap-block rate to find where it first crossed a line — the same gaps-and-onsets shape that finds a surge or an incident.

SQL · STANDING MAKE-GOOD LIABILITY, AND CAP-BLOCK ONSET
-- (a) What Netflix owes right now, by advertiser — the balance sheet. SELECT c.advertiser_id, count(DISTINCT mg.campaign_id) AS campaigns_owed, sum(mg.owed_impressions) AS impressions_owed, round(sum(mg.owed_value_usd), 2) AS dollars_owed FROM fct_makegood_obligations mg JOIN dim_campaigns c USING (campaign_id) WHERE mg.status IN ('pending','scheduled') GROUP BY c.advertiser_id ORDER BY dollars_owed DESC; -- (b) When did frequency capping START throttling a campaign's fills? -- LAG finds the hour the cap-block rate first crossed 20%. WITH hourly AS ( SELECT date_trunc('hour', opportunity_ts) AS hr, count(*) AS opps, count(*) FILTER (WHERE unfilled_reason = 'frequency_capped') * 1.0 / count(*) AS cap_rate FROM fct_ad_opportunities WHERE opportunity_ts >= CURRENT_DATE - 2 GROUP BY 1 ) SELECT hr, round(100 * cap_rate, 1) AS cap_block_pct FROM ( SELECT hr, cap_rate, lag(cap_rate) OVER (ORDER BY hr) AS prev_rate FROM hourly ) w WHERE cap_rate >= 0.20 AND coalesce(prev_rate, 0) < 0.20 -- the onset ORDER BY hr;

§ 08 — THE DASHBOARDThree audiences, one screen

A senior design ends with observability, because every careful accounting choice above is invisible without it. The inventory dashboard serves three audiences at once — ops watches fill, account managers watch pacing, finance watches the liability — and each reads a different definition of "healthy."

OPS · YIELD
fill rate by slot_type, unfilled-reason mix (the leak diagnosis), estimated revenue lost on null-creative rows, cap-block rate — a rising cap-block share means the frequency policy, not demand, is throttling yield.
ACCOUNT MGMT · PACING
campaigns under 0.95 with <5 days left, the daily-needed catch-up rate, and the distribution of pacing_index across the active book — a left-skewed cluster is a portfolio-wide under-delivery risk, not one bad campaign.
FINANCE · LIABILITY
pending make-good $ on the books, make-good fulfilled this month, and the owed-impression backlog that next period's bonus inventory has to absorb — this is a balance-sheet line, refreshed nightly, not a vanity metric.
Inventory Ops — Ad Tier FRI 21:10 PT · ALL CAMPAIGNS · NIGHTLY CLOSE PENDING
Fill Rate
88.6%
Cap-Block Rate
7.1%
Est. Revenue Lost
$1.4M/d
Campaigns At Risk
12
Unfilled-reason mix · last 24h (share of empty slots)
no_eligible_demand frequency_capped content_unsuitable demand-led leak
Recognized CPM
$31avg
Median Pacing Idx
1.02
Make-Good Pending
$6.8M
Owed Impr. Backlog
214M
MG Fulfilled MTD
$2.1M
Avg Freq / User
2.4×
FIG. 2 — The story a healthy-but-tightening night tells: fill is fine, but the cap-block rate and revenue-lost tiles are amber, twelve campaigns are out of runway, and the make-good liability is climbing — under-delivery is converting into dollars owed in real time.

Read the tiles together and the dashboard narrates a specific tension. Fill rate looks healthy at 88.6%, which a logging-only model would call a win and stop. But the unfilled-reason bar says most of the empty slots are no_eligible_demand — a sales problem, not an ops one — and the cap-block rate is creeping up, which means the frequency policy is starting to suppress otherwise-fillable inventory. Meanwhile twelve campaigns are behind with the month nearly out, and the make-good liability has already grown to $6.8M. None of that is visible from impressions alone. It is visible only because the empty slots were stored as first-class rows.


§ 09 — THE RUBRICWhat was actually being tested

Strip the ad-tier specifics away and the question was testing five judgments, each of which generalizes far beyond CTV advertising:

THE ABSENCE
Modeling what didn't happen as a first-class row. The unfilled opportunity is the only artifact that can answer "why did we leave money on the table?" — and the instinct to store absence, not just events, is the senior move in any inventory, funnel, or yield system.
THE INVARIANT
Enforcing correctness at write time, not query time. A frequency cap held by an atomic guarded increment, and an opportunity conserved by a filled-XOR-unfilled constraint, mean the numbers can't drift — fill rate is exact because the schema refuses ambiguity.
TEMPO
Separating the millisecond decision path from the nightly and monthly settlement. A contract is measured against a calendar; live consistency belongs only on the cap read, where staleness costs money in real time.
FAILURE SHAPE
Failing closed on purpose. When the cap store is unreachable, decline and log an honest empty row — a degraded experience you can diagnose beats a broken contract you can't undo.
HONESTY
Turning a revenue policy into queryable finance. Pacing as a snapshot and make-goods as a booked liability move the contractual layer out of a sales spreadsheet and onto the balance sheet, where a model has to survive quarter-end.
Anyone can count the ads that played. The interview is won by the candidate who insists on a row for the ad that didn't — because that empty row is where the revenue, the diagnosis, and the contract all live.— CLOSING ARGUMENT