Free full guide

Ad Click Aggregator

Design an ad click aggregation system that ingests millions of click events per second and serves near-real-time plus historical aggregate metrics to advertisers.

00

Practice checkpoints

The interview rhythm stays compact, so the page can spend attention on the actual design decisions.

  1. 01
    Clarify scope
  2. 02
    Requirements + scale
  3. 03
    API + data model
  4. 04
    Draw architecture
  5. 05
    Deep dive
  6. 06
    Trade-off decision
01

Requirements that shape the design

Do not only state requirements. Ask for them. Each card pairs the design constraint with a clarification question you can say out loud before drawing the architecture.

Functional requirements

01What exactly counts as one click — the user's tap, or landing on the advertiser site?

Users click an ad and get a 302 redirect to the advertiser site — the click is recorded server-side on that hop, so ad blockers and flaky clients cannot lose it.

02What granularity and freshness do advertisers need?

Advertisers query click metrics over time at a minimum granularity of one minute, available within about a minute of the click.

03If the same click arrives twice — a double-tap or a retry — should it count once?

A duplicate click on the same impression counts once — every shown ad already carries a unique impression ID.

04Clicks only, or impressions and CTR too?

Clicks first. Impressions and CTR (click-through rate) come later as a known follow-up.

05Which granularities beyond one minute - hourly, daily?

Advertisers also browse hourly and daily views; one minute stays the finest granularity.

06Do budget caps need to stop ads in near real time?

Yes — an ad that hits its budget cap must stop serving within about a minute.

Out of scopeAd targeting and ad serving (which ad to show) · Cross-device click tracking · Offline marketing channel integration

Non-functional requirements

01What peak click rate should we design for?

Peak 10K clicks per second with no loss. Click data is billing data, so the pipeline is built to never drop a click.

02How fast must advertiser dashboards answer?

Queries answer in under a second across arbitrary time ranges.

03How fresh do the numbers need to be?

Near-real-time: a click should be queryable within about a minute of happening.

04Do these counts feed billing directly — so over-counting is never acceptable?

No — duplicates and retries must never inflate the numbers; these are billing-grade figures.

05How exact must the numbers be — is a brief discrepancy acceptable if corrected later?

A small transient error in the first minutes is tolerable, but billing numbers must be exact and converge within a day.

Keep asking — the interview is a conversation

Real interviews probe far more than a tidy list. These are the scope questions that separate candidates who interrogate the problem from those who recite it.

  • How long must minute-level data stay queryable — 90 days? Two years?
  • Are bot and fraud clicks in scope, or filtered upstream?
  • Which timezone defines "a day" for advertiser reports?
  • Do advertisers need unique users per minute, or just click counts?
  • How late can a click legitimately arrive and still count?
02

Numbers that force architecture decisions

Treat every estimate as a pressure that justifies a component: cache, queue, partition, replica, worker pool, or fallback path.

01

Daily event volume

Peak 10K clicks/s, ~100M clicks/day100M events × ~100 B ≈ 10 GB/day raw

Raw events are cheap to keep forever in a data lake — that is what makes daily reconciliation possible.

02

Aggregate row count

Assume ~10M active ads × 1 row/minute10M ads × 1,440 minutes in a day (24 h × 60 min) ≈ 14B rows/day worst case — but only ads with clicks emit rows, realistically ≪ 1%

Sparse minute rows keep the OLAP store small enough for sub-second range scans.

03

Dedup cache size

Assume an impression ID stays valid for a ~1-hour click window10K/s × 3,600 s × ~50 B ≈ 1.8 GB

The whole dedup window fits in one Redis cluster comfortably.

04

Loss window if the stream processor dies

Flink checkpoints once per minute-windowcrash → replay from last checkpoint ≈ at most 1 minute recomputed

Kafka retention (days) plus checkpoints means a processor crash recomputes, never loses.

05

Why not query raw events

The raw click table vs one advertiser dashboard query over 30 daysthe raw table grows 3B rows/month; even an indexed per-ad slice at minute granularity scans millions of rows per query — nowhere near sub-second

Pre-aggregation is not an optimization here — it is the design.

Decision example

The numbers

Ten thousand clicks a second is money flowing in: every lost click is unbilled spend, and every double-counted click is an angry advertiser.

My choice

Record the click on the redirect hop and write it to a durable stream (Kafka) before anything else. A stream processor then aggregates continuously in one-minute windows and flushes the results into an OLAP store that the dashboards query. Each shown ad carries a signed impression ID, and a Redis check drops duplicates. Once a day, a batch job re-reads the raw events from the lake and corrects the streamed numbers.

Avoid

What I would NOT do: store raw clicks in a database and run GROUP BY on every dashboard query. Three billion rows per 30-day query kills it. I also would not partition the stream purely by ad ID, since one viral ad melts a single partition. The fix is simple: append a random suffix to hot ad IDs, spread them over N partitions, and strip the suffix when writing the aggregates. The whole problem comes from picking the wrong partition key.

Change if

If advertisers accept 5-minute freshness, I would drop the streaming layer entirely and run micro-batches — half the moving parts, same accuracy, just slower.

03

Architecture path

One complete picture first, then each path as its own diagram — the write path and the read path carry different traffic and justify different components.

Complete picture

Overview — every component

ClientClick IngestionAPIKafka (eventlog)Stream ProcessorOLAP StoreDedup Cache(Redis)impression seen?Data Lake (rawevents)archive for reconciliationDaily BatchReconcilercorrects the numbers
  • Write to the stream before the dedup cache — losing the cache must never lose clicks.
  • Dashed arrows are off the real-time path: archiving and the daily correction.
  • The daily batch re-reads raw events and overwrites any streamed number that drifted.

Path 1

Click path — record, then redirect

ClientClick IngestionAPIKafka (eventlog)Stream ProcessorOLAP Store
  • The user is redirected (302) the moment the click is safely written to Kafka — they never wait for any counting.
  • Counting happens behind the redirect: the stream processor folds clicks into per-minute rows and writes them to the OLAP store.
  • A click shows up in dashboards within about a minute — the freshness promise from the requirements.

Path 2

Query path — dashboards read aggregates only

AdvertiserDashboardMetrics APIOLAP StoreMinuteAggregaterows
  • Dashboards never touch raw click events — they only read the pre-aggregated minute rows.
  • That is why any time range answers in under a second: the heavy work already happened at write time.
04

API and data model

Before optimizing, make the contract inspectable: endpoints, entities, ownership, retries, and state.

GET/ads/{ad_id}/click?impression_id=

res302 Location: advertiser_url

The redirect hop records every attempt durably, then the stream processor deduplicates by impression_id before any billing aggregate or sink write. A cache may accelerate duplicate checks, but billing correctness must survive cache loss and replay.

GET/metrics?ad_id&from&to&granularity=1m

res200 [{ minute, clicks, unique_users }]

Served from the OLAP store, never from raw events — GROUP BY over billions of rows is what this design exists to avoid.

Core entities

ClickEvent

impression_id (PK) · ad_id · user_id · clicked_at

The impression ID is minted when the ad is shown and HMAC-signed, so clicks cannot be forged or replayed.

MinuteAggregate

ad_id · minute · clicks · unique_users

What the OLAP store serves; one row per ad per minute.

05

Deep dive directions

Pick one lane for the final third of the interview. Each lane gives you the topic, the interviewer question it should answer, and the failure mode to avoid.

Focus

The same click, twice

Ask

A user double-clicks, or a retry fires. How does the count stay at one?

Answer

Every shown ad carries a signed impression ID: the dedup cache drops repeats inside the click window, and the minute upsert is idempotent on that ID — a retry can never add a second count.

Avoid

Deduping on user+ad — retargeting legitimately shows the same ad to the same user again.

Focus

One ad goes viral

Ask

A single ad suddenly takes half of all clicks. Which component hits its limit first, and how do you spread that load?

Answer

Salt the hot key — split ad_id into ad_id#0..N so the load spreads across partitions, then a tiny merge step folds the partial counts back into one minute row.

Avoid

Partitioning the stream by ad ID alone — one viral ad then sends every event to the same partition, creating a hot key.

Mechanism sketch

One viral adSalted keys(ad_id#0..7)ParallelpartitionsMerged minuterow

Salting spreads one hot key across partitions; a tiny merge step reassembles the true count.

Focus

The stream processor crashes

Ask

Flink dies mid-window. How many clicks are lost, and how do you know?

Answer

Nothing is lost: Kafka retains the raw events, the processor restarts from its last checkpoint and recomputes at most one window, and idempotent upserts overwrite instead of double-count.

Avoid

Trusting the stream as the source of truth — Kafka retention plus checkpoints means replay, not loss.

Mechanism sketch

Kafka (eventlog)Checkpoint storeRestartedprocessorOLAP upsert

The restarted processor reloads the last checkpoint and replays Kafka from that offset; idempotent upserts overwrite the recomputed minute instead of double-counting it.

Focus

Why keep a batch layer at all

Ask

Streaming already aggregates in real time — what does the daily batch job add?

Answer

Streamed numbers drift — late events, crashes, bugs. Once a day the batch re-reads the raw events and overwrites every minute it recomputes: the stream buys freshness, the batch guarantees the bill.

Avoid

Skipping reconciliation — billing-grade accuracy cannot rest on a best-effort stream.

Mechanism sketch

Data Lake (rawevents)Daily BatchReconcilerOLAP Store

Once a day the batch recomputes every minute from raw events and overwrites drifted rows — the stream buys speed, the batch guarantees the bill.

Focus

A click arrives late

Ask

An event lands 5 minutes after its click. Which minute does it count toward, and how long do windows wait?

Answer

Count by the click's event time, not arrival time: windows wait a bounded lateness (a few minutes), and anything later is folded in by the daily reconciliation.

Avoid

Ignoring event-time vs processing-time — counting by arrival time quietly shifts money between minutes.

Ready to practice?

Talk through Ad Click Aggregator out loud and get AI scoring on the explanation.

Practice this with AI →