cswork / libraryOpen practice workspace

Interview guides · Arena Entertainment

Arena Entertainment Software Engineer Interview Questions & Guide 2026

Testing practice for Arena Entertainment software engineer candidates: the boundaries worth covering, and why flakiness is a real defect.

The 60-second brief

A Software Engineer at Arena Entertainment is at the forefront of the intersection between high-performance computing and interactive digital experiences. Whether you are working on Blockchain infrastructure—leveraging Rust, Cuda, and Cryptography—or developing AI-Native Games, your work directly shapes the platform's ability to deliver secure, scalable, and engaging content. This role is critical to the business because it requires not only rigorous technical execution but also an innovative mindset capable of pushing the boundaries of what is possible in digital entertainment.

The title spans product, platform and infrastructure work, and which of those the seat actually is decides whether design or algorithms carries more weight in your preparation. The posting rarely settles it; what the team is on call for usually does.

Focus areas

  • Sessionise at-least-once heartbeats into exactly-once plays
  • Expire rights across cached and offline copies
  • Enforce entitlement at the licence boundary

Overview

You will contribute to complex, high-stakes projects that demand both depth in low-level systems and an understanding of modern user-facing applications. The environment is fast-paced and intellectually demanding, requiring engineers who can navigate technical ambiguity while maintaining a focus on performance and reliability. If you thrive on solving complex, non-standard engineering problems and want to work on cutting-edge systems, this position offers a unique opportunity to influence the future of the technology stack.

The Arena Entertainment interview process

Arena Entertainment candidates report 2 rounds · ≈ 2-4 weeks. The stages below are what candidates describe, not a published process.

Initial Screening

Half of this call is the part candidates treat as small talk: start date, notice period, work authorisation and its timing, location and time zone, on-call, and the number. Those are what kill offers late, after several engineers have each spent a day. Surfacing a hard constraint now costs you nothing and occasionally buys you something, since a loop compressed to fit a competing deadline can usually only be arranged if it is asked for early. The common failure is deflecting the compensation question twice, then discovering at offer stage that the band never reached your number.

What to demonstrate

  • Whether your hard constraints are compatible with the role before a loop gets booked: earliest start, notice period, what authorisation you hold and when it needs action, days on site, willingness to carry a pager
  • Whether you give a compensation range with something behind it, such as current total compensation or a competing timeline, rather than leaving the band untested
  • Whether your stated timeline is real, since a competing deadline raised now is something scheduling can sometimes work around and the same deadline raised at offer stage usually is not

How to prepare

  • Write each constraint down in one line before the call and state them as facts rather than negotiating them live under a question you were not expecting
  • Set your range from two or three current data points for that level and location, and name the structure you are quoting in, so the number is comparable to the one they are holding
  • If another process is running, say where it stands and by when, and ask directly whether this loop can be scheduled inside that window

Technical Discussion

The same problem is scored by two different mechanisms depending on the format, and preparing for one does not cover the other. With a person watching, partial progress is visible and a hint is a correction you can absorb; silence is the expensive failure, because nobody can read a half-written function. With an automated grader there is no partial credit for what you were about to do, nobody to ask, and the worked examples in the prompt are the entire specification. Read them as a contract, down to whether an empty result should be an empty list or no output at all.

What to demonstrate

  • In a live session, whether your commentary tracks what your hands are doing, and whether a hint redirects you or gets defended against
  • In an automated one, whether you cover the cases the examples do not show, since the hidden cases are where the score moves
  • Whether you manage the clock on purpose: abandoning an approach that is not converging while there is still time to write something simpler that finishes

How to prepare

  • Have someone hand you a problem and feed you one deliberately wrong hint. Practise testing it against a concrete case instead of accepting or rejecting it on authority.
  • Do one timed run a week in a plain browser editor with autocomplete, linting and your own snippets switched off, which is closer to what these environments give you
  • For the automated format, write the harness before the solution: a main that feeds the worked examples plus an empty and a single-element case and prints expected against actual, so a wrong submission is caught by you first

Where candidates lose points

Shortening segment duration to cut live latency.

In classic segmented delivery a compliant client starts roughly three target durations back from the live edge, so halving the segment duration does halve that component - and that is the whole of the appeal. It also doubles the request rate for both segments and playlists, and doubles the number of IDR frames, which cost far more bits than the predicted frames around them, so quality at a fixed bitrate drops. Low-latency extensions exist precisely to break this coupling: they deliver sub-second parts within a segment that stays long enough to encode efficiently.

Assuming one encrypted copy of the ladder serves every DRM system.

Common encryption (ISO/IEC 23001-7) defines several protection schemes, and two matter in practice: AES-CTR full-sample ('cenc') and AES-CBC pattern encryption ('cbcs'). One of the three widely deployed DRM systems accepts only cbcs, and older client versions of the other two accept only cenc, so a single-copy strategy is correct only if every device class you still support has reached cbcs. Discovering late that one legacy class has not means a second packaging run, a second set of objects in every cache, and roughly double the storage for the affected catalogue.

Comparing floating-point values for equality, or holding money in them

Binary floating point cannot represent 0.1 exactly, so repeated addition drifts and an equality check fails on values that are mathematically equal. Store currency as integer minor units or a decimal type, and compare floats against a tolerance you chose for a stated reason.

Designing for a scale nobody asked for

Ask for request rate, data size, read-to-write ratio and expected growth, then size the simplest option first; one relational instance on current hardware covers a large share of real workloads. Reaching for shards, queues and a cache tier before any number has been quoted reads as pattern-matching rather than judgement.

Questions & practice

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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?

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.

Worked solution (20 min)

  1. State why the ratio cannot be ranked incrementally and draw the phase boundary explicitly.
  2. Write the heap selection with the floor applied at the push site, and give its complexity against a full sort at g = 10^6, k = 50.
  3. Compute the standard error at your chosen floor and use it to defend the floor's value.
  4. Describe the shard-local top-50 merge and say why hash partitioning is what makes it valid.

Expected result: Top 50 groups with ratio and sample count, produced by an O(n) hash aggregation followed by an O(g log k) heap selection over floored groups, O(g) space for the aggregate and O(k) for the selection.

Verify it yourself

  • No returned group sits below the stated sample floor.
  • The returned set is identical to fully sorting the floored groups.
  • Adding a group with 2 starts and 2 rebuffers does not change the output.

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?

Alert on a rolling rebuffer ratio over a time-based window

Medium · sliding window · two pointers · alerting

A session's QoE samples arrive in playback order as (sequence, wall_clock_ms, wall_delta_ms, rebuffer_ms). Find the earliest sample at which the rebuffer ratio over the trailing 120,000 ms of wall time exceeds 5%, ignoring any window covering under 30,000 ms of wall time. Sample spacing varies between 2 and 30 seconds depending on device and player version, and a long session can carry 10^5 samples. Return the sample index or none. Target O(n) time and O(1) additional space.

Approach

  1. Two indices over the time-ordered array with running integer sums of wall_delta_ms and rebuffer_ms. Advance the right index one sample at a time, then pull the left index forward while the window's wall span exceeds 120,000 ms, subtracting as you go. Each index only moves forward, so total work is O(n) with two accumulators and no auxiliary structure.
  2. Define the window by accumulated wall time, not by sample count. Spacing varies by more than 10x, so a fixed count of samples is a window somewhere between four minutes and an hour depending on which device reported it.
  3. Gate the predicate on a minimum covered span of 30,000 ms. Without it, the first two samples of any session containing one stall produce a window at or near 100% and every session with a startup stall pages someone.
  4. Name the naive alternative and its cost: recomputing the sums across the window at each step is O(n * w), roughly 6 x 10^6 operations at 10^5 samples and a 60-sample window. Acceptable for one session on demand, not acceptable when the same evaluation runs against 10^6 concurrent sessions.
  5. Keep both accumulators as int64 milliseconds so add-then-subtract is exact; a float accumulator drifts over 10^5 add/subtract pairs and the drift is largest on exactly the longest sessions.

Follow-up

  • Evaluate it incrementally as samples stream in with no array retained. What state do you keep per session, and what is its memory bound at 10^6 concurrent sessions?
  • A late sample arrives out of order after the window has already moved past its timestamp. Do you re-evaluate, and what does that do to the idempotency of the alert?

Withdraw a rights window and keep a reconstructable history

Easy · soft delete · audit history · triggers · temporal queries

availability_window(id, content_version_id, territory_code, licence_type, starts_at, ends_at NULL, status ENUM('draft','active','withdrawn'), updated_at, updated_by) is edited by operators rather than by deploys. Six months later a rights dispute asks what a row said at 14:00 on a given day and who made it say that; updated_at and updated_by hold only the most recent edit. Design the history table and the capture mechanism, decide whether withdrawal is a status change or a DELETE, and write the query that reconstructs every window that was active for a territory at a past instant.

Approach

  1. Split the two jobs. Current state stays in availability_window, which the serve path reads at roughly 10^4 QPS and must never join to history. History goes in availability_window_history(window_id, revision INT, <every column of the base row>, operation ENUM('insert','update','delete'), changed_by, change_reason, valid_from, valid_to NULL, PRIMARY KEY (window_id, revision)).
  2. Snapshot the whole row, not a diff. A diff makes 'state at T' a replay from revision 1 — O(revisions) per row, and wrong for any row whose early revisions predate the audit table. A full snapshot makes the same question a single range predicate.
  3. Capture with an AFTER INSERT OR UPDATE OR DELETE row trigger rather than in application code. This table's writes are rare, bursty and operator-driven, and a real correction is often an ad-hoc UPDATE run by hand against the database — which never passes through the application and so never writes history if the application is what does the capturing.
  4. Make withdrawal a status change, never a DELETE. license_grant and qualified_play rows describe what was actually served, and 'no row exists' is indistinguishable from 'no row ever existed'. Protect the serve path from the resulting filter debt with a partial index WHERE status = 'active' and a view that carries the predicate, so a forgotten status <> 'withdrawn' cannot serve a withdrawn window.
  5. Close the interval on write: the trigger sets the previous revision's valid_to to the new revision's valid_from, half-open, so reconstruction is WHERE valid_from <= :t AND (valid_to IS NULL OR valid_to > :t) rather than a correlated 'latest revision before :t' subquery. Index it on (window_id, valid_from), plus (territory_code, valid_from) if audits filter by territory.
  6. Keep the two time axes apart in the reconstruction query: valid time is when the row said what it said, and starts_at/ends_at is the interval the row describes. Select the revisions live at :t, then apply the serve-time predicate to that snapshot. Conflating them answers a different question that still looks plausible.

Follow-up

  • An operator edits the same window twice inside one transaction. How many revisions should exist, and how many does your trigger produce?
  • The dispute asks not what the row said but what was actually served. Which table answers that, and what do you owe if the two disagree?
  • How would you detect that the trigger was disabled for a bulk load?

Two child tables that quietly inflate a watch-time report sixfold

Medium · join fan-out · pre-aggregation · distinct aggregates

playback_session(id UUID PK, account_id, content_version_id, played_ms BIGINT, started_at, cdn_pop). license_grant(id, session_id, key_id, issued_at, status) has several rows per session because live channels rotate keys. qualified_play(id, session_id, content_version_id, status ENUM('pending','counted','reversed'), qualified_at) records a correction as a second row rather than an update. A daily report joins all three and reports, per content version: sessions, total played_ms, licences issued and counted plays. Total watch time comes out roughly six times too high. Explain the mechanism, write the corrected query, and say why COUNT(DISTINCT ...) is not a complete fix.

Approach

  1. Count the rows the join actually produces: one per (session x grant x play). A live session with six key rotations whose play was reversed and re-counted contributes 6 x 2 = 12 rows, so SUM(ps.played_ms) adds that session's watch time twelve times. The multiplier varies per session, which is why the result is 'roughly six times' rather than exactly anything — there is no constant to divide out.
  2. Fix the counts with COUNT(DISTINCT lg.id) and COUNT(DISTINCT qp.id) FILTER (WHERE qp.status = 'counted'), and price it: distinct aggregates sort or hash per group, so the planner gives up the cheap hash aggregate it would otherwise use.
  3. State why the sum cannot be rescued the same way. SUM(DISTINCT ps.played_ms) deduplicates by value, so two different sessions that each played exactly 1,800,000 ms collapse into one contribution. Deduplicating a measure by its value is wrong in general; the only correct move is to reduce each session to one row before summing.
  4. Write the corrected shape: aggregate each child to session grain independently, then join. WITH g AS (SELECT session_id, count(*) AS grants FROM license_grant GROUP BY 1), p AS (SELECT session_id, count(*) FILTER (WHERE status = 'counted') AS counted FROM qualified_play GROUP BY 1) SELECT ps.content_version_id, count(*) AS sessions, sum(ps.played_ms) AS played_ms, coalesce(sum(g.grants), 0) AS grants, coalesce(sum(p.counted), 0) AS counted FROM playback_session ps LEFT JOIN g ON g.session_id = ps.id LEFT JOIN p ON p.session_id = ps.id GROUP BY 1. Every join is now one-to-at-most-one, so no row is duplicated.
  5. Compare the cost: two hash aggregates over the child tables plus two hash joins, each linear in rows scanned, against a plan that first materialises the product of the two children and then aggregates the larger intermediate. Keep the joins LEFT with COALESCE so a session that started but never reached a licence stays in the session count — an INNER join drops it silently and changes what the report means.
  6. Say which column carries the highest correctness bar: played_ms feeds reporting, the counted play feeds payout, and a duplicate there is money moved from one rights holder's share to another's. The bar differs by column inside a single report.

Worked solution (25 min)

  1. Build a fixture on one content version: session A with 6 licence grants and 2 qualified_play rows (one counted, one reversed), session B with 1 grant and 1 counted play, session C with no grants and no plays.
  2. Run the naive three-way LEFT JOIN and record the output row count and SUM(played_ms).
  3. Isolate session A in the join output and confirm it occupies grants x plays rows.
  4. Rewrite with per-child CTEs at session grain and re-run.
  5. Switch the LEFT JOINs to INNER and note which session disappears.

Expected result: The naive join emits 12 rows for A, 1 for B and 1 for C, and its watch time equals 12xA + B + C. The corrected query emits one row for the content version with watch time A + B + C, grants 7, counted plays 2, and session C present with grants 0. Under INNER joins session C vanishes and the session count drops to 2.

Verify it yourself

  • The corrected SUM(played_ms) equals the same sum taken from playback_session alone with no join at all.
  • Result rows equal the number of distinct content versions, and each child count is independent of the other child's row count.
  • Deleting the reversed qualified_play row changes the counted total by zero.
  • Adding a seventh licence grant to session A changes grants by one and played_ms by nothing.

Follow-up

  • The same report is wanted broken down by CDN POP and device class. Does the pre-aggregation survive, or does each CTE need the extra key?
  • A play was reversed and later re-counted. Should the report show one counted play or zero, and stamped at which timestamp?
  • How would you catch this class of bug in CI, given the naive query returns a plausible number rather than an error?

Describe your approach to improving the output of LLM-based coding tools.

Medium

Approach

  1. State your assumptions explicitly before working the problem.
  2. Say what you would check first and why it is the highest-information step.
  3. Work from the requirement backwards to the design.

Follow-up

  • What assumption would you test first?
  • How would you know your answer was wrong?

What are the primary security considerations when implementing cryptographic protocols in a production environment?

Medium

Approach

  1. Say what you would check first and why it is the highest-information step.
  2. Work from the requirement backwards to the design.
  3. State your assumptions explicitly before working the problem.

Follow-up

  • How would you know your answer was wrong?
  • What assumption would you test first?

Hold the concurrent-stream limit with one atomic lease operation

Easy · leases · atomicity · ttl-tuning · hot-key

An account has a plan limit of concurrent streams. Session starts run 10,000 per second at peak against 1,000,000 concurrent sessions, each renewing its lease every 10 seconds. Two devices in one household can start within the same millisecond, and a client that crashes never releases its slot. Using one lease store, give the exact acquire operation, the data structure and key, the lease TTL with the arithmetic behind it, what a renewal returns when the lease has already expired, and what the client does with that answer.

Approach

  1. Pick a structure that makes expiry part of the read. A sorted set per account, key lease:{account_id}, member session_id, score expiry in epoch milliseconds, lets one operation drop the dead members and count the live ones. Storing a plain counter forces a second structure to know which session holds which slot, and the two drift.
  2. Make acquire one server-side operation: ZREMRANGEBYSCORE key 0 now, then ZCARD, then ZADD only if the count is under the limit, all inside a single Lua script. The script runs to completion without interleaving, so two simultaneous acquires at the limit cannot both observe the pre-add count. Any design where the application reads the count and then writes is a lost-update race that two devices starting together will win together.
  3. Make renewal its own script: return lease-lost when ZSCORE for that session is nil, and otherwise write the new expiry score. Expiry has already removed a member that missed too many heartbeats, so a renewal after a network gap fails and the client must re-acquire, which re-checks the limit. A blind ZADD re-adds the evicted session and quietly takes the account over its plan, which is the same lost-update bug as the naive acquire wearing different clothes.
  4. Derive the TTL instead of picking one. TTL = k x heartbeat interval sets both failure modes at once: a viewer is evicted only after k consecutive missed heartbeats, and a crashed client strands a paid slot for at most the TTL. At 10s heartbeats, TTL 30s tolerates two misses and caps the stranded slot at 30 seconds. Note that heartbeat losses are correlated rather than independent, since one access-network blip takes all three, so any p^k eviction estimate is optimistic.
  5. Bound the keyspace and the hot key. PEXPIRE the whole account key to TTL plus slack so accounts that stopped streaming do not accumulate forever, and release explicitly with ZREM on a clean stop so the common case does not wait out the TTL. Keying by account_id puts all of an account's leases on one shard, which is what makes the script atomic; at a limit of four, that key sees single-digit operations per second, while the fleet total is 1,000,000 / 10s = 100,000 renewals per second spread across shards by account hash.

Worked solution (20 min)

  1. Write the acquire script line by line and mark the point where a non-atomic version would interleave.
  2. Write the renewal script and state what it returns in the expired case and what the client does with that answer.
  3. Fix a heartbeat interval, derive the TTL from it, and state the false-eviction condition and the stranded-slot ceiling as numbers.
  4. Compute fleet-wide renewal operations per second and the per-key rate, then choose the shard key.
  5. Add the clean-release path and the key-level expiry, and say what each one prevents.

Expected result: A per-account sorted set scored by expiry, an acquire that prunes, counts and inserts inside one atomic script, a renewal script that reports lease-lost on an absent member and forces a re-acquire, a TTL of three heartbeat intervals with both failure modes quantified, explicit release plus a key-level expiry, and account_id as the shard key justified by the atomicity requirement.

Verify it yourself

  • Two acquires issued concurrently against an account already at its limit result in exactly one success and one refusal.
  • A session that misses three consecutive heartbeats finds its renewal returns lease-lost rather than silently succeeding.
  • Killing a client without a clean stop frees its slot within the TTL and no later.
  • An account that has not streamed for a day holds no keys in the lease store.

Follow-up

  • The lease store loses a shard for 30 seconds. Do you fail open and let sessions start unchecked, or fail closed and block them, and what does each cost?
  • A viewer switches from wifi to cellular and the app backgrounds for 45 seconds. Trace what they see, and change the design so it is not an eviction.
  • Downgrade a plan from four streams to two while four streams are playing. Which sessions stop, when, and who decides?

Concurrency limit over-admits only during live-start bursts

Hard · race condition · leases · atomicity · idempotency

Support reports accounts playing four concurrent streams on a two-stream plan, a few hundred times a day, clustered in the minutes after a scheduled live start. It never reproduces locally or in load tests below 500 starts per second. Acquire reads the account's active-lease count from the lease store and, if it is under the limit, adds a member with a TTL; heartbeats renew by removing and re-adding the member; clients retry a start that times out. Give the ordered checklist, the acquire primitive you would write, and the test that proves it.

Approach

  1. Rank hypotheses against the timing evidence before instrumenting anything. Read-then-write is a time-of-check-to-time-of-use race and explains clustering at bursts. A count read from a replica explains cases correlated with one region. A retry that mints a fresh session id explains excess leases that share a device but differ in session id. All three can be live at once, which is why one fix often fails to close it.
  2. Instrument the decision inputs rather than the outcome: observed count, limit, whether the member was added, which lease-store node served the read, and the client attempt number. One burst of these logs settles which mechanisms are firing and in what proportion.
  3. Close the check-then-act window first, because it is the one that scales with load. Make acquire a single server-side atomic operation over a sorted set keyed by account, with score equal to the lease expiry instant: in one script, ZREMRANGEBYSCORE up to now, then ZCARD, then ZADD only if the count is below the limit, returning acquired or refused. Cost is O(log N + M) for M expired members, and it cannot interleave because the script runs to completion on a single node.
  4. Close the renew hole second. Remove-then-add is two steps with a window in which the member is absent, so a concurrent acquire sees a free slot; under a burst that window is hit constantly. Renew must be one ZADD XX that updates the score of an existing member only, and must fail rather than re-create a member that has already expired.
  5. Close the retry hole third. The session id is client-generated, so a retry must reuse it and acquire must be idempotent on (account_id, session_id): re-acquiring an id already held refreshes the score and returns success without consuming a second slot. Without this, every timeout at a live start manufactures a second lease.
  6. Prove it with a test that fires limit + K concurrent acquires against one real lease-store instance and asserts exactly limit successes, repeated enough times that interleavings are likely, plus a test that renewing after expiry fails instead of resurrecting the member. A test built on a process-local mutex proves nothing, because the race is across service replicas.

Follow-up

  • What lease TTL do you pick against a 10-second heartbeat, and what are the false-eviction and stranded-slot numbers that follow?
  • How does the slot come back when a client crashes without sending a stop?
  • The lease store fails over mid-burst: do you fail open or fail closed, and who pays for that choice?

Suggested practice checklist

For a candidate senior enough that the loop turns on design and judgement rather than on whether the coding round gets finished. Five days build one system properly and then stress it; coding gets a single maintenance day, on the assumption that the risk at this level is an unexamined tradeoff rather than a missed algorithm.

Day 1 — Numbers before diagrams

  • Build your own reference card of the figures you will re-derive all week: bytes for a realistic record, requests per second implied by a given daily active count, and the storage that a year at a given write rate produces. Derive each one rather than copying it, because the derivation is what survives a follow-up.
  • Turn one product statement into capacity requirements. From ten million daily users at four writes and forty reads each, state the peak-to-average factor you are assuming and why, then produce peak write QPS, peak read QPS and a year of storage.
  • Write the two numbers whose order of magnitude changes the design, the read-to-write ratio and the working-set size against memory per node, and state the threshold at which each one flips your answer.

Deliverable: A one-page numbers card and one worked capacity estimate with every assumption written down.

Day 2 — One system, from requirements to schema

  • Spend the first ten minutes producing only functional requirements, non-functional targets with numbers attached, a p99 latency, a durability expectation, a consistency requirement, and an explicit out-of-scope list.
  • Define the interface before the boxes: the three or four endpoints, their parameters, what each returns, and which of them are idempotent.
  • Write the data model, then write the single access pattern that justifies it, and state what the schema would have to become if the dominant access pattern were the other one.

Deliverable: One design carried to endpoint-and-schema depth, with non-functional targets expressed as numbers and a written out-of-scope list.

Day 3 — The consistency you are actually buying

  • Write out what a client sees under asynchronous replication when its write commits on the leader and its next read is served by a lagging follower, then write the two fixes, pinning that session's reads to the leader for a bounded window or carrying a version token the replica must reach, and the cost of each.
  • Work the quorum arithmetic on paper for N of three with W and R of two, and separate what R + W > N does guarantee, that any read set intersects any write set, from what it does not: on its own it is not linearizability, and a sloppy quorum that accepts writes on nodes outside the preference list breaks even the intersection.
  • Take two storage choices with different defaults, a single-leader relational store committing synchronously and a quorum-replicated store that converges eventually, and write the specific product behaviour that would be wrong under each, rather than a general statement about which is stronger.

Deliverable: A page separating what quorum overlap guarantees from what it does not, with one concrete product misbehaviour attached to each gap.

Day 4 — Failure is the design

  • For one write path, work through the case where the client times out after the server has already committed, then design the idempotency key: who generates it, how long it is retained, and what the duplicate request returns.
  • Express the retry policy as parameters rather than as a word: maximum attempts, base delay, backoff factor, jitter, and which error classes are retried at all. Then state why retrying a non-idempotent write without a key is a correctness bug and not merely waste.
  • Compute the fan-out effect on tail latency. If a request waits on ten backends and each independently exceeds its p99 one percent of the time, the chance at least one is slow is 1 - 0.99^10, about ten percent. Then write why independence is the optimistic assumption and what correlates them in practice.
  • Name the backpressure mechanism for one queue or one dependency in the design, a bounded queue with shedding or a concurrency limit, and write what the caller is told when it engages.

Deliverable: One write path with an idempotency design, a parameterised retry policy, and a written tail-latency calculation with its assumption named.

Day 5 — Scaling the hot path

  • Choose cache-aside or write-through for one read path and write the staleness window each produces, then name the invalidation event and what the system does when that event is lost.
  • Design against the stampede: either coalesce requests so only one recomputes a missing key, or refresh early with jittered expiry, and write why identical TTLs on keys populated in the same moment produce a synchronised expiry and a thundering herd.
  • Shard one table by a key you choose, then answer the two questions that break the choice: which queries now require a scatter-gather, and what happens to the distribution when one tenant is a hundred times larger than the median.
  • Write the cost of adding a node under plain modulo placement, where nearly every key moves, against consistent hashing, where roughly one key in n+1 moves, and state what virtual nodes are for.

Deliverable: A caching and sharding decision for one path, each with its failure mode and its rebalancing cost written beside it.

Day 6 — Keep the coding hand in, at the bar that applies to you

  • Solve one medium problem in thirty minutes, then spend twenty more making it production-shaped: named invariants, validation at the boundary, and errors that distinguish a caller mistake from an internal fault.
  • Write the tests you would require of a colleague's version of that function: one for empty input, one for the boundary, and one for the case the implementation is most likely to get wrong.
  • Read a piece of your own code from six months ago and write the change you would ask for, phrased as you would actually phrase it in review.

Deliverable: One problem hardened to review standard, with its test list and one written review comment.

Day 7 — Defend it while being interrupted

  • Run a forty-five-minute design mock with an interviewer briefed to change a requirement halfway, a tenfold traffic increase or a new strict consistency requirement, and to push on one number you estimated.
  • Rehearse the two sentences a senior loop is listening for: naming the tradeoff you are choosing against and why, and saying what you would measure to learn that the choice was wrong.
  • Prepare the design you regret: a real decision, the constraint that produced it, what it cost, and what you changed afterwards.

Deliverable: Mock notes recording how the design changed under the new requirement, plus a written account of one regretted decision.

Know where your numbers came from

Bring the two or three numbers the story rests on and know how they were collected. A p99 whose timer starts inside your handler excludes the time a request spent queued, so it can sit flat while users wait longer. Give the window, the percentile and what the measurement left out, or drop the number.

What draws you to the intersection of AI-native game development and high-performance computing?

Medium · behavioural and engineering judgement

Approach

  1. Name the disagreement and how you resolved it with evidence.
  2. Pick a story where you made the decision, not one where you watched it.
  3. State the situation in two sentences and spend the rest on the reasoning.

Follow-up

  • What did you decide not to do, and why?
  • What would you do differently if you ran that again?

Why are you leaving your current company?

Medium · behavioural and engineering judgement

Approach

  1. Close with what you would do differently, concretely.
  2. Pick a story where you made the decision, not one where you watched it.
  3. Give the blast radius: what could have broken, and what you measured.

Follow-up

  • What did you decide not to do, and why?
  • What would you do differently if you ran that again?

How do you handle ambiguity when project requirements are still being defined?

Medium · behavioural and engineering judgement

Approach

  1. State the situation in two sentences and spend the rest on the reasoning.
  2. Close with what you would do differently, concretely.
  3. Give the blast radius: what could have broken, and what you measured.

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 had to resolve a technical disagreement within your team.

Medium · behavioural and engineering judgement

Approach

  1. Give the blast radius: what could have broken, and what you measured.
  2. Pick a story where you made the decision, not one where you watched it.
  3. Close with what you would do differently, concretely.

Follow-up

  • What would you do differently if you ran that again?
  • What did you decide not to do, and why?