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

01Where exactly do we capture the click — on the client, or on the redirect?

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.

03What happens when the same click arrives twice?

The same impression clicked twice counts once — every shown ad carries a unique impression ID, and counting is idempotent on it.

04Clicks only, or impressions and CTR too?

Clicks day one - but every click already carries impression_id, so impressions and CTR (click-through rate) can join later without re-instrumenting the pipeline.

05Which granularities beyond one minute - hourly, daily?

Minute is the base truth; hourly and daily are rollups derived inside the OLAP store (the pre-aggregated analytics database) - a dashboard query never recomputes from raw events.

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

Yes: budget enforcement consumes the same minute aggregates - an ad hitting its cap stops serving within about a minute, no separate counting system.

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 ingestion survive?

Peak 10K clicks per second without loss — click data is billing data; the pipeline is built for zero-loss collection.

02How fast must advertiser dashboards answer?

Queries answer in under a second across arbitrary time ranges — which is what forces pre-aggregation into an OLAP store.

03How fresh do the numbers need to be?

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

04Can we ever double-count?

No — duplicates and retries never inflate the numbers; idempotency keys off a signed impression ID.

05Is the streaming number the final truth?

No — the stream gives speed; a daily batch job over the raw event lake re-aggregates and corrects, so accuracy is guaranteed even when the stream hiccups.

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.

  • Is click capture on the redirect hop acceptable, or must it be client-side beacons?
  • Minute granularity enough, or do we need sub-minute windows?
  • How long is a click on an impression valid — what dedup window?
  • Do advertisers need unique users per minute, or just click counts?
  • How exact must real-time numbers be, given the daily reconciliation?
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

10M active ads × 1 row/minute10M × 1,440 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

Impression IDs 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

I would put the click on the redirect hop and write it to a durable stream (Kafka) before anything else — then aggregate continuously in one-minute windows with a stream processor, and flush results into an OLAP store 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 per dashboard query — three billion rows per 30-day query kills it. I also would not partition the stream purely by ad ID: one viral ad melts a single partition. The fix is boring and effective — append a random suffix to hot ad IDs, spread them over N partitions, and strip the suffix when writing aggregates. Picking the wrong partition key is how you build a hot partition by hand.

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

Draw the write path and the read path as separate lanes — they carry different traffic and justify different components.

Ad Click Aggregator serviceClick path — record, then redirectClientClick IngestionAPIKafka (event log)Stream ProcessorOLAP StoreQuery path — dashboards read aggregates onlyAdvertiserDashboardMetrics APIOLAP StoreMinuteAggregaterowsDedup Cache(Redis)impression seen?Data Lake (rawevents)archive for reconciliationDaily BatchReconcilercorrects the numbersservicestorequeue / logexternalasync

Write to the stream before the cache — cache loss must never mean click loss. Dashed = off the real-time path. The daily batch re-reads raw events and overwrites any streamed number that drifted.

The 302 answers as soon as the event is durably in Kafka; windowed aggregation and OLAP flush happen behind it, within the one-minute freshness budget.

Every dashboard query hits pre-aggregated minute rows; raw events exist only for replay and the daily reconciliation.

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.

01

FocusThe same click, twice

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

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

02

FocusOne ad goes viral

AskA single ad takes half of all clicks. What melts first, and how do you spread the load?

AvoidPartitioning the stream by ad ID alone — that is building a hot partition by hand.

03

FocusThe stream processor crashes

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

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

04

FocusWhy keep a batch layer at all

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

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

05

FocusA click arrives late

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

AvoidIgnoring 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 →