Interview guides · Bauer Media Group
Bauer Media Group Software Engineer Interview Questions & Guide 2026
Timed coding practice for Bauer Media Group software engineer candidates: full attempts under a clock, then scored against a rubric.
The 60-second brief
As a Software Engineer at Bauer Media Group, you are at the intersection of world-class media production and digital innovation. You will be responsible for building, maintaining, and scaling the platforms that deliver content to millions of users globally. Whether working on internal tooling, bidding platforms, or complex data pipelines, your work directly affects how Bauer Media Group's audiences engage with its brands and how its business operations function at scale.
For a design discussion, a catalogue of architectures is worth less than the ability to turn a vague requirement into a data model and an API contract. A box diagram with no schema under it collapses at the first follow-up question.
Focus areas
- Enforce entitlement at the licence boundary
- Key CDN caches without sharding on tokens
- Expire rights across cached and offline copies
Overview
This role requires a blend of technical precision and creative problem-solving. You will often be tasked with translating complex business requirements—such as auction logic or data integration needs—into clean, performant, and maintainable code. Success in this role means not just writing functional software, but understanding the Bauer Media Group ecosystem and ensuring your technical solutions align with the company's long-term strategic goals.
The Bauer Media Group interview process
Bauer Media Group candidates report 3 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.
Initial Conversation
When a round has no standard shape, it is often there because something is still open: an area no earlier conversation reached, a round where the signal came out mixed, or a decision someone is not ready to make alone. Work out which by going back over what each earlier round actually covered rather than how it felt, and arrive able to give evidence on that point without being asked twice. Weak answers replay the loop's earlier material at the same depth. Strong ones go a level deeper and stay consistent with what you already said.
What to demonstrate
- Whether your account of a project matches the one you gave earlier in the loop, since what you said before may be available to whoever runs this round
- Whether you can go a level deeper on something already covered, reaching the decision and its alternatives rather than repeating the summary
- Whether you state your own uncertainty accurately, including parts of a system you did not build and decisions you inherited, instead of claiming even ownership across all of it
- Whether you can answer a question you handled poorly earlier by naming what you missed, rather than delivering a polished second version as if the first had not happened
How to prepare
- Reconstruct the loop on one page: for each round, the questions you were asked and the answer you actually gave, not the better one you thought of afterwards. The gaps on that page are your best available guess at why this round exists.
- Take the two claims you made earlier that carry the most weight and assemble the backing for each: the measurement, the date, what broke, the decision you would make differently now.
- Write down the three facts about your work that must not drift between tellings, such as team size, timeline and your own role, and check your stories against that list rather than trusting recall under pressure
Technical Assessment
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
Cultural Fit Evaluation
Most of the time lost in this format is not lost to thinking. It goes to a standard-library call you half-remember, an off-by-one in a loop bound, and a debugging loop that mutates code at random until something passes. When output is wrong, stop re-reading the whole function: take the smallest input that reproduces it and walk the state through by hand, printing intermediates if the environment allows. Guessing at a fix without a failing case you understand is how a five-minute bug becomes twenty, and the clock does not pause while you do it.
What to demonstrate
- Whether you reach the right structure without a detour, and can write it from memory rather than only recall that one exists
- Whether overflow is considered where the language has fixed-width integers, since a signed 32-bit value stops at 2,147,483,647 and then wraps in Java, is undefined behaviour in C++, and does not arise in Python, whose integers grow instead
- Whether recursion depth is treated as a constraint on large inputs, given that CPython's default limit is 1000 frames and a deep recursion can exhaust the stack in any language where an iterative version would not
- Whether a failing case is isolated and explained before any edit is made to the code
How to prepare
- From an empty file and with no references open, implement the pieces you lean on most: a heap push and pop, an iterative DFS with an explicit stack, and a binary search whose midpoint is written lo + (hi - lo) / 2, which avoids the overflow that (lo + hi) / 2 can hit in a fixed-width integer type
- Time yourself on the ten library calls you look up most, such as sorting with a custom comparator, splitting and joining strings, and finding the next key at or above a value in an ordered map, until the lookup is gone
- Take a solution you know is broken and, before touching it, write one sentence naming the input, the expected value and the actual value. Repeat until you do it without deciding to.
Where candidates lose points
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.
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.
Choosing a schema before the access patterns are known
Write the queries first, with their filters, sort orders, cardinalities and which ones sit on the latency-critical path, then design tables and indexes to serve them. An index nothing queries still costs write throughput and storage, and a hot query with no supporting index becomes a full scan that only hurts once the table is big.
Questions & practice
Choose ladder rungs against a measured bandwidth distribution
Hard · dynamic programming · optimisation · cost modelling
You have 60 candidate encoding rungs sorted ascending by bitrate, and an audience bandwidth histogram of 1,000 buckets measured from delivered throughput. A viewer plays the highest rung whose bitrate is at most 80% of their sustained throughput, and cannot play at all if no rung qualifies. Choose exactly 12 rungs, always including the lowest candidate, maximising total delivered bitrate across the audience. Return the chosen rungs and the objective value, state the complexity, and say why a log-spaced ladder is not automatically the answer.
Approach
- Precompute for each candidate i the bucket at which viewers can first afford it, threshold_i = bitrate_i / 0.8, by binary search over the bucket edges: O(n log m). Build a prefix sum over bucket counts so any viewer-count range is O(1).
- Formulate the contribution: with the selected set sorted, if rung i is selected and j is the next selected rung, every viewer whose threshold falls in [threshold_i, threshold_j) plays rung i, contributing count * bitrate_i. Call it value(i, j), computable in O(1) from the prefix sums.
- Run the DP over (last selected rung, rungs used): f[j][t] = max over i < j of f[i][t-1] + value(i, j), with the lowest rung forced selected and a sentinel j = n+1 closing the final interval. O(n^2 k) = 60^2 * 12, about 43,000 transitions. The point is that the ladder is measured, not that the algorithm is clever.
- Say when the structure buys more: if value satisfies the concave quadrangle inequality, the inner maximisation admits divide-and-conquer optimisation at O(n k log n). That condition depends on the histogram's shape and must be tested on the real distribution rather than assumed.
- Name what the objective leaves out, because the interview point is the modelling: storage and cache footprint scale with rung count, device decode limits make some rungs unusable by whole client classes, and delivered bitrate is a crude proxy for perceived quality. Log spacing is a prior about a smooth unimodal distribution, and measured throughput is usually bimodal, which is exactly where the prior loses.
Follow-up
- Add a per-device decode cap so one client class cannot use rungs above 1080p. Does the DP still decompose, and along which dimension?
- Throughput varies during playback and the player switches rungs. What does that do to the highest-affordable-rung model, and what would you measure instead?
- Storage and cache footprint become a hard budget rather than a fixed rung count. Which dimension of the DP state changes?
Audit segment boundary alignment across the catalogue without pairwise comparison
Hard · hashing · binary search · batch verification
Every video rendition of a content version must share identical segment boundaries. Per content version you have 6-12 renditions, each with a sorted int64 list of segment start times in milliseconds, up to 3,600 entries for a two-hour title. The audit runs over 10^4 content versions a day. The obvious check compares every rendition against every other element by element, and it is correct. Give a method fast enough for the daily run, localise the first divergence when a version fails, and state the complexity of both.
Approach
- Price the naive version first, because it is correct and the argument has to be about cost: C(12,2) = 66 pairs times 3,600 elements is about 2.4 x 10^5 comparisons per version, so 2.4 x 10^9 a day. Embarrassingly parallel, and still CPU you do not need to spend.
- Replace pairwise equality with a canonical fingerprint: one streaming 64-bit hash per rendition over the boundary sequence, O(R * S) per version, then group renditions by fingerprint. Do the arithmetic rather than asserting a win: 12 x 3,600 = 4.32 x 10^4 element hashes per version, so 4.3 x 10^8 a day against 2.4 x 10^9 comparisons. That is 5.5x, not an order of magnitude, and 5.5 is not a remembered constant - it is (R - 1) / 2, the R^2 to R factor, which falls to 2.5x on a six-rung ladder. Per element a hash mix also costs more than an int64 compare, so treat 5.5x as an upper bound on element touches, not on wall clock.
- The durable reasons to prefer the fingerprint are structural rather than that ratio: each list is read exactly once so renditions never need to be resident together, and 8 bytes per rendition persists, which is what lets the check run incrementally as rungs become ready instead of as a batch. Say which direction can lie: a mismatch is always a real difference, while equality is equality up to collision probability, which is what you are trading away.
- Localise a failure with prefix hashes: build a prefix-hash array per rendition in O(S), then binary search for the lowest index at which two renditions' prefix hashes differ. O(log S), about 12 probes instead of a 3,600-element scan.
- Check the stronger property at publish time, since equal lists do not prove the boundaries are legal. Frames per segment = segment_duration_ms * frame_rate / 1000, and gop_size_frames must divide it. At exactly 25 fps a 6,000 ms segment is 150 frames and GOP sizes 25, 50 or 75 divide it cleanly. Do the NTSC case in the rational rate, not in the stored column: the true rate is 24000/1001 fps, so a 6,000 ms segment is 144000/1001 = 143.86 frames and can never align, while 6,006 ms is exactly 144 frames. The frame_rate_milli_fps column stores 23976, a rounding that drifts about 1 ms per 1,000 s (roughly 7 ms over a two-hour title) and computes that same aligned segment as 143.999856 frames.
Worked solution (35 min)
- Write both costs as arithmetic expressions in R, S and the daily version count, evaluate them, and derive the ratio as (R - 1) / 2 rather than quoting a number.
- Define the fingerprint over int64 boundaries and state explicitly which direction of the comparison is probabilistic.
- State the reasons to adopt it that do not depend on that ratio: one pass per list, 8 bytes retained per rendition, incremental rather than batch.
- Write the prefix-hash binary search and give its probe count at S = 3,600.
- Work the frames-per-segment arithmetic at 25 fps and at 24000/1001 fps, show which segment durations are legal at each, and state what the audit still cannot see.
Expected result: A 64-bit fingerprint per rendition with grouping in O(R * S) per version - about 4.3 x 10^8 element hashes a day against 2.4 x 10^9 pairwise comparisons, a factor of (R - 1) / 2 = 5.5 at twelve rungs - plus prefix-hash binary search returning the first divergent segment index in O(log S) for the versions that fail.
Verify it yourself
- Perturbing one boundary by 1 ms in one rung changes exactly one fingerprint, and the binary search returns that index.
- Two renditions differing only in bitrate produce identical fingerprints.
- On a small synthetic version the reported divergence index matches a linear element-by-element scan.
- Halving the rendition count from 12 to 6 halves the fingerprint cost and cuts the pairwise cost by about four, matching the stated (R - 1) / 2 ratio at both points.
Follow-up
- The audit is green but one player stalls switching from rung 4 to rung 5 at 00:41:12. What do you check that list equality cannot tell you?
- Run the audit incrementally as each rendition reaches status ready rather than as a nightly batch. What do you persist per content version, and what does a rung that arrives a day late cost you?
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?
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?
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
- 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 inavailability_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)). - 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.
- 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.
- Make withdrawal a status change, never a DELETE.
license_grantandqualified_playrows 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 indexWHERE status = 'active'and a view that carries the predicate, so a forgottenstatus <> 'withdrawn'cannot serve a withdrawn window. - Close the interval on write: the trigger sets the previous revision's
valid_toto the new revision'svalid_from, half-open, so reconstruction isWHERE 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. - Keep the two time axes apart in the reconstruction query: valid time is when the row said what it said, and
starts_at/ends_atis 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.
Worked solution (25 min)
- Create the history table and the trigger, then make three edits to one window under three different actors, spaced apart in time.
- Withdraw the window with a status change and confirm the base row still exists.
- Run the reconstruction query at an instant between the second and third edit.
- Issue an UPDATE directly against the database, bypassing the application, and re-run the reconstruction.
- Query the serve-path view at an instant when the withdrawn window's dates are current.
Expected result: Reconstruction at an instant between the second and third edit returns exactly one revision per window, carrying that instant's column values and the actor who set them. The withdrawn window is present in both history and the base table and absent from the active view. The hand-issued UPDATE produces its own revision.
Verify it yourself
- Exactly one revision per window has
valid_to IS NULLat any moment. - A window's revisions tile the timeline with no gaps and no overlaps: each
valid_toequals the nextvalid_from. - The serve-path view returns no
withdrawnordraftrow even when the dates are current. - The number of history rows equals the number of write statements applied, including the one issued outside the application.
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?
What is your process for debugging inconsistent outputs in a large dataset?
Medium
Approach
- 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.
- State your assumptions explicitly before working the problem.
Follow-up
- What assumption would you test first?
- How would you know your answer was wrong?
How would you approach a technical coding challenge that involves complex business rules and data constraints?
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?
Can you walk me through your approach to designing and implementing an ETL solution?
Medium
Approach
- State your assumptions explicitly before working the problem.
- Clarify what is being asked and what a complete answer contains.
- Work from the requirement backwards to the design.
Follow-up
- How would you know your answer was wrong?
- What assumption would you test first?
Evolve the playback-start response without updating shipped televisions
Hard · versioning · backward-compatibility · capability-negotiation
POST /playback/sessions returns {session_id, content_version_id, manifest_url, lease_expires_at}. Three changes are due: the response must offer both cenc and cbcs packagings, because one widely deployed DRM system accepts only cbcs while older clients accept only cenc; it must carry the max_resolution_cap some availability windows impose; and it must return several manifest URLs rather than one. Television clients ship inside firmware and remain in the field for years, and a meaningful share will never be updated. Define the evolution: what you add, what must never change, how the server learns what the caller supports, and how the old contract retires.
Approach
- Sort the three changes by whether a v1 client can ignore them. Adding fields is safe only if v1 clients were specified as tolerant readers; if they were not, a device that rejects unknown keys breaks on the first field you add, and you learn that from a firmware lab rather than from a schema. max_resolution_cap is additive. Replacing manifest_url with manifest_urls[] is not, so manifest_url stays forever, populated with the single best option the caller can actually play.
- Never repurpose a field. The tempting move — keep manifest_url and change which packaging it points at — is a silent breaking change: the type is unchanged, every contract test passes, and the failure surfaces as licence acquisition errors on the one device class you cannot patch. If the meaning changes, the name changes.
- Replace server inference with client declaration. Selecting the packaging by sniffing User-Agent embeds an unbounded device matrix in your server and is wrong for every device released after your last deploy. Have the client declare drm schemes, client security level, codecs and maximum decode resolution, and let the server select; a device shipped next year then works with no server change, provided it declares itself honestly. A caller that declares nothing receives the documented default, which must be the conservative option (cenc, capped resolution), not the newest one.
- Choose the versioning axis deliberately. Capability negotiation covers what a client can play; a major-version boundary in the path or an Accept media type is reserved for a shape change you genuinely cannot make additive. Two endpoint versions are two code paths and two test matrices for as long as both live; a capability flag is one path with a branch. Prefer the branch, and spend the version only when the response's structure, not its content, has to differ.
- Plan a retirement you can actually execute. Instrument responses by capability set so the live population is a number before anyone proposes a sunset; announce with a Sunset header and a date; and accept that for firmware devices the honest end state is a permanently frozen v1 served by a compatibility shim over the v2 resolver rather than a removal. Budget that shim as the standing cost of the original shape.
Worked solution (30 min)
- Write v1 and the target v2 response side by side and label every field added, unchanged, or changed-in-meaning. The third label is the whole problem.
- Write the capability block the client sends and the server's selection rule, including the default applied when the block is absent.
- Trace three callers through the rule — a current client, a five-year-old cenc-only television, and a client declaring a scheme it cannot truly use — and write each response in full.
- Write the retirement plan: the metric that sizes the old population, the header and date you would publish, and the shim you keep when the population never reaches zero.
Expected result: A v2 response strictly additive over v1 with manifest_url retained and populated; a client-declared capability block with a documented conservative default; a selection rule producing three traced responses; and a retirement plan whose end state is a budgeted compatibility shim rather than a removal date you cannot meet.
Verify it yourself
- A v1 client parsing the v2 response still receives a playable stream without reading any new field.
- No existing field's meaning, type or nullability changed; every new behaviour is carried by a new field or by the client's declaration.
- The default for a caller that declares nothing is the most conservative option, and you can name the device class that default exists for.
- Your sunset date is backed by a population figure you could measure today, not by a hoped-for decay curve.
Follow-up
- A firmware class declares support for a scheme, but one model cannot really use it. How does your contract let you correct that without a firmware update?
- Which field in today's response would you most regret having shipped, and what would you have named instead?
- You now need a field that a v1 client would misinterpret rather than ignore. What is your move?
Licence p99 collapses at live start while dependency looks healthy
Hard · lock contention · connection pool · dependency degradation · backpressure
At a scheduled live start, licence issuance goes from 300 to 3,000 requests per second, p99 rises from 40 ms to 9 s, 12% of requests time out and playback fails with decrypt errors. One entitlement-store read replica sits at 400 ms, up from 5 ms, with zero errors and a green health check. The licence path opens a transaction, locks the account's entitlement-snapshot row, calls the entitlement service, writes a license_grant row and commits. Fleet connection pools total 800. Give the ordered checklist and the two changes you would make.
Approach
- Separate queueing from service time first. Break the request into time waiting for a pool connection and time executing. If pool wait dominates, the pool is the queue and the cause sits upstream of the database: a long hold, not a slow statement.
- Do the concurrency arithmetic. Required concurrency is arrival rate times hold time. At 3,000 per second with a 5 ms hold that is 15 connections; with the entitlement call at 400 ms inside the transaction the hold is roughly 405 ms, so required concurrency is about 1,215 against 800 available. Demand exceeds capacity, the queue grows without bound for the duration of the spike, and p99 becomes queueing delay rather than work.
- Measure lock wait per row before assuming which lock matters. Fan-in on the account's entitlement-snapshot row is one account's own sessions, so it is small and is not the ceiling here. State the conditional you are ruling out: had the lock been on a per-channel key row, every session on the channel would serialise on it and the ceiling would be one over the hold time, about 2.5 acquisitions per second at 400 ms, which would collapse far harder than observed.
- Explain the green dashboard so it stops being reassuring. A degraded replica that still answers has no error rate, and availability or p50 indicators hide it. The signals that show it are the dependency's p99 by replica and the caller's own pool-wait metric; a health check that the replica passes tells you nothing about hold time.
- Change one: never hold a lock or a pooled connection across a network call. Resolve entitlement before opening the transaction, so the transaction contains only local writes and the hold drops back to a couple of milliseconds and required concurrency to single digits.
- Change two: bound the blast radius with a per-dependency timeout well below the client's, a concurrency limiter or circuit breaker on the entitlement call, and no unbounded retries, since retrying into a saturated dependency is what turns a slow replica into an outage. Name the degradation you choose: refusing a licence stops playback, so failing open would delete the only enforcement point in the system, and the correct behaviour is to shed load and fail closed with a retryable error.
Follow-up
- Key rotation on a live channel makes every session ask for a licence in the same second. How do you spread that without weakening enforcement?
- What should a client do on a licence timeout, and how do you stop its retries becoming the second wave?
- Would caching licences fix the load, and exactly what would it cost you?
Suggested practice checklist
Four days sample coding, design, fundamentals and the practical rounds at deliberately shallow depth, which is enough to surface the topics you did not know were in scope. That map, rather than a guess made on day one, decides where the last three days go.
Day 1 — Coding, one pass at shallow depth
- Solve one problem from each of six families, an array with two pointers, hash counting, binary search, a tree traversal, a graph traversal and one dynamic program, under a hard twenty-minute cap with no extensions, marking each finished, late, or stalled.
- For every stall, write the exact move you could not make rather than the subject, so the note reads could not turn the recurrence into a loop rather than bad at dynamic programming.
- Fix nothing today. The value of the pass is the unfixed record.
Deliverable: Six timed attempts marked finished, late or stalled, each stall carrying a named blocking move.
Day 2 — Design, one pass at shallow depth
- Spend twenty minutes each on three different shapes, a read-heavy feed, a write-heavy ingest path, and something needing a transaction across two entities, stopping each at requirements, interface and data model.
- After each, write the first question you could not answer, which is usually a number you could not estimate or a failure mode you had no vocabulary for.
- Mark which of the three you would be most relieved not to be asked, and treat that as data rather than as a preference.
Deliverable: Three shallow designs, each with the first unanswerable question written at the bottom.
Day 3 — Fundamentals and the practical rounds
- Answer eight short questions in writing at four minutes each, covering the material that fills the gaps between the big rounds: what happens between a URL and a rendered page, what an index costs on write, when a process is preferable to a thread, and what conditions a deadlock requires.
- Do one thirty-minute practical task of the kind a take-home compresses: read an unfamiliar two-hundred-line file and write what it does, what you would change, and the one thing you remain unsure of.
- Score every answer fluent, correct but slow, or absent, and keep the absent ones visible.
Deliverable: Eight scored short answers and one written reading of unfamiliar code.
Day 4 — The rounds that are about you, and the map
- Deliver three behavioural answers aloud against a timer, a conflict, a failure you owned, and a decision made without enough information, marking any that ran past three minutes or contained no number.
- Assemble the map: every marked item from days one to three on a single page, sorted by how likely it is to appear in your loop rather than by how uncomfortable it felt.
- Choose exactly two areas for the remaining three days and write down what you are deliberately abandoning.
Deliverable: A one-page scored map of the whole surface area with two areas chosen and the rest explicitly abandoned.
Day 5 — First chosen area, to the depth you skipped
- Work the higher-ranked area in four focused blocks, choosing items one level above where you stalled rather than repeating what already works.
- After each block write the rule you extracted in one sentence with its precondition attached, since a rule carrying no precondition is exactly what fails under a variation.
- Re-attempt the day-one or day-two item that exposed this area and compare against the original timing.
Deliverable: Four worked blocks, a timed re-attempt against the original, and three one-sentence rules with preconditions.
Day 6 — Second chosen area, where the gap is coverage rather than speed
- Treat the second area differently from the first. Day five drilled something you could already half-do; this one is usually a topic you had simply never met, so build one worked reference example end to end and keep it, rather than attempting six problems badly.
- Write down the vocabulary you were missing on day two or three, five terms at most, each with the one sentence that makes it usable in an answer rather than the textbook definition.
- Redo the shallow attempt that exposed this area and note whether you now fail later in the problem, because moving the failure point is the realistic gain from a single day and is worth more than a score that did not change.
Deliverable: One worked reference example for the newly covered area, a five-term vocabulary list, and a note on where the failure point moved.
Day 7 — Reassemble the loop
- Sit two rounds back to back with no gap, ordering them so the area you chose second comes last, because the map was built from rested, isolated attempts and the loop will reach your weaker area when you are already spent.
- Write where the second round suffered from the first, which is normally the point at which structure collapses into narration.
- Reduce the week to one page holding only the rules you can state without reading them.
Deliverable: Mock notes on cross-round carryover plus a one-page card of rules you can recite from memory.
The failure that was yours
Keep one story where the bad call was yours rather than a dependency's or a manager's. Name the check that would have caught it, whether you added that check afterwards, and whether it has fired since. Answers that route blame outward end the conversation early; answers that end in a guardrail someone still relies on tend to open it up.
Tell me about yourself and your professional background.
Medium · behavioural and engineering judgement
Approach
- Pick a story where you made the decision, not one where you watched it.
- Close with what you would do differently, concretely.
- State the situation in two sentences and spend the rest on the reasoning.
Follow-up
- How did you know your change caused the improvement?
- What did you decide not to do, and why?
How have you dealt with a difficult customer or stakeholder in a past project?
Medium · behavioural and engineering judgement
Approach
- Give the blast radius: what could have broken, and what you measured.
- Close with what you would do differently, concretely.
- State the situation in two sentences and spend the rest on the reasoning.
Follow-up
- What did you decide not to do, and why?
- How did you know your change caused the improvement?
How do you look for a solution when a new, complex problem arrives?
Medium · behavioural and engineering judgement
Approach
- Name the disagreement and how you resolved it with evidence.
- Give the blast radius: what could have broken, and what you measured.
- 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?