cswork / libraryOpen practice workspace

Interview guides · The Trade Desk

The Trade Desk Software Engineer Interview Guide

The Trade Desk Software Engineer interview guide: 4 reported rounds, cache and thread-safety design questions, system design drills and a 7-day plan.

The 60-second brief

The Trade Desk is an ad tech company. Candidate-facing descriptions of this Software Engineer role center on real-time bidding engines, distributed caching, audience targeting data, streaming data pipelines and reporting analytics. Those descriptions stress low latency, high throughput, concurrency and memory management, and list C#, Java, C++ and Python as the languages teams write in. The reported questions include cache design, thread safety, refactoring existing code and high-QPS system design alongside algorithm problems such as sliding windows and progressive multi-part coding.

This guide covers the four rounds candidates report for The Trade Desk Software Engineer role: the automated technical assessment, the recruiter discussion, the technical screening call and the Superday panel. It also covers the question categories reported across them: progressive multi-part coding, object-oriented and low-level design with thread safety, distributed system design for high-throughput ad tech, and behavioral questions on trade-offs, ownership and disagreement. The original drills add SQL, debugging and design practice set in bidding and billing scenarios. Three of them come with worked solutions.

Focus areas

  • Restate attribution results without double-counting credit
  • Enforce budget caps across stateless distributed bidders
  • Make impression billing idempotent under at-least-once delivery

Overview

Candidate-facing descriptions of the Software Engineer role at The Trade Desk describe designing, building and operating ad-bidding platforms, real-time data streaming engines and data pipelines. The areas named are real-time bidding (RTB) engines, distributed caching systems, audience targeting databases and user-facing reporting analytics. The languages listed are C#, Java, C++ and Python. The same descriptions emphasize concurrency, thread safety, low-latency networking and efficient memory management.

The reported questions follow the same themes. On the object-oriented side there is an N-way set-associative cache with pluggable LRU and MRU eviction that must be thread-safe, an in-memory rate limiter with per-client limits, a refactor of buggy legacy code, and a class design for an event logging system with thread pools and storage sinks. The reported distributed design questions include a real-time ad bidding engine under a sub-10ms latency SLA, a streaming reporting pipeline, cache invalidation and replication across regions, and an asynchronous queue with delivery guarantees. On the coding side, candidates describe progressive problems that start simple and add constraints in later parts.

Use this guide to prepare in that order: get the coding fundamentals solid, then build caches and concurrency-safe classes until you can do them from a blank file, then practice system design with an explicit latency budget. The rounds and questions are what candidates report, not a published process. The drills are original practice, and the SQL, coding and design drills with worked solutions show the level of detail to aim for when you explain an answer.

The The Trade Desk interview process

The Trade Desk candidates report 4 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.

Automated Technical Assessment

Candidates describe this as an initial online assessment of technical skills. The sources do not give its format, platform or number of problems. The reported coding questions are the best material to prepare with: contiguous subarrays matching a condition, reordering string patterns or log lines, 2D array problems solved in O(1) auxiliary space, a sliding-window maximum or minimum over a stream, and a progressive three-part problem that moves from string manipulation to matrix traversal to dynamic programming. Nobody here ties a particular question to this round. If no one is available to answer questions, the prompt's constraints and examples are all you have, so read them twice. Get a correct version working before you optimize, and test the edge cases yourself before you submit.

What to demonstrate

  • Whether the solution is correct on edge cases like empty input, a single element, duplicates and all-equal values, without anyone prompting you
  • Whether the complexity you choose fits the stated input bounds rather than whichever pattern you recognized first
  • Whether a working baseline exists before any optimization, so a partial optimization never leaves you with nothing that runs

How to prepare

  • Implement a sliding-window maximum with a monotonic deque from a blank file, then change it to a minimum and to a stream that arrives one value at a time
  • Drill in-place 2D array problems such as rotating a matrix and zeroing rows and columns, and state the auxiliary space of each version
  • Before each practice submission, write four edge-case inputs and their expected outputs, and run them
  • Practice in the language you will actually use, with a timer running, so the pressure of an automated assessment is familiar

Recruiter Discussion

Candidates report that this conversation covers your background, team fit and compensation expectations. It is not a coding round. Assume a non-engineer may need to retell your projects, so make them survive a paraphrase. Map your experience to the areas the role descriptions name: backend services, concurrency, caching, streaming data and high-throughput systems. Say plainly where you have no experience. This is also the cheapest place to learn the format of the next rounds, so bring questions about it.

What to demonstrate

  • Whether your background can be summarized accurately in a few sentences, with what you owned separated from what the team did
  • Whether your experience lines up honestly with the areas the role describes, including gaps you name yourself
  • Whether you have a considered answer on compensation expectations and team preferences instead of improvising one

How to prepare

  • Write each headline project as two sentences with no internal codenames: what was breaking or needed, what you changed, and the measured result
  • Decide your compensation range from your own research before the call, and have one sentence ready on what drives it
  • Prepare questions about the screening call (live coding or low-level design, which languages are allowed, which editor) and about how the Superday sessions are split
  • Name the one concurrency or performance problem you have handled in production. It is your strongest link to the reported questions

Technical Screening Call

Candidates describe this as a phone assessment built around live coding or low-level design. The reported object-oriented questions are the natural material to prepare with, though the sources do not say which one shows up in which round: an N-way set-associative cache with custom eviction strategies (LRU, MRU) that stays correct under concurrent access, an in-memory thread-safe rate limiter with configurable windows and per-client limits, and a refactor of buggy legacy code without breaking behavior. Start by stating the interface and the concurrency contract, then build the core, then add eviction policies and locking. Talk through your reasoning as you go, since the interviewer may be on the phone and can only follow what you say.

What to demonstrate

  • Whether the class boundaries allow extension, for example eviction behind an interface so MRU is a new class and not an edited branch
  • Whether get and put run in O(1) and you can say why, using a hash map plus a doubly linked list per set
  • Whether thread safety is designed and not bolted on: what the lock protects, its granularity, and why get needs exclusive access under LRU
  • Whether you clarify requirements before writing code: capacity, set count, key hashing, and behavior on a miss

How to prepare

  • Build LRU and MRU caches from a blank file in O(1) per operation. Note that both share one linked list and evict from opposite ends
  • Extend that to an N-way set-associative cache with one lock per set, and explain why a single global lock serializes every caller
  • Implement a per-client rate limiter twice, once as a sliding window log and once as a token bucket, and compare their memory and precision
  • Rehearse by explaining aloud while you type in a plain editor, so your reasoning is clear without anyone watching your screen

Panel Round (Superday)

Candidates report a final panel called the Superday: 4 to 5 back-to-back sessions covering algorithms, object-oriented design, distributed system architecture, and leadership or cultural fit. The sources do not give the order of the sessions or which reported question lands in which one. Two things to prepare for are the depth of each category and staying sharp across consecutive sessions. The reported system design questions include a real-time ad bidding engine with a sub-10ms latency SLA, a streaming reporting pipeline with fast queries, cache invalidation and replication across regions, and a queueing system with delivery guarantees. For the latency-bound designs, build around an explicit budget: what sits in memory, what is precomputed, and what the system does when a dependency is slow.

What to demonstrate

  • Whether a design states its latency budget and keeps database lookups off the synchronous bid path
  • Whether single points of failure and bottlenecks are named, with a concrete remedy for each
  • Whether the object-oriented design and algorithms sessions reach the same standard of correctness and thread safety as a fresh first session
  • Whether behavioral answers show ownership of a trade-off, a disagreement or a production bottleneck, with a measured outcome

How to prepare

  • Run a back-to-back mock of at least three sessions (coding, object-oriented design, system design) with no break, then note where quality dropped
  • Design the ad bidding engine with a per-hop deadline and a no-bid fallback, then work through the debugging drill on late bids and the bidder snapshot drill
  • Practice a multi-region cache invalidation answer that names the consistency you give up and where stale reads are acceptable
  • Prepare four behavioral stories covering a speed-versus-quality trade-off, an architecture disagreement, ambiguous requirements and a production bottleneck you fixed

Where candidates lose points

Handing in a single-threaded cache or rate limiter when the reported prompt says thread-safe

Decide the concurrency contract before writing the core. In an N-way set-associative cache, one lock per set lets unrelated keys proceed in parallel, while one global lock serializes every caller. Under LRU, get updates recency, so it mutates the list. A read-write lock where get takes the read side is a bug, not an optimization. Say this out loud, then show which fields each lock protects.

Hard-coding LRU inside the cache class when the reported question asks for custom eviction strategies

Put eviction behind a small interface (record access, choose a victim) and inject it. MRU then becomes a second implementation. It uses the same doubly linked list and evicts from the other end, so one extra class shows the design is extensible. Mention how a new policy would be added without touching the cache's lookup path.

Writing part one of a progressive problem so tightly that part two forces a rewrite

Progressive multi-part coding problems are reported for this role. Keep parsing, the core traversal and the output as separate functions with clear inputs, and state the complexity of each part before moving on. When a later part adds scale or asks for dynamic programming, you can then replace one function instead of starting from a blank file with less time left.

Designing the ad bidding engine with synchronous database or service lookups on the request path

The reported bidding question sets a sub-10ms latency SLA, so start with a budget, not boxes. Keep campaign and targeting state in an in-memory snapshot rebuilt off the request path, and give each downstream call the remaining deadline rather than a fixed timeout. Treat a no-bid as the safe failure when the budget runs out. Also say how you would detect a stale snapshot.

Rewriting the reported legacy codebase from scratch instead of refactoring it

The reported prompt asks you to improve structure without breaking existing functionality. First name the smells and any race conditions you see, then pin the current behavior with a few tests, then change one thing at a time and say why each step is safe. A plan explained step by step before you touch the code is part of the answer.

Questions & practice

Solve a progressive 3-part problem starting with a basic string manipulation, building to a matrix traversal, and concluding with a dynamic programming optimization.

Medium · data structures and algorithms

Approach

  1. Name the brute-force solution and its complexity before improving on it.
  2. Choose the data structure from the access pattern, not from familiarity.
  3. State the target complexity and say which constraint rules the naive version out.

Follow-up

  • Which test case would catch an off-by-one here?
  • How does this change if the input no longer fits in memory?

Compute eligible serving time per flight minus overlapping pauses

Medium · intervals · sweep line · sorting

Each line item has flight_start_ts and a nullable flight_end_ts, and its change history yields pause intervals [pause_start, pause_end) - up to 10^7 pauses across 10^6 line items, unsorted, frequently overlapping or nested, some with a null end meaning still paused. For each line item return the number of seconds it was eligible to bid inside its flight, clipped to a reporting horizon where the flight is open-ended. Give the complexity, and state your interval convention explicitly before you write any code.

Approach

  1. Fix the convention first: half-open [start, end), integer epoch seconds in UTC, a null pause_end resolved to the horizon and a null flight_end_ts likewise. Mixing closed and half-open ends is what produces the off-by-one-second sums nobody notices until a full-day flight reports 86401 seconds.
  2. Group by line_item_id, sort each group by pause_start, then merge: hold a running [cur_start, cur_end), extend cur_end when the next start is at most cur_end, otherwise emit and restart. Sorting dominates at O(m log m) over m pauses.
  3. Clip merged pauses to the flight interval before subtracting, not after. A pause can begin before the flight opens and end after it closes, and an unclipped subtraction removes time the line item never had in the first place.
  4. Eligible time is flight length minus the summed clipped merged pause length. With merging done first this cannot go negative, so assert it and treat a firing assertion as a bug in the merge rather than as unusual data.
  5. For the batch shape given, sort the entire array once by (line_item_id, pause_start) and sweep it. Grouping then costs nothing and memory stays at one pass, instead of building a map of 10^6 vectors.

Follow-up

  • The same figure now has to be maintained incrementally as pause events stream in rather than recomputed as a batch. What changes?
  • How would you answer 'how many line items were eligible at instant T' over the same input, and what does that cost?
  • A pause row arrives whose end precedes its start. Do you reject it, clamp it, or drop the line item from the report, and who finds out?

Select the top eight candidates under a shrinking deadline budget

Medium · top-k · heap · deadline propagation

Candidate selection hands the scorer between 1 and 20,000 eligible line items per impression slot. The expensive second-stage model can score only 8 of them inside the request's remaining budget, and every candidate carries a cheap precomputed score readable in O(1). Return the 8 highest by cheap score, deterministically, and describe what you do when the remaining budget is smaller than the cost of scoring 8. Beat O(C log C), and state the time and space complexity you are targeting.

Approach

  1. Use a bounded min-heap of size k = 8: push the first 8, then for each remaining candidate compare against the root and replace only when larger. That is O(C log k) time and O(k) space, and it consumes candidates as posting-list intersection emits them rather than requiring the full array up front.
  2. Compare against quickselect honestly rather than reciting its average case: it is expected O(C) but needs the whole candidate array materialised and mutable, its worst case is quadratic without an introselect fallback, and at C = 20,000 with k = 8 the log k factor is 3, so the constant decides this, not the asymptotics.
  3. Make ties deterministic by ordering on (cheap_score, line_item_id). Without it, two replicas handed the same candidate set can return different eights, which turns bid replay and any experiment comparison into noise you cannot attribute.
  4. Derive k from the deadline rather than configuring a timeout for the second stage: divide the time remaining on the request by the measured per-candidate scoring cost at a high percentile, floor at zero, and when it reaches zero either bid from the cheap score alone or no-bid.
  5. Measure that per-candidate cost at a tail percentile rather than at the mean, because a mean-derived k overruns the deadline on exactly the requests where the fleet is already slow.
  6. Account for the fan-out: one bid request carries several impression slots, so this selection runs per slot against a budget that is per request. The affordable k shrinks as slots are processed.

Worked solution (25 min)

  1. Write the bounded min-heap loop and state the steady-state work per candidate: one comparison against the root, and a sift only when it replaces.
  2. Do the arithmetic for C = 20,000 and k = 8 - roughly C root comparisons plus at most C sifts of depth 3 - against a full sort at C log2 C, about 20,000 x 14.
  3. Write the formula for the affordable k from remaining_micros and the p99 per-candidate cost, and evaluate it at 2 ms and at 200 microseconds remaining.
  4. Add the (cheap_score, line_item_id) tie-break and state the determinism property it buys.

Expected result: A size-8 min-heap giving O(C log k) time and O(k) space with a deterministic tie-break, plus an affordable k derived from the request's remaining deadline and a stated degradation to cheap-score-only bidding or a no-bid when that k reaches zero.

Verify it yourself

  • Running the selection twice over the same candidate set in a different order returns the same eight line items.
  • The heap never holds more than 8 entries, at any value of C.
  • With the remaining deadline forced to zero, the path produces a no-bid rather than a late bid.

Follow-up

  • C becomes 2 million because one advertiser created a line item per postcode. What in your design changes and what does not?
  • The cheap score is a replica of a model refreshed every ten minutes. How does that staleness change which candidates you are willing to drop?
  • You need the top 8 per slot, but the union across 6 slots must not exceed 20 second-stage calls. How do you allocate them?

Explain why this billing-notice query ignores the primary key

Medium · indexing · sargability · partitioning

delivery_event is RANGE partitioned by received_ts date, has PRIMARY KEY (impression_id, event_type), and carries line_item_id, creative_id, exchange_id, clearing_price_micros, clearing_currency, event_ts, received_ts and ivt_status. A PostgreSQL reporting query filters line_item_id = $1 AND event_type = 'billing_notice' AND received_ts::date BETWEEN $2 AND $3 and sums clearing_price_micros. EXPLAIN shows a sequential scan on every partition. Give three separate reasons the existing index cannot serve it, rewrite the predicate so partition pruning applies, and specify the index you would add including column order.

Approach

  1. Reason one is coverage: the primary key contains neither line_item_id nor received_ts, and its leading column is a UUID with no predicate supplied, so there is no start point to seek to and nothing in the index that answers the filter. A btree scan begins from a prefix of its own column order or it begins from the start.
  2. Reason two is the cast on the partition key. received_ts::date applies a function to the column, so the planner cannot map the predicate onto partition boundaries and prunes nothing. Rewrite as a half-open range on the raw column, received_ts >= $2 AND received_ts < $3 + interval '1 day', and note that timestamptz to date conversion depends on the session TimeZone, so the rewrite must pin the zone or the day boundaries move.
  3. Reason three is parameter typing. If $1 arrives as numeric rather than bigint, the comparison casts the column rather than the constant, and a btree on line_item_id becomes unusable. Read the EXPLAIN for which side of the operator the cast landed on, because a cast on the constant is free and a cast on the column is fatal.
  4. Specify the index as (line_item_id, received_ts) with a partial predicate WHERE event_type = 'billing_notice', created on each partition. Equality column first so the scan starts at one point, range column second so the matching rows are contiguous, and the partial predicate shrinks the index to the minority event type that carries a price.
  5. Consider INCLUDE (clearing_price_micros) for an index-only scan, and state the precondition rather than asserting the win: an index-only scan requires the partition's visibility map to be largely all-visible, which holds for a sealed older partition and does not hold for the one currently receiving writes.

Worked solution (25 min)

  1. Count the partitions named in the EXPLAIN output; if it is all of them, pruning failed and the predicate is the reason.
  2. Rewrite the date filter as a half-open timestamptz range and re-run EXPLAIN to confirm pruning to only the overlapping partitions.
  3. Create (line_item_id, received_ts) WHERE event_type = 'billing_notice' on one partition and compare the plan and the buffers read with EXPLAIN (ANALYZE, BUFFERS).
  4. Inspect the EXPLAIN filter text for a cast applied to the column side of the comparison rather than the parameter side.

Expected result: Pruning to exactly the partitions overlapping the requested range, an index scan on (line_item_id, received_ts) restricted by the partial predicate, and a distinct reason given for each of the three failures: the primary key does not contain the filtered columns, a function on the partition key defeats pruning, and a parameter type forces a column-side cast.

Verify it yourself

  • EXPLAIN names only the partitions whose bounds overlap the requested range.
  • Buffers read fall by orders of magnitude against the sequential scan, and the row count returned is unchanged.
  • The rewritten predicate returns the same rows as the original under the session's TimeZone, confirming the change moved the plan rather than the result.

Follow-up

  • The partial predicate is a literal event type. What do you do when the same report also needs click counts, and is a second partial index better than one wider index?
  • line_item_id = $1 matches 40% of the rows in a partition. Does the planner still choose the index, and what statistic decides?
  • How do you create this index on a live partitioned table without blocking inserts?

Design the serving snapshot a bidder reads instead of line_item

Easy · denormalisation · versioning · staleness

line_item(line_item_id, status, bid_strategy, bid_value_micros, daily_budget_micros, pacing_mode, frequency_cap_per_day, flight_start_ts, flight_end_ts, targeting_hash, version) is authoritative; creative_approval(creative_id, exchange_id, status, expires_ts) carries per-exchange approval. A stateless bid service answers within tens of milliseconds and cannot query either table, so it loads a denormalised snapshot rebuilt every few minutes and held in memory. Specify the snapshot's record shape, which fields you copy and which you precompute, and the mechanism by which a bidder decides its snapshot is too stale to serve from, plus what it does at that point.

Approach

  1. Derive the shape from the read pattern before listing fields. One bid request carries several impression slots and each slot evaluates many candidates inside a tens-of-milliseconds budget, so every value the candidacy test reads has to be resident and flat. A per-candidate lookup into a second structure is the thing the snapshot exists to remove.
  2. Copy status, flight_start_ts, flight_end_ts, pacing_mode, frequency_cap_per_day, bid_strategy and bid_value_micros as scalars. Precompute what a join would otherwise cost: the compiled posting lists keyed on targeting predicate values, and, per creative, a bitmask over exchange_id of the exchanges where approval is currently present and unexpired.
  3. expires_ts makes approval time-dependent, so decide where that predicate is evaluated. Baking eligibility at build time makes the bid path a bitmask test and inherits up to one build period of error; carrying expires_ts into the record moves the comparison onto the bid path and costs one timestamp compare per candidate. Pick one and say which error you accepted.
  4. Handle freshness at the snapshot level rather than the row level. The per-line-item version column only advances when that line item changes, so a bidder whose build pipeline stopped sees identical versions indefinitely. Put generation_id and built_at in a snapshot header, and have the index service emit a heartbeat so absence of new builds is itself observable.
  5. Fail closed past the bound: drop candidates rather than bid from a snapshot older than the limit, and state the limit as build period plus fan-out time plus the interval at which a bidder checks. That sum, not the build period alone, is the number an advertiser hears when they ask how fast a pause takes effect.

Follow-up

  • A build ships with a corrupt posting list and every bidder has already loaded it. What is the rollback path, and how long is the fleet wrong for?
  • One advertiser holds 10^5 line items and the index build is now the bottleneck. What do you change: build frequency, partitioning of the index, or the staleness bound?
  • The bidder is told its snapshot is stale. Is dropping candidates always right, or is there a case where serving from a slightly old snapshot is the safer failure?

Design an N-Way Set-Associative Cache featuring custom eviction strategies (e.g., LRU, MRU) and ensure thread safety in a multi-threaded environment.

Medium · system design and architecture

Approach

  1. Name the failure you are designing for, then the recovery path.
  2. State the consistency you need, and where you are willing to be stale.
  3. Name the read and write paths separately; they rarely have the same bottleneck.

Follow-up

  • How does this behave when that dependency is down for an hour?
  • What breaks first when traffic grows ten times?

Explain how to handle cache invalidation, data consistency, and replication across multiple geographic regions in a high-traffic web application.

Medium · system design and architecture

Approach

  1. Fix the scope first: who calls this, how often, and what they do when it fails.
  2. Choose a partition key and say what query it makes expensive.
  3. Name the read and write paths separately; they rarely have the same bottleneck.

Follow-up

  • What would you drop to keep the system up under load?
  • How does this behave when that dependency is down for an hour?

Implement an in-memory thread-safe rate limiter class with configurable time windows and request limits per client.

Medium · system design and architecture

Approach

  1. Fix the scope first: who calls this, how often, and what they do when it fails.
  2. Choose a partition key and say what query it makes expensive.
  3. Name the failure you are designing for, then the recovery path.

Follow-up

  • How does this behave when that dependency is down for an hour?
  • What breaks first when traffic grows ten times?

Design the billing notice callback an exchange fires later

Medium · webhooks · authentication · idempotency · billing

At bid time your bidder mints impression_id and returns notice URLs the exchange calls afterwards: a win notice, and a separate billing notice carrying the clearing price substituted into a macro, sometimes encrypted under a per-exchange key. The callback is a public endpoint reached by a caller whose retry behaviour you do not control, and its result becomes a budget_ledger charge row. Design the URL and handler contract: what the URL carries, how you authenticate it, what must be durable before you respond, and which status codes you return.

Approach

  1. Put identity in the URL and sign it: impression_id, line_item_id, exchange_id, an expiry, and an HMAC over that tuple keyed per exchange. These URLs travel through intermediaries and appear in markup on some integrations, so an unsigned endpoint lets anyone who can read one charge an advertiser's budget or flood the ledger. Verify with a constant-time comparison and reject anything past its expiry.
  2. Treat the price as untrusted input from a known counterparty. Decrypt and verify it with the per-exchange key, and treat a MAC or decryption failure as an authentication failure to reject and alert on, never as a parse failure to default to zero or to your bid price. The clearing price is not knowable at bid time and is not generally equal to the bid, so there is nothing safe to fall back to.
  3. Charge on the billing notice only, and keep win and billing as separate event_type rows on the same impression_id. A win is an auction outcome; a billing notice is the exchange asserting the impression was served. Charging on the win inflates spend in proportion to render failure, which varies by supply source and surfaces later as an unexplained reconciliation gap rather than as an obvious bug.
  4. Make the handler's synchronous work a keyed durable append and nothing more: insert delivery_event (impression_id, 'billing_notice') and the budget_ledger charge under the partial unique index on impression_id where entry_type = 'charge', commit, then respond. A duplicate must return 2xx — returning an error on a replay invites further retries and can get your endpoint marked unhealthy by the caller.
  5. Set the status codes from what you want the caller to do. 4xx for failed verification or expiry, because retrying will not help and you want it to stop; 5xx only when the write is genuinely not durable, because that is the one case where a retry is the correct outcome; 2xx for success and for duplicates alike. Defer enrichment, invalid-traffic review and currency conversion to downstream consumers of the log.

Worked solution (30 min)

  1. Write the notice URL template the bidder emits, listing every parameter, the price macro placeholder, the expiry, and the exact HMAC input string in field order.
  2. Write the handler's step order and mark the commit point: verify signature and expiry, verify and decrypt the price, insert the delivery_event row, insert the ledger charge, commit, respond.
  3. Enumerate six cases — first valid notice, duplicate, bad signature, expired, undecryptable price, database unavailable — and give each a status code with a one-line justification in terms of what the caller should do next.
  4. Estimate the synchronous work per notice and list what you deliberately deferred to the downstream consumer, with the reason.

Expected result: A signed, expiring notice URL keyed per exchange; a handler that charges only on the billing notice and only after a durable keyed write protected by the partial unique index; 2xx on duplicates; 4xx on verification failure; 5xx reserved for non-durable writes.

Verify it yourself

  • Replaying a captured notice ten times produces exactly one charge row and ten 2xx responses.
  • Tampering with any signed field, including line_item_id or the expiry, is rejected before any write happens.
  • Charge count reconciles against billing notices rather than win notices, and the gap between the two is a number you can quote per exchange.

Follow-up

  • The same billing notice arrives again a week later from a replay — does it charge, and which specific part of your design decides that?
  • Your charge count and the exchange's reported count differ by 0.4% at daily close — where do you look first, and what row do you write to the ledger?

Exchange reports late bids while internal dashboards look healthy

Easy · deadline propagation · tail latency · timeouts

Your bidder answers an exchange that enforces a 100 ms deadline measured from its own send to its own receive. Internal dashboards show mean handler time at 9 ms with no regression at any deploy, but the exchange now discards 6% of your responses as late and has warned about throttling your access to its supply. The bid path makes three sequential dependency calls, each given a 40 ms timeout, added by three different changes. Give the ordered list of what you inspect and in what order, and the change you make to the timeout scheme.

Approach

  1. Fix the measurement boundary before trusting any number. The exchange times from its socket write to its socket read, so its figure includes network round trip, your accept-queue wait, TLS handshake on cold connections and response flush. Instrument accept-to-flush and compare it against the handler-only timer; a divergence puts the regression in queueing rather than in code.
  2. Replace the mean with p99 and p99.9, cut per exchange and per region. At 10^5 requests per second a 6% late rate is thousands of requests a second, and a mean over that population cannot move enough to be visible. An average is the wrong statistic for a deadline because the deadline is a threshold, not a central tendency.
  3. Do the deadline arithmetic explicitly. Take 100 ms, subtract measured round trip and the exchange's own slack, and you have roughly 70 ms of internal budget. Three sequential calls with 40 ms timeouts admit 120 ms, so any two slow hops blow the budget while every per-dependency dashboard still reports healthy, because each hop stayed inside its own constant.
  4. Attribute the tail rather than the mean: per-hop span timings at p99, garbage collection and pause logs, connection pool checkout wait, and DNS or TLS cost on new connections. Look for the hop whose p99 moved, not whose average moved.
  5. Change the scheme to a deadline carried on the request and decremented at every hop, granting each call the remaining time minus a reserve rather than a constant. Write the degraded behaviour next to each dependency before writing code: which ones can be skipped into a lower-confidence bid and which force a no-bid. A late response is worse than a no-bid, because it consumes capacity, buys nothing, and counts against the supply relationship.
  6. Alert on your own internal budget overrun rate rather than waiting for the exchange to complain, since the exchange's signal arrives only after it has already decided to throttle you.

Follow-up

  • The identity lookup has a p50 of 2 ms and a p99 of 36 ms. Do you keep it on the path, and what does the bid look like when you skip it?
  • How do you tell a no-bid caused by a squeezed deadline apart from a no-bid caused by no eligible line item, in metrics you would actually keep?
  • The exchange's deadline and your measured round trip both vary by region. Where does the per-region budget live, and who owns changing it?

Suggested practice checklist

Roughly ninety minutes on weeknights with one longer weekend block. The plan cuts scope rather than compressing everything, on the assumption that one thing finished per night beats four half-started.

Day 1 — Coding baseline for the automated assessment

  • Attempt the reported progressive problem (reported-algorithms-1) cold: string manipulation, then matrix traversal, then a dynamic programming optimization, keeping each part in its own function.
  • Implement a sliding-window maximum over a stream with a monotonic deque, then adapt it to the minimum.
  • Solve one in-place 2D array problem in O(1) auxiliary space and write down the auxiliary space of your first attempt as well.
  • For every problem, write edge-case inputs before running, and log what you missed.

Deliverable: Three working solutions with stated complexity, and a list of the edge cases you missed on the first try.

Day 2 — Caches from a blank file

  • Implement LRU and MRU caches in O(1) per operation with a hash map and a doubly linked list, from an empty file both times.
  • Build the reported N-way set-associative cache (reported-systemdesign-2) single-threaded, with eviction behind an interface so LRU and MRU are separate classes.
  • List the clarifying questions you would ask first: capacity, number of sets, key-to-set mapping, and behavior on a miss.

Deliverable: A single-threaded N-way set-associative cache with pluggable LRU and MRU eviction, plus your clarifying-question list.

Day 3 — Thread safety and refactoring for the screening call

  • Add per-set locking to yesterday's cache and write two sentences on why get needs exclusive access under LRU.
  • Implement the reported per-client rate limiter (reported-systemdesign-4) with configurable windows, then make it thread-safe and state what each lock protects.
  • Take a messy class you have written or found, list its smells and race conditions, pin its behavior with tests, and refactor it one step at a time.
  • Explain one of the three designs aloud end to end as if on a phone call.

Deliverable: A thread-safe cache, a thread-safe rate limiter, and a refactoring log with behavior-pinning tests.

Day 4 — Algorithms with production constraints

  • Solve the interval drill (drill-coding-3): state the half-open convention, merge the pauses, clip them to the flight, and give the O(m log m) bound.
  • Work through the top-k under a deadline drill (drill-coding-4) and compare your answer with its worked exercise, especially the tie-break and how k is derived.
  • Do the partitioned-table indexing drill (drill-sql-1) and check your three reasons against its worked exercise.

Deliverable: Two coding solutions and one SQL analysis, each compared line by line with the worked exercise where one exists.

Day 5 — Distributed system design for the panel

  • Design the reported ad bidding engine under a sub-10ms SLA: in-memory state, a per-hop deadline, and no-bid as the fallback. Then work the late-bids debugging drill (drill-debugging-6).
  • Specify the bidder's serving snapshot (drill-sql-2), including how a bidder detects that its snapshot is too stale.
  • Answer the reported multi-region cache invalidation question (reported-systemdesign-3), naming the consistency you give up and where stale reads are acceptable.
  • Design the billing notice callback (drill-design-5) and check it against its worked exercise for idempotency and status codes.

Deliverable: Four one-page designs, each with a latency or consistency budget, the failure it is built to handle, and its single points of failure named.

Day 6 — Recruiter call and behavioral stories

  • Write two-sentence summaries of your headline projects with no internal codenames, each with a measured result, for the recruiter discussion.
  • Prepare stories for the reported behavioral prompts: a challenging project and its architecture choices (reported-behavioral-5), and a production flaw or bottleneck you fixed (reported-behavioral-6).
  • Add stories for a speed-versus-quality trade-off, an architecture disagreement, and ambiguous requirements. Rehearse the code review disagreement drill (drill-behavioral-7).
  • Write your questions for the recruiter about the screening format, allowed languages and how the Superday is split.

Deliverable: Project summaries, five behavioral stories each with a decision and an outcome, and a list of questions for the recruiter.

Day 7 — Superday rehearsal

  • Run back-to-back mocks covering algorithms, object-oriented design, system design and one behavioral story, with no break between them.
  • Afterwards, write down where quality dropped: missed edge cases, a forgotten lock, a design without a latency budget, a vague story.
  • Fix only those points. Then rebuild the cache and rate limiter once more from a blank file as a final check.

Deliverable: Notes from the mock sequence naming each point where quality dropped and the fix, plus one clean rebuild of the cache and the rate limiter.

'We rewrote it in a better framework' is not a story

The behavioral questions reported for this role focus on trade-offs, ownership of production problems, ambiguous requirements and technical disagreement. Build each story around one decision you made: what you chose, what you gave up, what you measured, and what happened next. For the architecture and performance prompts, expect follow-ups about the technical detail, so choose stories you can defend at code level.

  1. Describe a time when you made a complex technical trade-off between delivery speed and system performance or code quality.
  2. How do you handle a situation where requirements for a project are ambiguous or continuously changing?
  3. Describe a situation where you had a disagreement with a team member or technical lead on software architecture, and how you resolved it.
  4. Explain how you prioritize work across multiple simultaneous projects with shared people, conflicting stakeholder pressure, and uneven urgency.

Walk me through a challenging project on your resume, focusing on the specific architectural choices you made and the outcome.

Medium · behavioural and engineering judgement

Approach

  1. Name the disagreement and how you resolved it with evidence.
  2. Give the blast radius: what could have broken, and what you measured.
  3. Pick a story where you made the decision, not one where you watched it.

Follow-up

  • How did you know your change caused the improvement?
  • What would you do differently if you ran that again?

Tell me about a time you identified a critical flaw or performance bottleneck in a production system and drove the fix.

Medium · behavioural and engineering judgement

Approach

  1. Close with what you would do differently, concretely.
  2. Give the blast radius: what could have broken, and what you measured.
  3. Name the disagreement and how you resolved it with evidence.

Follow-up

  • What did you decide not to do, and why?
  • How did you know your change caused the improvement?

Take a code review disagreement about duplicate impression charges

Medium · idempotency · code review · concurrency · constraints

A colleague's change charges a won impression by selecting from budget_ledger on impression_id, inserting a charge row if nothing is found, and wrapping the pair in a transaction. They note the tests pass, that a 30-second in-memory dedup cache sits in front of the worker, and that you are the only objector. The billing notice arrives from the exchange over the public internet and is retried on any non-2xx. Reconstruct the review: the comment you wrote, how you showed the defect without a reproduction, and what you would have accepted instead.

Approach

  1. Name the interleaving instead of saying race condition. Worker A selects and finds nothing, worker B selects and finds nothing, both insert, both commit. Under READ COMMITTED and under REPEATABLE READ in PostgreSQL this pair of transactions both succeed, because a read that returns no rows takes no lock on rows that do not exist yet.
  2. Explain why the defect is invisible where they looked: the window is the gap between the SELECT and the INSERT, so it never appears in a single-threaded test and appears constantly under a backlog drain, which is exactly when the team scales workers up to catch up.
  3. Propose the mechanism that removes the race rather than narrowing it. The partial unique index on budget_ledger (impression_id) WHERE entry_type = 'charge' means the second write is rejected by the database. The application inserts first, catches the unique violation (SQLSTATE 23505), treats it as success, increments a duplicate counter, and returns 2xx so the exchange stops retrying.
  4. Address the cache argument on its own terms rather than dismissing it. A 30-second cache absorbs a retry seconds apart and does nothing for the same notice replayed a week later during a reprocess, and its hit rate is a function of worker restarts, so it is a cost optimisation and not a correctness mechanism.
  5. Concede the alternative that also works and say why you did not choose it: SERIALIZABLE isolation would abort one of the two transactions, at the cost of serialization failures and retries on the hottest table in the system.
  6. Finish on the review dynamics, not just the technical point: what you wrote, whether you blocked the merge, who decided, and how you would have behaved if the author had still disagreed.

Follow-up

  • The same notice arrives twice a week apart during a reprocess. Walk both designs through that case.
  • Where does impression_id come from, and what breaks if you derive the key by hashing the notice body instead?
  • The insert succeeds but the process crashes before acknowledging the notice to the exchange. What happens next, and is that acceptable?