Interview guides · Harmonic
Harmonic Software Engineer Interview Questions & Guide 2026
Testing practice for Harmonic software engineer candidates: the boundaries worth covering, and why flakiness is a real defect.
The 60-second brief
A Software Engineer at Harmonic plays a pivotal role in the development and enhancement of video streaming solutions that are at the forefront of the industry. This position is integral to creating high-quality, reliable products that impact millions of users worldwide. As a Software Engineer, you will work on diverse projects involving both software development and system architecture, contributing to the seamless delivery of broadcast and streaming technologies.
The behavioural round is a technical round in narrative form. Prepare it by collecting specifics you actually owned, such as a design you argued against, an incident you diagnosed, or a decision you later reversed, rather than by rehearsing phrasing.
Focus areas
- Key CDN caches without sharding on tokens
- Expire rights across cached and offline copies
- Cut live latency without starving the buffer
Overview
The challenges you face will be both complex and rewarding, requiring innovative thinking and technical expertise. You will collaborate with cross-functional teams, including product management and operations, to design and implement features that enhance user experiences. The work you do directly influences the quality and performance of Harmonic's products, making it a critical and exciting opportunity for those passionate about technology and its application in media and entertainment.
The Harmonic interview process
Harmonic candidates report 3 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.
HR 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 Interviews
What this round decides is narrow: whether you can produce code that runs and is correct on inputs nobody showed you. An elegant solution that does not compile scores below a plain one that does, so write a correct brute force first, say out loud that you know its cost, and improve it with the working version still on screen. What separates strong answers is who finds the broken case. Trace your own code against an empty input, a single element, and duplicate keys before you say you are finished, because being told is far more expensive than noticing.
What to demonstrate
- Whether degenerate inputs get checked without being asked for: an empty collection, one element, every element equal, and the extreme value the input type allows
- Whether the complexity you state matches the code you actually wrote, including a sort or a copy sitting inside a loop
- Whether the finished answer is verified against the worked examples before you call it done, rather than assumed correct because the code reads correctly
How to prepare
- Take five problems you have already solved and, without running anything, write down what each returns for empty input, a single element, and all-duplicates. Then run them and count how many you predicted wrong.
- Drill the brute force as its own skill: on ten problems, write only the obviously-correct slow version and time how long it takes to get it passing. If that is more than a few minutes, that is what to practise, not the optimal version.
- Add a fixed last step before you submit anything, reading only the loop bounds and the initial value of each accumulator, which is where most off-by-one errors live
Final Discussions
Nobody in the room with you decides this. Interviewers typically write their rounds up separately, often before seeing anyone else's, and the outcome is settled later from those write-ups. A split panel gets resolved by whichever note carries specific evidence, so what you want out of each room is one concrete thing that person could write down: a bug you caught yourself, a trade-off you named, a decision you owned. The rest is arithmetic. The project you describe in a behavioural conversation is often the same system you sketched an hour earlier, and the two accounts have to agree.
What to demonstrate
- Whether the scale, team size and timeline you attach to a project hold steady when that project resurfaces in a different round
- Whether each interviewer leaves with a specific thing to cite rather than a general impression of competence
- Whether a trade-off you defended in one round survives a challenge in another, instead of being quietly swapped for the answer the new interviewer seemed to want
- Whether a question you have already answered earlier in the day gets the same answer at the same depth, without visible impatience
How to prepare
- Write a one-page sheet per project fixing the figures you will quote — request volume, data size, team size, elapsed time, what broke — and say them aloud from the sheet until they come out identical every time
- For each round on the schedule, decide in advance the one sentence you want in that person's notes, then check in a mock that you said it outright instead of leaving it to be inferred
- Have someone ask you the same project question twice, an hour apart, and diff the two answers for numbers that moved or a trade-off that reversed
Where candidates lose points
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.
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.
Trusting input because it came from your own front end
Anything crossing a trust boundary is hostile: parameterise queries instead of building SQL by concatenation, validate against an allow-list rather than a deny-list, and bound the size of anything you allocate from a request. Raising this unprompted in an API or design question is a cheap and unusually strong signal.
Assuming the input fits in memory
Ask how large the input is in bytes before committing to an in-memory algorithm; beyond that point the options are a single streaming pass, an external sort with bounded buffers, or a sketch that trades exactness for constant memory. An algorithm that assumes random access to the whole input is a different algorithm from one that sees each element once.
Questions & practice
Explain the time complexity of your solution.
Medium · data structures and algorithms
Approach
- Walk one small example through your approach before writing the whole thing.
- State the target complexity and say which constraint rules the naive version out.
- Choose the data structure from the access pattern, not from familiarity.
Follow-up
- Which test case would catch an off-by-one here?
- What is the worst case, and how likely is it on real data?
Write a function to reverse a string.
Medium · data structures and algorithms
Approach
- Choose the data structure from the access pattern, not from familiarity.
- Name the brute-force solution and its complexity before improving on it.
- Walk one small example through your approach before writing the whole thing.
Follow-up
- How does this change if the input no longer fits in memory?
- Which test case would catch an off-by-one here?
Solve the problem of removing duplicates from a linked list.
Medium · data structures and algorithms
Approach
- Name the brute-force solution and its complexity before improving on it.
- Restate the input: its shape, its size, and what is guaranteed about it.
- Walk one small example through your approach before writing the whole thing.
Follow-up
- How does this change if the input no longer fits in memory?
- What is the worst case, and how likely is it on real data?
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?
Put segment duration where alignment cannot drift
Medium · normalisation · check constraints · composite foreign key
content_version(id, title_id, duration_ms, encoder_build_id, packaging_status) and rendition(id, content_version_id, stream_kind ENUM('video','audio','subtitle'), width, height, frame_rate_milli_fps, segment_duration_ms, gop_size_frames, status). Every video rendition of a content version must have identical segment boundaries, and each segment must begin at an IDR frame, so gop_size_frames has to divide the segment length in frames. Both facts currently live per rendition and nothing stops a re-encoded rung from disagreeing. Restructure so the database rejects drift, and say what breaks at a frame rate of 24000/1001 with a 2,000 ms segment.
Approach
- Identify the functional dependency. For video rows,
segment_duration_msis determined bycontent_version_id, not byrendition_id. A dependency stored at the wrong grain is exactly what lets two rows disagree, and the invariant is presently a sentence in a comment rather than anything the database can refuse. - Move the authoritative value to the parent as
content_version.segment_frames. Ask whether the child copy earns its keep: the manifest build already loads the content_version row to resolve the URL, so the join costs nothing and the denormalised copy buys no read. If it is kept anyway for write-path convenience, guard it with a composite foreign key —UNIQUE (id, segment_duration_ms)on the parent andFOREIGN KEY (content_version_id, segment_duration_ms) REFERENCES content_version(id, segment_duration_ms)on the child. Under the default MATCH SIMPLE semantics a NULL in either child column satisfies the constraint, which is the escape hatch that lets audio and subtitle rows carry no video segment length. - Fix the representation before writing any CHECK. 24000/1001 is 23.976023976… and is not representable in integer thousandths, so
frame_rate_milli_fps = 23976is already a different number from the real rate and every derived value drifts from it. Store the exact rational asframe_rate_num INTEGER, frame_rate_den INTEGER— 24000 and 1001 — and store the segment length in frames rather than milliseconds. - With frames and an exact rational, both invariants become integer CHECKs that cannot round:
CHECK (segment_frames % gop_size_frames = 0)for IDR alignment, and the millisecond duration becomes derived —segment_frames * 1000 * den / num— rather than a second stored copy to keep in sync. - Work the stated case: at 24000/1001, 2,000 ms is 48000/1001 frames, about 47.952, so no segment of that target length contains a whole number of frames and no integer GOP size divides it. 96 frames is exactly 4.004 s and admits any GOP size dividing 96. The encoder has to round a millisecond target to whole frames, and that rounding is where two rungs encoded at different times end up on different boundaries.
- Accept what the schema still cannot catch. Rate control can relocate an IDR without changing any stored column, so add a post-packaging QC step that compares segment counts and first-sample timestamps across the video renditions of a version and fails the publish on a mismatch, and pin
encoder_build_idper rendition so a rung re-encoded on a newer build is a visible difference rather than an inference.
Follow-up
- One rung fails QC and is re-encoded alone. What do you verify before republishing the manifest, and what is the rollback if boundaries moved?
- Audio renditions legitimately use a different segment length from video. Which of your constraints still applies and which has to be weakened?
- How do you migrate the existing millisecond column to frames without a window in which neither constraint holds?
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
- 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. - Fix the counts with
COUNT(DISTINCT lg.id)andCOUNT(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. - 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. - 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. - 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.
- Say which column carries the highest correctness bar:
played_msfeeds 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)
- Build a fixture on one content version: session A with 6 licence grants and 2
qualified_playrows (one counted, one reversed), session B with 1 grant and 1 counted play, session C with no grants and no plays. - Run the naive three-way LEFT JOIN and record the output row count and
SUM(played_ms). - Isolate session A in the join output and confirm it occupies grants x plays rows.
- Rewrite with per-child CTEs at session grain and re-run.
- 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 fromplayback_sessionalone 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_playrow changes the counted total by zero. - Adding a seventh licence grant to session A changes
grantsby one andplayed_msby 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?
How would you design a scalable API for a video streaming service?
Medium · system design and architecture
Approach
- Fix the scope first: who calls this, how often, and what they do when it fails.
- Name the failure you are designing for, then the recovery path.
- State the consistency you need, and where you are willing to be stale.
Follow-up
- What would you drop to keep the system up under load?
- How does this behave when that dependency is down for an hour?
Discuss the architecture of a microservices-based application you have worked on.
Medium · system design and architecture
Approach
- Name the read and write paths separately; they rarely have the same bottleneck.
- Fix the scope first: who calls this, how often, and what they do when it fails.
- Name the failure you are designing for, then the recovery path.
Follow-up
- How does this behave when that dependency is down for an hour?
- What would you drop to keep the system up under load?
Design a system for processing real-time video streams. What considerations would you take into account?
Medium · system design and architecture
Approach
- Choose a partition key and say what query it makes expensive.
- State the consistency you need, and where you are willing to be stale.
- Fix the scope first: who calls this, how often, and what they do when it fails.
Follow-up
- What would you drop to keep the system up under load?
- How does this behave when that dependency is down for an hour?
Given a scenario involving system performance issues, how would you diagnose the root cause?
Medium
Approach
- State your assumptions explicitly before working the problem.
- 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
- What assumption would you test first?
- How would you know your answer was wrong?
Page a rights-change feed without skipping withdrawn windows
Easy · pagination · change-feed · at-least-once
A partner's catalogue mirror polls GET /availability-windows every minute to keep its copy current. The endpoint pages with limit and offset ordered by updated_at, rights operators edit rows constantly, and the mirror is demonstrably missing withdrawals. The table is availability_window(id, content_version_id, territory_code, licence_type, starts_at, ends_at NULL, status ENUM('draft','active','withdrawn'), max_resolution_cap NULL, created_at, updated_at, updated_by), indexed on (content_version_id, territory_code, starts_at, ends_at). Design the pagination and change-feed contract: the cursor, the ordering, how a withdrawal reaches the mirror, and what the caller does when a page fails.
Approach
- Name the failure mechanically. OFFSET is a position into a result set recomputed on every request. A row edited mid-scan gets a fresh updated_at, sorts behind the cursor, and shifts every later row one position forward — so the row that slid into the vacated offset is never returned. It also scans offset+k rows per page and degrades linearly with depth, where a keyset seek is O(log n + k).
- Replace it with a keyset cursor on (updated_at, id) and a strict row comparison,
WHERE (updated_at, id) > (:ts, :id) ORDER BY updated_at, id LIMIT :n. The id is not decoration: without a unique tiebreak, rows sharing a timestamp straddle a page boundary and are dropped or repeated forever. This needs a new index on (updated_at, id) — the existing one is built for the serve-time range predicate and cannot order this scan. - Close the visibility gap, which is the part most candidates miss. Do not serve up to now(): a transaction that began before your high-water mark can commit after you have read past it, carrying an updated_at that now sits behind the cursor, so that row is never seen again. Either hold the watermark back by more than the longest write transaction, or order by a commit-ordered sequence instead of a wall-clock column.
- Make withdrawal a row rather than an absence. status flips to 'withdrawn' and updated_at bumps, so it travels the feed like any other edit. Hard deletion would leave the mirror able to learn about a withdrawal only by diffing the entire catalogue, which is the diff you built the feed to avoid.
- State the delivery guarantee out loud. Keyset-by-updated_at is at-least-once — a row edited during a scan is re-delivered — so the contract must require the consumer to apply rows idempotently keyed on id and must forbid treating a repeat as a new event. A failed page is simply refetched from the last committed cursor, and the cursor advances only after the page is durably applied.
Worked solution (15 min)
- Build a ten-row table by hand, page it with limit=3 at offsets 0, 3 and 6, and edit row two's updated_at between pages. Write down the id that is never returned.
- Redo the same sequence with a keyset cursor and write down which id is returned twice instead.
- Write the cursor encoding (opaque, so the sort key can change later) and the exact WHERE clause with the strict tuple comparison.
- Write the consumer's apply step and confirm it is a per-id upsert that absorbs the repeat from step two.
Expected result: A worked example naming the id dropped by offset paging and the id repeated by keyset paging; a strict tuple comparison with a matching (updated_at, id) index; and a stated at-least-once contract whose consumer upserts by id and advances the cursor only after applying.
Verify it yourself
- The predicate compares the pair strictly, not
updated_at >= :tswith a separate id filter — that variant either loops on a timestamp cluster or skips past it. - A withdrawal is observable as a row with status='withdrawn', never as a missing row.
- You can state which transaction-visibility case your watermark rule covers, and how far behind now() it holds.
Follow-up
- The mirror is down for six hours and resumes from an old cursor. Does it converge, and what will it never learn?
- An operator edits 40,000 windows in one bulk job. What does the feed look like to a consumer polling every minute, and what do you add to the contract?
One partition stops dead while the other sixty-three drain
Medium · poison message · dead letter queue · at-least-once
Consumer lag on one partition of the heartbeat topic climbs steadily while the other 63 partitions are healthy: throughput on it is zero, its committed offset has not moved in 40 minutes, and the consumer process restarts every few seconds. Events are (session_id, sequence, played_ms) delivered at-least-once and deduped on (session_id, sequence). Give the ordered checklist, what you do in the next ten minutes, and the permanent fix.
Approach
- Separate stalled from slow using three metrics before opening a log: committed offset (stationary versus advancing), per-partition throughput (zero versus reduced) and the consumer restart counter. Stationary offset plus zero throughput plus rising restarts is a poison record; a slow consumer advances its offset and shows raised per-record processing time.
- Confirm with the crash fingerprint. The same exception at the same offset on every restart is definitive. Note why lag is linear rather than bursty: at-least-once delivery plus unbounded in-process retry is an infinite loop by construction.
- Fetch the record at that offset with a separate consumer group so the live group is untouched, and classify it: a deserialisation failure, a value outside a column domain (played_ms beyond the content duration, a sequence beyond the column's integer width), or a code path only that key reaches.
- Mitigate within ten minutes: bounded retries, then route the record to a dead-letter topic carrying key, partition, offset and exception, and commit past it. The right to skip comes from the dedupe key: replaying the DLQ later is safe precisely because (session_id, sequence) makes reprocessing idempotent.
- Do not hand-commit a later offset to unstick it. That discards the whole batch, which contains hundreds of healthy sessions, and the loss is invisible afterwards.
- Permanent fix: validate at the consumer boundary with a schema plus range checks and treat an invalid record as an expected input class with a defined output, not as an exception. A poison record is data; if the handler has no branch for it, the queue has no way forward.
Follow-up
- What bounds the damage if the poison is a whole batch from a bad producer release rather than one record?
- What alert would have paged before lag was visible, and why is consumer lag alone a weak signal here?
- How does the diagnosis change if the record is valid but the downstream write is the thing failing?
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 bug you decided not to fix
Engineers over-index on what they repaired. A stronger answer covers something you knowingly left broken: the alert you tuned down, the data inconsistency you documented instead of chasing, the cleanup you deferred past two quarters. Give the reasoning and the condition that would have reopened it, so it reads as a decision and not as neglect.
What motivates you to perform your best work?
Medium · behavioural and engineering judgement
Approach
- Pick a story where you made the decision, not one where you watched it.
- Name the disagreement and how you resolved it with evidence.
- Give the blast radius: what could have broken, and what you measured.
Follow-up
- How did you know your change caused the improvement?
- What would you do differently if you ran that again?
Tell me about a time you faced a conflict in a team. How did you resolve it?
Medium · behavioural and engineering judgement
Approach
- Name the disagreement and how you resolved it with evidence.
- State the situation in two sentences and spend the rest on the reasoning.
- 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?
Resolve a review disagreement over watch-time accounting
Easy · code review · telemetry · idempotency
A colleague's pull request accumulates watch time by summing differences between reported playhead positions in the heartbeat stream. They point out that it matches the reference trace exactly and has run in staging for two weeks. The schema already has playback_session.played_ms, a monotonic counter reported by the client. Describe a code review disagreement where you were confident and the author was not convinced: how you made the failure concrete, how many rounds it took, when you would have approved anyway, and what you conceded.
Approach
- The probe is whether you turn an objection into something reproducible instead of a stalemate. Name the mechanism rather than the smell: a backward seek makes the position delta negative and a forward seek makes it as large as the jump, so a viewer scrubbing through a 45-minute title records either a negative watch or several hours of one.
- Address their evidence head-on. The reference trace is linear happy-path playback, where position deltas and elapsed play are the same number, so it cannot distinguish the two implementations at all. That sentence ends the argument faster than the theory does.
- Build a fixture rather than a paragraph: ten heartbeat rows for one session containing a backward seek, a forward seek, one duplicate and one out-of-order arrival, with the correct watch time written beside it. Ten rows settle in one round what four paragraphs will not settle in three.
- State the fix as a property rather than a patch. MAX(played_ms) per session_id is idempotent under duplicate delivery and independent of arrival order, because max is commutative, associative and idempotent - the same rows replayed in any order give the same answer. Sum-of-deltas has none of those properties.
- Name the precondition you are relying on, because a reviewer who hides one loses the next argument. MAX is correct only while the counter is monotonic within a session_id; a player that resumes after a crash and restarts the counter under the same id will undercount, which is why a resume mints a new client-generated session UUID.
- Say where your bar sits and what you gave up. Counting that money is paid against is a blocking comment and style is not; the author was probably right about something separable, and saying so is what makes the next blocking comment land.
Follow-up
- A firmware bug makes played_ms decrease once in a thousand sessions. What does your query do, and what should it do?
- How do you backfill the watch time already recorded under the old implementation?
- Which implementation survives a client delivering four days of buffered heartbeats after an offline trip?