Interview guides · ATG Entertainment
ATG Entertainment Software Engineer Interview Questions & Guide 2026
Timed coding practice for ATG Entertainment software engineer candidates: full attempts under a clock, then scored against a rubric.
The 60-second brief
As a Software Engineer at ATG Entertainment, you sit at the intersection of high-stakes live entertainment and robust technical infrastructure. This role is pivotal in ensuring that the digital platforms supporting world-class theatrical and live event experiences remain performant, reliable, and scalable. You are not just writing code; you are building the digital backbone that connects audiences to live performances.
Ask how many rounds there are and what each one is before you plan your weeks, because the composition is what your hours should follow. A loop with three coding rounds and one short design conversation deserves a different split from the reverse, and whoever schedules it will usually say plainly which it is.
Focus areas
- Cut live latency without starving the buffer
- Key CDN caches without sharding on tokens
- Expire rights across cached and offline copies
Overview
The work environment at ATG Entertainment demands a blend of technical precision and operational awareness. Whether you are optimizing network architectures or managing complex operating systems, your contributions directly influence the seamless delivery of content and services. You will be expected to solve problems that are critical to the company’s mission, requiring a mindset that values stability, efficiency, and a deep understanding of the end-user experience.
Given the diverse nature of engineering roles at ATG Entertainment, ensure you are intimately familiar with the specific technical domain—whether it be network engineering or operating systems—outlined in your specific job description.
The ATG Entertainment interview process
ATG Entertainment candidates report 2 rounds · ≈ 2-4 weeks. The stages below are what candidates describe, not a published process.
Initial Screening
Before anything technical happens, someone has to decide which rung of the ladder your loop is calibrated to, and that decision sets the bar for every round after it. It comes from how you describe scope, not from your title, because titles do not convert cleanly between companies. The weak version of the answer is team size and years. The strong version names the largest change you shipped where nobody reviewed the design, what would have broken if you had been wrong, and what you were paged for. Get the level said out loud on this call, because the range and the loop both follow from it.
What to demonstrate
- Whether the scope in your own account maps onto a level the team actually has an opening at, so a mismatch ends the process cheaply rather than after four interviewers have spent a day
- Whether your title needs re-mapping: the same word describes very different amounts of independent decision-making at a twenty-person company and a ten-thousand-person one
- Whether your compensation expectation can be filled at that level in the structure the role pays in, which is why the number gets asked for before any engineer is scheduled
How to prepare
- Write down two changes from the last two years: the largest one you designed with nobody reviewing the design, and the largest one where someone more senior did. Lead with the first when scope comes up, and be ready to say which parts of the second were yours
- Ask which level the loop is calibrated to and what changes at the level above it, then plan your weeks from that answer rather than from the posting
- Settle a total-compensation range beforehand with the split named, base against bonus against equity and its vesting period, so a question about numbers gets a number instead of the word market
Technical Deep Dives
Input bounds are the part of the prompt most often skimmed, and they usually contain the answer. They tell you which complexity class is admissible, which narrows the search before you have thought about the problem itself. As a rough planning figure, a compiled language does on the order of 10^8 simple operations per second and an interpreted one roughly an order of magnitude less. So n up to about twenty admits enumerating subsets, a few thousand admits a quadratic pass, and a million admits neither: you need near-linear, or linear with a log factor. If the bounds are missing, ask for them.
What to demonstrate
- Whether the approach is justified by the stated input size rather than by whichever pattern you recognised first
- Whether you ask about the properties that change the algorithm: whether the input arrives sorted, whether duplicates occur, whether values are bounded integers, whether it all fits in memory
- Whether you can name the bottleneck in your own solution and what would remove it, even when you deliberately leave it in place
- Whether a claimed speedup is real, since memoising a recursion only helps when subproblems genuinely overlap and the state can be keyed cheaply
How to prepare
- For each algorithm you rely on, write down the largest n it handles in roughly a second, then check two of those figures by timing them in the language you will actually type in
- For two weeks, write one line naming your target complexity and the bound that justifies it before you write any code, then compare that line with what you ended up submitting
- Practise the conversion backwards: given a required O(n log n), list the mechanisms that get you there (sorting, a heap, an ordered map, divide and conquer) and choose by what the problem needs to query, not by what you used last
Where candidates lose points
Treating the manifest as the authorisation boundary.
Checking entitlement when the manifest is requested feels natural, because that is where the session visibly begins. But the manifest is a cacheable document and the segment URLs inside it carry no identity, so anyone who obtains it plays until the delivery token expires, and a subscriber who cancels mid-title finishes the title. Authorisation has to sit at the licence request, the only thing on the path that is per-user and uncacheable by construction.
Including the delivery token, session id or any per-user parameter in the CDN cache key.
It looks like the conservative choice, and it behaves perfectly in staging where three people are watching. In production every viewer becomes a distinct cache object, hit ratio collapses toward zero, and the origin absorbs the entire read load at peak - a cost and availability event rather than a slow page. The correct shape is for the edge to verify the token's signature and then strip it from the key, which means the token has to be verifiable at the edge without a callback to any origin service.
Issuing one query per row of a result set
Fetch related rows in a single batched query keyed by the ids you already hold, or join them into the original query. A per-row round trip multiplies network latency by the row count, and it looks perfectly fine against the ten rows in your development database.
A queue or buffer with no bound
Every producer-consumer boundary needs a capacity and a policy for reaching it: block the producer, shed load, or drop the oldest entry. Unbounded buffering converts a temporary slowdown into memory exhaustion and hides the backpressure signal that would have revealed the consumer was falling behind.
Questions & practice
Rank delivery groups by rebuffer ratio without ranking on noise
Medium · top-k · heap · aggregation
A completed hour of telemetry yields roughly 10^6 groups keyed (cdn_pop, device_class, isp), each arriving as an unordered stream of per-minute partials carrying qualified_starts and rebuffered_starts. On-call needs the 50 worst groups by rebuffer ratio, excluding groups too small to mean anything: a group with 3 starts and 1 rebuffer reads as 33%. Return the top 50 with their ratios and sample counts. Target something better than a full sort, and state both time and space.
Approach
- Two phases, forced by the fact that the ranking key is a ratio that is only final once every partial for a group has landed. Phase one is a hash aggregation of partials into per-group counters, O(n) time and O(g) space for g groups.
- Phase two selects with a size-50 min-heap keyed on the ratio: O(g log k) time and O(k) space, against O(g log g) for a sort. At g = 10^6 and k = 50 that is about 6 comparisons per group rather than 20. Quickselect is O(g) but needs every group resident and mutable, so the heap wins when phase one streams.
- Apply the minimum-sample floor before the heap, never after. At a floor of 200 qualified starts the standard error of the ratio is sqrt(p(1-p)/n), about 1.5 percentage points at p = 5%, so the ordering carries information. Filtering after the heap leaves it full of three-sample groups and returns fewer than 50 usable rows.
- Offer the alternative that removes the arbitrary cutoff: rank on the lower bound of a Wilson score interval, which shrinks small groups toward the population mean automatically. The cost is a heavier comparison per group and a figure that is no longer the raw ratio people expect on the dashboard.
- If g does not fit in memory, partition by hash of the group key, take a local top-50 per shard and merge. Top-k decomposes this way only because each group's aggregate is complete within its shard, which the hash partition guarantees.
Follow-up
- Make it a rolling one-hour top-50 recomputed every minute. What changes when groups must also be evicted as old minutes leave the window?
- Two groups tie on ratio but differ 100x in volume. Which do you page on, and what does that argue about ranking on count-weighted impact instead of on ratio?
Parse a live media playlist for seek offsets and deltas
Medium · parsing · prefix sums · binary search
You are given two successive snapshots of an HLS media playlist as text: #EXT-X-TARGETDURATION, #EXT-X-MEDIA-SEQUENCE, then repeated #EXTINF:<seconds>, lines each followed by a segment URI, with #EXT-X-DISCONTINUITY markers possible between entries. A four-hour DVR window at two-second segments is 7,200 entries. Build a structure that answers which segment contains playback position P in better than linear time, and compute which segments were added and removed between the two snapshots. State the complexity of both the build and each query.
Approach
- Single-pass parse into an array of (media_sequence_number, duration_ms, uri, discontinuity_flag), converting each #EXTINF decimal-seconds value into integer milliseconds at parse time. Accumulating 7,200 floating-point seconds drifts, and every comparison downstream wants exact integers.
- Build prefix sums of duration_ms alongside the array, O(n) time and O(n) space, and answer a position query by binary search over the prefix array: O(log n), about 13 probes at 7,200 entries.
- Reset the accumulator at each #EXT-X-DISCONTINUITY and keep a per-discontinuity base. A discontinuity is a timeline break, so a prefix sum that runs straight through one maps every position after it to the wrong segment. The query then returns (discontinuity index, offset within it) rather than a single scalar.
- Diff by media sequence number, not by URI or by position. The first entry's number is #EXT-X-MEDIA-SEQUENCE and each subsequent entry is one greater, so added = numbers in B above A's maximum and removed = numbers in A below B's first. That is O(added + removed) and survives URI reuse across a discontinuity.
- Tie it back to delivery: every viewer refetches this document about once per target duration, so the delta is the thing you would actually transmit, and it is the reason a 7,200-entry playlist is a bandwidth problem rather than a parsing problem.
Follow-up
- Add #EXT-X-BYTERANGE so several segments share one resource. What changes in the structure, and what does a seek now have to return?
- The playlist is served as a delta against a client-held version. What must the server retain, for how long, and what happens to a client that has fallen further behind than that?
Recompute the downstream job set after a single rendition re-encode
Medium · dag · topological sort · invalidation
The packaging pipeline for one content version is a DAG of up to 10^5 jobs and 5 x 10^5 edges: source validation, per-chunk encodes, per-rendition concatenation, packaging, encryption, manifest generation, publish. QC fails one video rung, so its chunk encodes are re-run on a different encoder build. Given the edge list and a set of dirty job ids, return every job that must re-run plus a valid execution order for them, and report whether the graph contains a cycle. Target O(V + E) time.
Approach
- Build forward adjacency (job -> dependents) in one O(E) pass over the edge list. No sorting is required anywhere in this problem.
- Collect the affected set S by BFS or DFS from the dirty set over forward edges, marking a visited bitset. O(V + E), each node and edge touched once.
- Order S with Kahn's algorithm run on the induced subgraph, recomputing in-degrees counting only edges whose tail is also in S. Reusing whole-graph in-degrees is the failure here: clean predecessors outside S leave phantom in-degrees, the ready queue starts empty and nothing is ever emitted.
- Detect the cycle from Kahn's own termination condition: if emitted count < |S|, the residual nodes contain a cycle and everything it feeds. Report the residual set rather than asserting, because a malformed encoder profile is a data bug, not a crash.
- State the trade-off against re-running the whole version: the affected set is one rung's chunks plus concatenation, packaging, encryption, manifest and publish, so you save the 6-12x ladder fan-out. But manifest and publish are common descendants of every rung and are therefore always in S, which is precisely why re-encoding one rung is not a local change.
Worked solution (25 min)
- Sketch a 12-node pipeline with three rungs so that manifest generation has three predecessors at different depths.
- Mark one rung's chunk encode dirty and hand-compute the forward-reachable set.
- Show a BFS order on that sketch that violates a dependency, then run Kahn on the induced subgraph and show it does not.
- Recompute in-degrees restricted to S and state exactly which edges are excluded.
- Give the complexity and the saving versus a full re-run in terms of the ladder fan-out.
Expected result: The affected set equals the dirty set union its forward-reachable closure, emitted in a Kahn order computed over the induced subgraph, in O(V + E); a cycle is reported when emitted count is less than |S|.
Verify it yourself
- Manifest generation and publish appear in every non-empty affected set.
- Every node in the emitted order has all of its in-S predecessors emitted earlier.
- On acyclic input the emitted count equals |S| exactly.
Follow-up
- The re-encode uses a different encoder build. Which additional nodes become dirty, and why is encoder_build_id pinned per asset rather than taken from the pipeline default?
- Two rungs fail QC concurrently and two invalidation runs overlap. What makes the recomputation idempotent so the second run does not restart jobs the first already completed?
Make a monthly payout scan use an index at two billion rows
Medium · sargability · covering index · partitioning · query planning
qualified_play(id BIGSERIAL PK, session_id, content_version_id, rights_holder_id, account_id, territory_code, qualified_at TIMESTAMPTZ, status ENUM('pending','counted','reversed'), source ENUM('online','offline_sync'), dedupe_key UNIQUE, counted_in_period) holds about two billion rows, roughly 150 million per month, and is physically ordered by insert time. The monthly close runs SELECT rights_holder_id, count(*) FROM qualified_play WHERE date_trunc('month', qualified_at) = DATE '2026-08-01' AND status = 'counted' GROUP BY 1 and scans the entire table. Give three separate reasons it does, and the change you would make for each.
Approach
- Reason one is the expression. Wrapping the column in
date_truncmakes the predicate non-sargable, so no b-tree onqualified_atis usable and the planner also falls back to a default selectivity guess instead of the column's histogram. Rewrite to the half-open rangequalified_at >= DATE '2026-08-01' AND qualified_at < DATE '2026-09-01', which selects the same rows. An expression index is the alternative, but in PostgreSQLdate_trunc(text, timestamptz)is STABLE rather than IMMUTABLE because it depends on the session TimeZone, so it cannot be indexed directly — you would indexdate_trunc('month', qualified_at AT TIME ZONE 'UTC')and write the predicate in exactly that form. - Reason two is selectivity, and the fixed predicate does not remove it. One month is about 7.5% of the table; at roughly 50 rows per page the table is about 40 million pages, and 150 million rows scattered across it touch essentially every page. A plain index scan would pay random I/O for the whole table while a sequential scan reads it in order — the planner is right. Make the scan index-only instead:
(status, qualified_at, rights_holder_id)covers every column the query touches, so the plan reads index pages alone. That holds only while the visibility map is current, which makes it a dependency on autovacuum keeping up with continuous inserts. - Reason three is structural.
status = 'counted'is most of the table and is not selective by itself, but a partial indexWHERE status = 'counted'keeps reversed and pending rows out of the index entirely. At two billion rows the real fix is range partitioning byqualified_atmonth: the close reads one partition, the planner prunes the rest, and the partition boundary lines up withcounted_in_periodso a period close and a partition are the same object. - Name the BRIN trap before someone reaches for it. BRIN is the obvious cheap index for an append-ordered table and it is tight on the arrival timestamp — but
qualified_atis when the threshold was crossed, andoffline_syncrows land days later, so physical order correlates with arrival rather than withqualified_at. Checkpg_stats.correlationfor the column instead of assuming; poor correlation makes every block range a candidate and the index worthless. - Settle it with EXPLAIN (ANALYZE, BUFFERS) rather than argument, and look at estimated versus actual rows for the range predicate first. A plan that goes wrong at this size usually went wrong at the estimate, and a wrong estimate has a different fix from a wrong index.
- Sequence the work by cost and risk: rewrite the predicate first because it is free and takes no lock, measure, then build the covering index with CREATE INDEX CONCURRENTLY, and only then consider partitioning, which is a migration of the whole table rather than an addition to it.
Follow-up
- The close also needs plays that qualified in August but arrived in September. Which of your changes still works, and which partition does a late row live in?
- The same aggregate is now wanted per territory as well. Do you widen the covering index or accept the scan?
- How would you prove the scan really is index-only in production rather than on a freshly vacuumed copy?
Add a dedupe constraint to a live plays table
Hard · online migration · lock queueing · backfill · unique constraints
qualified_play holds about two billion rows, takes continuous inserts, and was created without the dedupe_key column the design calls for. Add the column, backfill it deterministically from session_id and content_version_id, and make it unique and NOT NULL without taking the table offline. Duplicate plays already exist and money was paid against them, and corrections are written as reversing rows that share the same logical key. Give the ordered plan, name the lock each step takes, and say what the uniqueness is actually on.
Approach
- Deploy the write path before the backfill. Ship the code that populates
dedupe_keyon new rows first so the backfill covers a finite set — everything older than that deploy — instead of chasing a table that keeps growing new null rows behind it. - Add the column as nullable with no default. A nullable column with no default has never rewritten the table; since PostgreSQL 11 a constant default does not either, but a volatile default still does. The statement takes ACCESS EXCLUSIVE briefly, and the real hazard is the queue: the ALTER waits behind a long analytics scan of this table, and every query arriving after it waits behind the ALTER. Take it with
SET lock_timeout = '2s'and retry, so a failure to get the lock is a no-op rather than a stall of the read path. - Backfill in bounded batches over the primary key range, committing per batch. Each UPDATE writes a new row version, so the backfill produces roughly a table's worth of dead tuples before autovacuum reclaims them; throttle to the rate autovacuum can keep up with and watch the dead-tuple count rather than wall-clock progress. Never hold one long transaction over the whole backfill — it pins the transaction horizon and blocks cleanup everywhere.
- Decide what the uniqueness is on before building anything. A reversing row carries the same logical key as the row it reverses, so a plain unique on
dedupe_keywould make corrections impossible. Either make the index partial — unique ondedupe_keyWHERE status IN ('pending','counted')— or fold a revision into the stored key. Say which, and say what a re-count after a reversal then looks like under your choice. - Resolve the pre-existing duplicates with a window function rather than a DELETE:
ROW_NUMBER() OVER (PARTITION BY dedupe_key ORDER BY qualified_at, id)identifies every row past the first, and each gets a reversing row. Deleting them would erase rows a payout already ran against; the ledger has to show the correction, not lose the evidence. - Build with
CREATE UNIQUE INDEX CONCURRENTLY, which takes SHARE UPDATE EXCLUSIVE and does not block reads or writes, at the cost of two table passes and waiting on open transactions. It fails if any duplicate remains, and a failed build leaves an index withindisvalid = falsethat must be dropped before retrying. Promote it withALTER TABLE ... ADD CONSTRAINT ... UNIQUE USING INDEX, a brief ACCESS EXCLUSIVE that does not rebuild. For NOT NULL, addCHECK (dedupe_key IS NOT NULL) NOT VALID, runVALIDATE CONSTRAINTunder SHARE UPDATE EXCLUSIVE, thenSET NOT NULL, which from PostgreSQL 12 uses the validated check instead of scanning the table again.
Worked solution (40 min)
- Build a scaled copy with a known duplicate population and a writer process inserting continuously throughout.
- Hold a long SELECT open, then run the ADD COLUMN with
lock_timeoutset, and observe what queues behind what. - Backfill in batches, recording dead tuples and table size after each batch.
- Enumerate duplicates with ROW_NUMBER and write reversing rows for every row past the first — leaving one duplicate in place deliberately.
- Run CREATE UNIQUE INDEX CONCURRENTLY, observe the failure, and inspect
pg_index.indisvalidfor the leftover. - Drop the invalid index, reverse the last duplicate, rebuild, promote to a constraint, and set NOT NULL through the NOT VALID check path.
Expected result: No writer statement blocks for longer than the lock_timeout at any step. The first concurrent build fails and leaves an index with indisvalid = false that must be dropped rather than retried over. After the duplicates are reversed the build succeeds, promotion adds the constraint without a rebuild, and NOT NULL is set without a second full scan.
Verify it yourself
pg_index.indisvalidis true for the final index and no invalid index remains behind.- The writer process logs no errors and no statement waiting longer than the configured lock_timeout.
- Every pre-existing duplicate has a reversing row, and the count of rows in status
pendingorcountedperdedupe_keyis exactly 1. - The SET NOT NULL step finishes in far less time than a full scan of the table takes, confirming it used the validated check.
Follow-up
- The backfill is half complete when a bug in the key derivation is found. What is the recovery, and does it need another full pass?
- A duplicate surfaces after the constraint is live and the period is already closed. Which table changes and which does not?
- How do you prove the key is deterministic enough that a client regenerating it after a crash produces the identical string?
Explain the process you use to troubleshoot high-latency network issues in a production environment.
Medium
Approach
- State your assumptions explicitly before working the problem.
- Work from the requirement backwards to the design.
- Say what you would check first and why it is the highest-information step.
Follow-up
- How would you know your answer was wrong?
- What assumption would you test first?
How do you balance the need for system updates with the requirement for continuous, uninterrupted service?
Medium
Approach
- Work from the requirement backwards to the design.
- Say what you would check first and why it is the highest-information step.
- Clarify what is being asked and what a complete answer contains.
Follow-up
- How would you know your answer was wrong?
- What assumption would you test first?
Key the segment cache without sharding on the delivery token
Medium · cache-key · edge-auth · offload · request-coalescing
Segments are fetched from /v/{content_version}/{rendition}/{seq}.m4s?tok=..., where tok is minted per playback session. Peak is 100,000 concurrent viewers averaging 5 Mbps, and the offload target is 95%. The edge must reject a forged or expired token without calling back to any origin service. Give the cache key, what the edge verifies and with what key material, the origin egress at 95% and at 94% hit ratio, how concurrent misses for the same object are collapsed, and what happens when a token expires mid-title.
Approach
- Separate the two jobs the token has. Verification happens before cache lookup; the cache key is scheme, host and path only. Including tok in the key gives every viewer a private copy of every segment, so hit ratio collapses toward zero and the origin absorbs the whole read load, which presents as an origin outage rather than a slow page.
- Specify verification that terminates at the edge. The token carries a path prefix, an expiry and a session nonce, signed with a key the edge already holds; the edge recomputes the signature and checks the expiry and the prefix against the request path. Symmetric HMAC costs microseconds but puts minting capability in every POP; an asymmetric signature keeps the private key central at higher verify cost. Rotate with two keys valid at once so a rotation does not invalidate tokens already in flight.
- Do the offload arithmetic out loud. 100,000 x 5 Mbps = 500 Gbps leaving the edge. At 95% hit ratio the origin serves 5% = 25 Gbps; at 94% it serves 6% = 30 Gbps, a 20% increase in origin load from a one-point move. Sensitivity that steep is why cache-key mistakes surface as origin saturation, and why the origin has to be sized against a hit-ratio floor rather than the target.
- Collapse duplicate misses at two tiers. Request rate is 100,000 viewers / 2.002s per segment = about 50,000 segment requests per second, and a popular new title puts thousands of players on the same object within a second. Coalesce concurrent misses per cache key at the POP and again at a shield tier so the origin sees roughly one fetch per object per shield, and give the collapsed request a deadline so a slow origin blocks one fetch rather than every waiting client forever.
- Handle token expiry as a session concern, not a security boundary. Give the token a lifetime shorter than a title and have the player refresh it from the session service, which is also where a revoked session stops getting new ones. This is acceptable precisely because the token is not the enforcement point: entitlement is enforced at licence issuance, so a token that outlives a cancellation grants bytes, not playback. Binding the token to a client IP looks stronger and breaks every viewer whose handset moves between networks mid-title.
Worked solution (25 min)
- Write the cache key explicitly, listing which request components are included, which are verified then stripped, and which are ignored.
- Define the token payload and the verification steps in order, including what the edge does during a key rotation.
- Compute edge egress, origin egress at 95% and 94%, and state the origin sizing floor that follows.
- Compute segment request rate and describe coalescing at POP and shield, including the timeout on a collapsed miss.
- Trace a token expiring at minute 40 of a 90-minute title end to end, naming which service reissues and what the player does meanwhile.
Expected result: A path-only cache key with the token verified and stripped before lookup, an edge-verifiable signature over path prefix plus expiry plus nonce with overlapping rotation keys, 25 Gbps origin egress at 95% and 30 Gbps at 94% with the 20% sensitivity stated, two-tier miss coalescing with a deadline, and token refresh handled by the session service on the grounds that enforcement lives at the licence request.
Verify it yourself
- Two requests for the same segment with different tokens produce one cache object and one origin fetch.
- A request with a tampered or expired token is rejected at the edge with no origin request generated.
- A synthetic burst of 5,000 simultaneous first requests for one uncached segment produces a single origin fetch per shield.
- Hit ratio is unchanged when token lifetime is halved, confirming the token is absent from the key.
Follow-up
- A partner reports segment URLs being shared publicly. What do you change, and what does each option cost in hit ratio?
- You must serve two encryption schemes for the same ladder. How many distinct cache objects does that create, and does anything about the key change?
- Origin is saturated and hit ratio reads 93% instead of 95%. How do you tell a cache-key bug from a genuine traffic shift, using data you already collect?
Origin egress quadruples while viewer demand stays flat
Easy · cdn caching · cache key · origin load
Overnight, origin egress for segments rose about fourfold while edge-delivered bytes, concurrent sessions and peak bitrate stayed flat. Segment hit ratio at the edge fell from 95% to 81%; manifest hit ratio is unchanged and every POP is affected equally. No playback alarm fired, so the first signal was a cost alert. The previous evening's release added a signed delivery token to segment URLs. Produce an ordered diagnostic checklist, name the change that explains the numbers, and show the arithmetic linking hit ratio to origin load.
Approach
- Establish that demand did not change before touching any cache config: compare edge bytes delivered, concurrent sessions and requests per second week over week. Origin volume is (1 - h) x edge volume, so h moving 0.95 to 0.81 takes the origin share from 0.05 to 0.19, a 3.8x rise. The observed numbers are already self-consistent, which rules out a traffic story.
- Slice hit ratio by path prefix, content type and POP. Segments down, manifests flat, all POPs equally affected points at a rule change applied to the segment path, not a regional failure, a purge storm or an eviction problem.
- Read the cache key the edge actually computes for a sampled segment request rather than the configuration you believe is deployed: which query arguments, headers and cookies are included. Diff it against the previous release.
- Count distinct cache keys per segment URL over one hour. A ratio near one key per viewer is proof the token is in the key; a ratio near one is proof it is not, and sends you to storage suppression instead.
- If the key is clean, check origin responses on segments for Set-Cookie and for a Vary on a per-request header, either of which stops a shared cache storing the object independently of the key.
- Fix by keeping the token in the URL for the edge to verify and excluding it from the key. Verification must be signature-only at the edge (HMAC or public-key over path plus expiry) so it needs no origin callback. State the trade-off out loud: one cached object now serves every holder of a valid token, so token lifetime and path scope become the only limits on URL sharing.
Follow-up
- How do you rotate the token signing key without breaking sessions already in flight?
- What token lifetime do you choose, given a segment stays cached for hours and a single playback can run three hours?
- Why would a staging test with twenty clients never surface this, and what test would?
Suggested practice checklist
Day one measures instead of guessing, under a fixed rubric, and the remaining hours are allocated in proportion to the gaps before any studying begins. The allocation is deliberately not renegotiated midweek, because the area that feels worst on day three is usually the one that is moving.
Day 1 — Diagnostic, scored before you study anything
- Sit a 110-minute diagnostic in four blocks: forty-five minutes on two coding problems, twenty-five on one design prompt taken to interface and data model, twenty of short-answer fundamentals, and twenty delivering two behavioural answers aloud.
- Score each block from 0 to 3 on a fixed rubric where 3 is correct and fluent, 2 is correct but slow or prompted, 1 is partially correct and 0 is stuck, grading the artifact rather than how the attempt felt.
- Allocate days two to five in proportion to 3 minus each block's score, write the allocation down, and commit to leaving it alone.
Deliverable: A scored rubric and a fixed hour allocation for the rest of the week.
Day 2 — Largest gap: find the boundary rather than the subject
- Split the weakest area into named sub-skills and rate each separately. For coding those are restating the problem, choosing the structure, stating the invariant, turning the invariant into loop bounds, handling empty and single-element input, and accounting for complexity out loud.
- Attempt three items positioned just above where the rating drops off, and for each write the first move you failed to make.
- Re-attempt one of them from blank four hours later with nothing open.
Deliverable: A sub-skill map with the two blocking sub-skills circled.
Day 3 — Drill the blocking sub-skill by repeating the shape
- Do eight short repetitions of the same shape rather than eight different problems, so what gets practised is the pattern and not the puzzle.
- State the rule you now hold in one sentence, then test it against a case built to break it, a sliding window over an array containing negative values, or a cache-aside read path whose invalidation message is dropped.
- Have someone else read your one-sentence rule and find the precondition you left out.
Deliverable: One rule statement with its preconditions attached and one counterexample that would have caught the incomplete version.
Day 4 — Second gap, plus maintenance on the strongest area
- Run the same sub-skill decomposition on the second-largest gap in half the time.
- Spend twenty-five timed minutes on the block you scored highest, choosing the hardest item you can still finish rather than a warm-up.
- Write whether each area fails you on recall, on setup, or on execution, and set the fix accordingly: repetition for recall, a written checklist for setup, timed work for execution.
Deliverable: A second sub-skill map plus a one-line failure diagnosis for each area.
Day 5 — The gap that is not a skill
- Record one technical and one behavioural answer, then count two things in the playback: seconds before your first clarifying question, and sentences you began without knowing where they would end.
- Practise saying that you do not know, followed by how you would find out, without letting it soften into a guess, and practise stating a complexity or an estimate before being asked for it.
- Redeliver one answer under a hard ninety-second cap, which forces structure ahead of detail.
Deliverable: Two recordings with a counted reduction in time-to-first-question.
Day 6 — Retest under day-one conditions
- Sit the same 110-minute structure with new prompts of comparable difficulty and score it on the identical rubric.
- For any block that did not move, change the method rather than adding hours: a block stuck at 1 usually means the practice was too varied, not too short.
- Write down which single block you would still lose the offer on.
Deliverable: A second scored rubric placed beside the first, with one named remaining risk.
Day 7 — Full loop under interview conditions
- Run a sixty-minute mock over the two blocks that moved least, with an interviewer briefed to interrupt and change direction mid-answer.
- Write the recovery script for going blank: restate the question, state your assumption, name the first thing you would check.
- Say every rule from the week aloud without reading it, and cut any you cannot state in a single sentence, since a rule you have to reconstruct mid-answer will not survive an interruption.
Deliverable: A one-page card holding the recovery script and only the rules you could state from memory.
What the behavioural round is actually measuring
Every story you tell gets read for blast radius and judgement: what could have broken, who else it touched, what you knew at the moment you decided. Nobody can audit your code in an hour, so they audit your reasoning instead. Pick work where the call was genuinely yours and the consequences were real enough to remember.
Describe a time you had to optimize a system architecture to handle a significant increase in concurrent user traffic.
Medium · behavioural and engineering judgement
Approach
- Give the blast radius: what could have broken, and what you measured.
- Name the disagreement and how you resolved it with evidence.
- Close with what you would do differently, concretely.
Follow-up
- What did you decide not to do, and why?
- How did you know your change caused the improvement?
Describe a situation where your technical recommendation was challenged by a team member. How did you resolve the conflict?
Medium · behavioural and engineering judgement
Approach
- Pick a story where you made the decision, not one where you watched it.
- State the situation in two sentences and spend the rest on the reasoning.
- 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?
How do you handle situations where you receive feedback that your approach or opinion may be misaligned with project goals?
Medium · behavioural and engineering judgement
Approach
- Close with what you would do differently, concretely.
- Give the blast radius: what could have broken, and what you measured.
- State the situation in two sentences and spend the rest on the reasoning.
Follow-up
- What would you do differently if you ran that again?
- What did you decide not to do, and why?
Tell me about a time you identified a critical failure in a system. What were the immediate steps you took to mitigate the risk?
Medium · behavioural and engineering judgement
Approach
- Close with what you would do differently, concretely.
- Pick a story where you made the decision, not one where you watched it.
- State the situation in two sentences and spend the rest on the reasoning.
Follow-up
- What would you do differently if you ran that again?
- How did you know your change caused the improvement?