Interview guides · Amagi Media Labs
Amagi Media Labs Software Engineer Interview Guide
Amagi Media Labs Software Engineer interview guide: four reported rounds, linked-list cycle questions, project discussion prep and a 7-day plan.
The 60-second brief
Amagi Media Labs is described by candidates as a cloud-broadcast company. The work covers infrastructure that lets television networks move from hardware-based workflows to cloud-native ones. The Software Engineer role is reported to involve distributed systems, streaming services that handle many concurrent viewers, and data-heavy platforms for live and linear television. Prepare to show clean fundamentals in code and to talk about services you have built and kept running.
This guide is for a Software Engineer candidate at Amagi Media Labs. It covers the four reported rounds (Technical Screen, Coding Challenges, Project Discussion, Final Offer Stage), the one reported question family on linked-list cycles, and how to talk about your own projects. The requirements candidates describe mention an object-oriented language (Java, C++ or Go), data structures, distributed systems and roughly 3 to 5 years of experience.
Candidates report four rounds over about 3 to 5 weeks: a Technical Screen, Coding Challenges, a Project Discussion and a Final Offer Stage. The reported questions all revolve around finding a cycle in a linked list.
Focus areas
- Data Structures (Linked Lists)
- Algorithmic Problem Solving
- Two-Pointer Technique
Overview
The reported question list for this loop is narrow. Three questions form one thread: detect whether a linked list has a cycle and return the node where the cycle begins, state the time and space complexity of your approach, then say how you would change it if memory were much tighter. Reported topics are linked lists, the two-pointer technique, cycle detection and locating the cycle entry. That narrowness is useful: you can rehearse this thread until you can code it, prove it and vary it without notes.
Candidates are told to expect follow-ups that change the constraints of the problem ("What if?" questions), so memorising Floyd's tortoise-and-hare code is not enough. Be able to derive why the pointers meet, why restarting one pointer from the head finds the entry, and what you give up or gain when you swap in a hash set, a different traversal or an in-place marking trick. Practise reasoning about null input, a one-node list, a self-loop, a cycle that includes the head and a list with no cycle, and write the code the way you would submit it for review: clear names, no surprise mutation, tests for those cases.
The Project Discussion is the stage where the conversation pivots from coding to past work, and the requirements candidates describe list distributed systems, cloud platforms (AWS or GCP), video streaming protocols, and Docker or Kubernetes as nice-to-haves. You do not need to claim experience you lack. You do need two projects you can explain at depth: what you owned, which decisions were yours, what broke or got slow, and how you scaled it.
Use the rest of your time on the language you will code in. The requirements name Java, C++ or Go as object-oriented languages, so practise writing a linked list node, a traversal loop and a small test harness in one of them without an IDE's help. Confirm the language and format with your recruiter, since candidates note the process varies slightly by team.
The Amagi Media Labs interview process
These are the four stages candidates describe, in the order they report them. The loop can differ slightly by team, so confirm the next step with your recruiter after each stage.
Technical Screen
The Technical Screen is the first stage candidates describe, a first look at how you solve technical problems before the later rounds. Data structures and algorithms are reported as the gateway to the role, and the reported question on finding a cycle in a linked list and returning its entry node is a natural thing to rehearse for a stage like this. Approach it by stating the plan before you type, writing code that handles empty and single-node input, and giving time and space complexity without being asked.
What to demonstrate
- Problem-solving on a data structures question under a limited time
- Correct, readable code that handles null, single-node and no-cycle inputs
- Ability to state time and space complexity and justify the approach
How to prepare
- Implement cycle detection with a hash set first, then with Floyd's slow and fast pointers, in your chosen language
- Write the entry-point step from memory and be able to explain why restarting one pointer at the head works
- Prepare a short list of test inputs: empty list, one node pointing to itself, two nodes, cycle at the head, no cycle
Coding Challenges
Candidates describe Coding Challenges as hands-on problems where directness and technical depth are emphasised. Linked lists, the two-pointer technique and cycle entry localization are among the reported topics. Candidates are also advised, for the interview in general, to expect "What if?" follow-ups that change the constraints, and the tighter-memory question has that shape: it asks you to change the solution, not just defend it. Approach each challenge by solving it plainly, naming the trade-off you chose, and then re-solving under a new constraint.
What to demonstrate
- Hands-on coding ability on data structure problems
- Optimising a working solution when a constraint, such as memory, tightens
- Edge case handling and clarity of explanation while coding
How to prepare
- Rehearse the three-step arc: working solution, complexity statement, tighter-memory variant
- Practise sibling two-pointer problems such as finding the middle node, the nth node from the end and the intersection of two lists
- Prepare answers for constraint changes such as a doubly linked list, a list too large for memory, or a list that other threads may be modifying
Project Discussion
Candidates describe the Project Discussion as the stage where the conversation pivots from coding questions to your past projects in detail. The requirements candidates describe mention distributed systems, with cloud platforms, streaming protocols and containers as nice-to-haves, and the tips advise explaining how you used your skills on hard problems and how you scaled your solutions. Approach it with two projects written out in advance, each with your role, the decision you made, the failure or bottleneck you hit, and the result.
What to demonstrate
- Depth of understanding of the projects you list
- Ability to explain technical decisions and the trade-offs behind them
- Experience with scaling and reliability problems in your previous roles
How to prepare
- Write a one-page summary for two projects: scope, your contribution, one hard technical problem and how you solved it
- Work out how each project would behave if it served many concurrent viewers of a live channel with low latency, and which component would fail first
- If you have cloud, containers or streaming-protocol experience, tie it to specific work, and be plain about what you have not done
Final Offer Stage
Candidates describe the Final Offer Stage as concluding discussions before an offer, with core topics still in scope. Little else is reported, so treat it as a stage where earlier topics can return: the cycle question family, your projects and your reasons for the trade-offs you chose. Candidates are also advised to stay responsive when scheduling and to ask the recruiter what comes next.
What to demonstrate
- Consistency between what you said in earlier rounds and what you say now
- Command of core data structure topics
- Clear communication about decisions and trade-offs
How to prepare
- Re-read your cycle detection code and your two project summaries the day before and fix anything you would now say differently
- Candidates note that salary discussions come in the final stages, so research the compensation range for a Software Engineer with 3 to 5 years of experience and have a number and its justification ready
- Prepare two or three questions about the team's services and how engineers work there
Where candidates lose points
Detecting the cycle but not returning its entry node
Read the prompt as two tasks: does a cycle exist, and which node starts it. If you finish phase one with Floyd's pointers, say out loud that the meeting point is generally not the entry, then reset one pointer to the head and advance both one step at a time until they meet.
Dereferencing fast.next.next on an empty or short list
Guard the loop with a check on both fast and fast.next, and test a null head, a single node without a cycle, a single node pointing to itself and a two-node list before you declare the code done.
Quoting O(n) time and O(1) space with no reasoning behind either number
Be able to say why the slow pointer cannot loop around the cycle many times before the fast pointer catches it, and why the hash set version costs O(n) extra space. Give both bounds for both approaches and say which one the memory constraint selects.
Answering the tighter-memory follow-up by mutating the list without saying so
If you mention marking visited nodes or reversing pointers, say that it changes the input, breaks other readers and is unsafe if threads share the list. Offer Floyd's O(1) space method first, and treat mutation as a last resort you can explain.
Listing technologies in the project discussion instead of describing decisions
For each project, prepare the problem, your own role, one trade-off you made, one thing that failed or slowed down, and what you changed. Skip any technology you cannot discuss in detail, since the Project Discussion is reported to go through past work in detail.
Questions & practice
Detect if a linked list has a cycle and return the entry point node.
Medium · Data Structures and Algorithms
Approach
- Clarify first: a singly linked list, nodes compared by identity rather than value (duplicate values are allowed), may the list be modified, and return null when there is no cycle.
- Baseline: walk the list and store each visited node in a hash set keyed on node identity; the first node already in the set is the entry. That is O(n) time and O(n) extra space.
- Floyd's two pointers: slow moves one step, fast moves two, looping while fast and fast.next are non-null. If fast hits null there is no cycle; if slow == fast, a cycle exists.
- Finding the entry: reset one pointer to head and advance both one step at a time; where they meet is the entry. Why: with a = head-to-entry distance, b = entry-to-meeting distance and c = cycle length, slow has walked a + b and fast 2(a + b), so a + b is a multiple of c and a steps from the meeting point land on the entry.
- Edge cases to test aloud: empty list, a single node with next = null, a single node pointing to itself, a cycle whose entry is the head, and a two-node cycle.
Follow-up
- How would you return the length of the cycle once you have found the meeting point?
- How would you remove the cycle so the list ends in null, without losing any node?
- Does your loop condition crash on an even-length list with no cycle? Walk through it.
Explain the time and space complexity of your chosen approach for cycle detection.
Medium · Data Structures and Algorithms
Approach
- Hash set version: O(n) expected time, since each node is inserted and looked up once at amortised O(1), and O(n) extra space for the set. In Java that means an IdentityHashMap or default equals on nodes, in Python a set of node objects or id() values.
- Floyd detection phase: with no cycle, fast reaches null after about n/2 iterations. With a cycle, slow enters it after a steps; from then on fast gains one node per step on a gap smaller than c, so they meet before slow completes one lap. Slow takes fewer than a + c = n steps.
- Floyd entry phase: a further a steps for each pointer, so the total is O(n) time with O(1) extra space: two pointer variables, no allocation.
- State the constant factor honestly: fast reads about twice as many nodes as slow, so Floyd touches more nodes than the single hash set pass but avoids any per-node memory and hashing cost.
- Contrast with marking visited nodes in place. A per-node flag is O(1) extra space only if the node already has a spare field; adding one costs O(n). Redirecting each visited next pointer to a sentinel is the truly O(1) extra-space variant, with O(n) time, but it mutates input the caller may still be using, so it needs restoring or explicit permission.
Follow-up
- Why is it guaranteed that fast cannot jump over slow inside the cycle?
- Would moving the fast pointer three steps at a time still detect the cycle, and would the entry-finding step still work?
- Which version would you pick for a list with a few hundred nodes, and why?
How would you optimize your solution if the memory constraints were significantly tighter?
Medium · Data Structures and Algorithms
Approach
- Name where the memory goes first: the hash set costs O(n) node references plus hashing overhead, so the main optimisation is moving to Floyd's two pointers, which keeps O(n) time with O(1) extra space.
- Keep the solution iterative: a recursive walk uses O(n) call stack, which is exactly the memory you were told to remove and can overflow on long lists.
- Mention Brent's algorithm as an O(1)-space alternative: one pointer stays put while the other advances, the stationary pointer jumps forward at each power of two, and it typically needs fewer next-pointer reads than Floyd while still giving cycle length and entry.
- Rule out tricks that look cheap: a Bloom filter uses less memory than a set but its false positives can report a cycle that is not there; marking or reversing nodes uses O(1) space but mutates the list, so it is wrong if the list is shared or read-only.
- Generalise: Floyd only needs a next() function, so the same O(1) approach works on any sequence x -> f(x), such as finding a duplicate in an array of n + 1 values in 1..n by treating values as indexes.
Follow-up
- If the list is shared with other threads and must stay read-only, which of these approaches are still allowed?
- How would you apply the same idea to find the duplicate number in an array without modifying it and with O(1) extra space?
- What changes if the nodes live on disk and each next-pointer read is a slow I/O operation?
Explain why the owner filter ignores the listing index
Medium · composite index · query plans · sort elimination
The only index on resource is (tenant_id, status, updated_at DESC, resource_id DESC). A new endpoint returns one user's resources across all statuses, newest created first: WHERE tenant_id = $1 AND owner_user_id = $2 ORDER BY created_at DESC LIMIT 20. On a tenant with 2M rows it takes 900 ms and EXPLAIN shows a sort above a large scan. Explain precisely why the existing index cannot serve it, give the index that can, and state which of these the new index still will not help: owner_user_id alone across tenants; the same query ordered by updated_at. PostgreSQL 16.
Approach
- Separate the two jobs an index does. For filtering, a composite btree is seekable only on a left prefix, so with no predicate on status the scan can at best range over tenant_id and test owner_user_id per row; PostgreSQL 16 has no btree skip scan to jump the unconstrained column.
- For ordering, the index is sorted by (status, updated_at) within a tenant and not by created_at, so the LIMIT cannot stop early: every matching row is read and then sorted. That is the 'Sort Method: top-N heapsort' line, and it is why the plan reads 2M rows to answer with 20.
- Derive the replacement from the access path — equality, equality, then the ordering column: CREATE INDEX CONCURRENTLY ON resource (tenant_id, owner_user_id, created_at DESC). The scan seeks to the (tenant, owner) range and walks 20 entries in order, so the Sort node disappears along with the row-read.
- Treat INCLUDE (title, status) as conditional, not free. An index-only scan still visits the heap for any row whose page is not marked all-visible, so on a table taking 1.2k writes/second the win depends on autovacuum keeping the visibility map current, and the wider index costs more on every insert.
Follow-up
- 90% of rows are status='active'. Would a partial index WHERE status = 'active' change your answer, and for which of the three queries?
- A dashboard runs this for 40 owners in one page load. What changes about the design?
Stop tag and share joins from fanning out a page
Medium · join fan-out · semi-joins · many-to-many
resource_tag is (resource_id, tag_id) with PK (resource_id, tag_id); resource_share is (resource_id, shared_with_user_id, permission). The tagged-and-shared listing inner-joins resource to both, filters tenant_id, tag_id = ANY($2) and shared_with_user_id = $3, orders by updated_at DESC and takes 50. Pages come back with fewer than 50 distinct resources and the total in the header is far too high. Explain the row multiplication, rewrite both the page query and the count query so each is correct, and name the index each one needs. PostgreSQL 16.
Approach
- Do the arithmetic against the predicates that are actually there. An inner join emits one row per matching child row, and both joins are filtered: tag_id = ANY($2) admits only the requested tags, shared_with_user_id = $3 admits one user's share rows. So a resource holding three of the requested tags and shared with $3 once yields three rows, not one — the multiplier is its count of matching tags times its share rows for that single user, and that second factor is 1 unless the table admits duplicate (resource_id, shared_with_user_id) pairs. LIMIT 50 then limits rows rather than resources, and COUNT(*) counts pairs — the header is the product, not the population.
- Reject DISTINCT as the fix. It deduplicates after the product has been built, so the planner must materialise and sort the fanned-out set before the LIMIT can apply, and it leaves any SUM or AVG in the same select list wrong.
- Rewrite both filters as semi-joins, keeping resource as the only row source: AND EXISTS (SELECT 1 FROM resource_tag rt WHERE rt.resource_id = r.resource_id AND rt.tag_id = ANY($2)) and the same shape against resource_share. A semi-join stops at the first match per resource and preserves the driving index order, so ORDER BY updated_at DESC, resource_id DESC LIMIT 50 still stops after 50 rows.
- Count with the same predicates and no join at all: SELECT count(*) FROM resource r WHERE r.tenant_id = $1 AND r.status = 'active' AND EXISTS (...) AND EXISTS (...). Nothing multiplies a resource, so the number is the population.
Follow-up
- The filter changes from 'any of these tags' to 'all of these tags'. Rewrite it and state what it costs relative to the ANY form.
- A resource can be shared with the same user twice under different permissions. Does your count change, and should it?
Choose what to break when replication lag reaches forty seconds
Hard · replication lag · read-after-write · consistency vs availability
Reads are served from two replicas: 14k requests/second, about 85% absorbed by cache, so roughly 2.1k reads/second reach the database. Writes go to the primary at 1.2k/second. A tenant's backfill drives replication lag from under 100 ms to 40 seconds and it is still climbing. Sessions that have just written are pinned to the primary. Decide, endpoint class by endpoint class, whether to serve stale, fail, or route to the primary, and justify each choice with the load it adds to the primary. Then state what you would have built beforehand.
Approach
- Establish blast radius before cause, because mitigation and diagnosis have different deadlines. The decisive arithmetic is what happens if the database reads move to the primary: 2.1k reads/second on top of 1.2k writes/second roughly triples its operation count, on the node already absorbing the backfill that caused this. Reads and writes are not equal in cost, so treat that as an argument against a blanket move rather than as a capacity model - but it is enough to rule out routing everything to the primary.
- Classify endpoints by what staleness costs, not by how important they feel. Reads whose staleness is invisible - listings, search, counters - stay on the replica and return the watermark so the client can tell. Reads that immediately follow that same session's write keep their primary pin, which is a small bounded slice of traffic rather than the whole 2.1k/second. Reads that feed a decision with a side effect - authorisation, quota, the read half of a read-modify-write - must not be stale at all, because a 40-second-old permission row is the stale-permission failure wearing a different costume; those go to the primary or fail.
- Shed instead of queueing. If the must-be-fresh class alone exceeds the primary's headroom, refuse its lowest-value slice with 503 and a retry-after. A request queued behind a saturated primary holds a connection for a client that has already given up, and the retry storm that follows is what turns degradation into an outage. Bound the connection pool per role so the read fallback cannot consume the write path's connections - that bulkhead is the single decision that determines whether writes survive the next ten minutes.
- Attack the cause in parallel, since it is the one thing that can be stopped. The backfill is the load generator. A backfill that reads replication lag as its throttle signal and pauses above a threshold would have made this a non-event, with batch sizes small enough that each batch's write volume is a fraction of what a replica can apply per second. That is most of the answer to what should have existed beforehand.
Follow-up
- Lag returns to normal in nine minutes. Which mitigation do you remove first, and which one stays permanently?
- A user reports their change did not save, and the write committed. Trace the path that produces that report and name the signal that would have shown it before the report arrived.
Keep one tenant's bulk export from starving projection updates
Medium · backpressure · fair scheduling · head-of-line blocking
The worker fleet runs about 600 jobs/second out of job_run, claimed under a lease and heartbeated while running. Most handlers finish under 200 ms; exports run for minutes. One tenant enqueues 50,000 exports. Design the scheduling so that tenant's exports slow down and nothing else does: the queue split, the concurrency caps and where they are enforced, the claim query, and the backpressure signal that stops accepting new work. State the in-flight worker count each class needs at its arrival rate, and what the cap costs the tenant that hits it.
Approach
- Do the concurrency arithmetic first, because it determines the pool shapes. Required in-flight work is arrival rate times service time: 600/second of 200 ms handlers needs about 120 workers, while one export per second at 180 seconds needs 180 on its own. The long tail dominates any pool it shares, so a single pool sized from the mean is consumed by exports while 50 ms projections queue behind them. That is head-of-line blocking, and adding capacity does not fix it because the ratio is what is wrong.
- Split by job class rather than by priority. Priority inside one pool still lets a running export hold its worker for the next four minutes - there is no preemption for a handler already executing. Separate queues and separate worker processes give each class a floor the other cannot take.
- Enforce the per-tenant cap inside the claim, not at enqueue. Claim with SELECT ... WHERE status='queued' AND run_after <= now() AND job_type=$1 ORDER BY run_after, job_run_id FOR UPDATE SKIP LOCKED LIMIT 1, restricted to tenants currently under their cap. Derive the running count from rows in status 'running' with a live lease rather than from a counter incremented at claim and decremented at completion: the derived count self-heals when a worker dies, and the counter leaks a slot every time one does.
- Make the ordering fair over tenants instead of over jobs. Strict FIFO with a cap still forces the claim to scan past 50,000 rows belonging to a capped tenant before it finds anyone else's work, so cost grows with backlog depth. Pick the tenant first from a small per-tenant queue-depth summary, then claim within that tenant, which makes the scan proportional to the number of active tenants instead.
Follow-up
- An export outruns its lease, so two copies run. What must the handler do to make the second copy harmless, and what does it write to guarantee that?
- With a cap of 4, the tenant's 50,000 exports now take hours. What do you tell them, and does the answer change if they are the largest tenant on the platform?
Listing latency scales with page size, not with filters
Easy · n+1 · latency · query batching
The tenant listing endpoint reads resource filtered by tenant_id and status, ordered by updated_at DESC, and returns each row plus the owner's display name from app_user and the actor of that resource's latest resource_revision. p99 is 55 ms at 10 rows per page and 1.4 s at 200. Database telemetry shows 401 statements per request, each under 1 ms, and nothing in the slow-query log. Diagnose the cause and give the fix, stating the statement count per request and the p99 you expect afterwards.
Approach
- Read the counters before forming a theory. 401 statements for 200 rows is one driver query plus two per row, and sub-millisecond execution with an empty slow-query log rules out a bad plan. The time is round trips, which is why it is invisible in every per-query metric and scales with rows returned rather than with filter selectivity.
- Name the two per-row statements from their normalised text: a single-row app_user lookup by user_id, and a resource_revision lookup by resource_id ordered by version DESC LIMIT 1. Confirm by dropping those two response fields and watching the statement count fall to one. That locates the calls in the serialisation layer, not the repository.
- Check that the arithmetic accounts for the whole gap. Measure one round trip to the replica in isolation; 400 trips at roughly 3 ms of network plus 0.2 ms of execution is about 1.3 s on top of a 55 ms baseline, which matches. If the multiplication had fallen short, the N+1 would only be part of the story and you would keep looking.
- Batch both lookups. Collect owner_user_ids and resource_ids from the driver query, then issue WHERE tenant_id = $1 AND user_id = ANY($2) for the users, and PostgreSQL's SELECT DISTINCT ON (resource_id) ... WHERE resource_id = ANY($2) ORDER BY resource_id, version DESC for the latest revision, which the UNIQUE (resource_id, version) index serves directly. On an engine without DISTINCT ON, use a lateral join or a row_number window. Three statements per request at any page size.
Follow-up
- The page size is capped at 200 today. What breaks first if it is raised to 2,000, and is it still this bug?
- How do you stop the next N+1 from reaching production, given that no individual query is slow and the endpoint's tests pass?
Suggested practice checklist
The plan front-loads the reported cycle detection question family, then widens to related two-pointer problems, production-quality code and the project discussion. Days 5 to 7 practise answering aloud, since the reported follow-ups change constraints.
Day 1 — Map the loop and choose your language
- Write the four reported rounds (Technical Screen, Coding Challenges, Project Discussion, Final Offer Stage) on one page and mark which one you feel least ready for.
- Pick the language you will code in from the object-oriented ones the requirements name (Java, C++ or Go) and write a linked list node class plus a traversal loop from memory.
- List the two projects you could discuss in depth and one hard problem from each.
Deliverable: A one-page round map with the weakest round marked, a working node and traversal in your chosen language, and a list of two candidate projects with one hard problem each.
Day 2 — Cycle detection and the entry node
- Write cycle detection with a hash set, then with slow and fast pointers, and run both on an empty list, a one-node self-loop and a list with no cycle.
- Add the entry-point step: reset one pointer to the head, move both one node at a time and return where they meet.
- Write the algebra for why it works: with a nodes before the cycle, b steps into it at the meeting point and cycle length c, then a plus b equals a multiple of c.
Deliverable: Two working implementations with a test list covering the edge cases, plus a handwritten derivation of the entry step.
Day 3 — Complexity and tighter memory
- Write the time and space complexity of the hash set and Floyd versions, with one sentence of justification for each bound.
- Write down every way to cut memory further, mark which ones change the input list, and decide when you would use each.
- Answer aloud: how would you optimise the solution if memory were significantly tighter, then record yourself and trim the answer to what matters.
Deliverable: A comparison table of both approaches with reasoned bounds, plus a recorded spoken answer to the tighter-memory follow-up.
Day 4 — Two-pointer problems around the same idea
- Solve find the middle node, the nth node from the end and the intersection of two linked lists with two pointers each.
- Solve one Floyd variant on an array, such as finding the duplicate number when values index into the array.
- For each problem, write the invariant that your two pointers maintain.
Deliverable: Four solved problems, each with a one-line pointer invariant and a list of edge cases.
Day 5 — Constraint changes and production-quality code
- Rewrite your cycle entry solution for review: clear names, a short comment on the invariant, no hidden mutation, and a few unit tests.
- Prepare an answer for each constraint change: a doubly linked list, a list too large for memory, a list another thread may modify, and a request to return the cycle length as well.
- Run the three reported cycle questions back to back aloud, without notes, and note where you hesitate.
Deliverable: A review-ready solution with tests, a one-line answer per constraint change, and a note listing where your spoken run hesitated.
Day 6 — Project discussion rehearsal
- Write each project summary as: context, your role, the hard technical problem, the trade-off you chose, what failed, what you would change.
- Add a scaling paragraph to each: what would break first at ten times the load and how you would find out.
Day 7 — Full mock and questions for the team
- Run a full mock: one cycle entry problem coded live with complexity and a tighter-memory follow-up, then a walk through one project.
- Fix the two weakest points from the mock and rerun that part.
- Write two or three questions to ask about the team's services and how engineers review code, and confirm scheduling details with your recruiter.
Deliverable: A completed mock with a list of fixes, plus two or three written questions for the team.
Project and decision stories
Candidates do not report a stand-alone behavioral round for this loop. They describe the Project Discussion as the stage where your past work is examined in detail, and the tips advise preparing the technical challenges you faced and how you scaled your solutions. So the prompts below are about projects, decisions and trade-offs. For each, prepare a specific story: what you owned, what you decided, what went wrong, and the result.
- Walk me through a project you owned end to end: the problem, your role, the design and what you would change now.
- Describe a technical problem you solved by changing the data structure or algorithm, and what the old approach cost.
- Tell me about a service of yours that had to handle much more load than it was first built for, and what you changed.
- Describe a time you disagreed with a colleague in code review and how the decision was reached.
- Describe a production incident you helped debug: what you saw first, how you narrowed it down and what you changed afterwards.
- Tell me about a time requirements changed late and you had to rework your solution.
Turn a code review disagreement into a decision
Easy · code review · optimistic concurrency · communication
A colleague's change updates a row with UPDATE resource SET version = version + 1 WHERE resource_id = $1 AND version = $2 and treats an affected-row count of zero as a successful no-op. You read that as a silently lost update; they think returning 200 is friendlier to clients than returning a conflict. Describe how you have handled a review disagreement of this shape: what goes in the comment, when you leave the thread, and who decides. Then write the comment you would leave here, in under 80 words.
Approach
- Sort the disagreement before writing anything. A silently discarded write is a correctness claim about data; the choice between 409 and 412 is taste. Only the first justifies blocking a merge, and saying which one you are doing is most of the value of the comment.
- Make the claim reproducible in the comment itself with an interleaving rather than a principle: A reads version 7, B reads version 7, B commits version 8, A's predicate matches zero rows, A is told it succeeded and A's edit is gone.
- Offer the alternative with its cost attached: return 409 carrying the current version and the revision that won, so the client can re-read and re-apply. Note that automatic retry is not the fix, because a retry re-reads the winner's state and reapplies an intent formed against data that no longer exists.
- Apply an escalation rule you can state: two round trips on the thread, then a call, and the service's owner decides rather than the reviewer. A reviewer who cannot be overruled is a bottleneck with extra steps.
Follow-up
- Where would you put the test that fails if someone reintroduces the swallowed zero rowcount?
- The author says clients cannot handle a 409. How do you check whether that is true?
Tell callers you do not own that their integration breaks
Medium · deprecation · compatibility · stakeholders
A field in a write endpoint's response must change shape. You own the endpoint; you do not own the four internal callers or the outbound webhook consumers who read it. Describe a deprecation you were responsible for: what you shipped first, how you established who was actually reading the field, the window you gave and what set its length, what you did about the consumer who never moved, and how you decided removal was safe. Name the signal you used, not the announcement you sent.
Approach
- Establish the reader set empirically rather than from a wiki of owners: per-field usage counters keyed by principal, or access logs attributed to a consumer. State the blind spot of whichever you pick, since a consumer that reads the field only on a monthly job will not appear in a week of logs.
- Ship additive first. Populate the new field alongside the old one so no reader is forced to move, which is also what keeps a rolling deploy safe, because old and new instances answer the same requests at the same time and a rollback must still find the old shape present.
- Set the window from the slowest legitimate consumer's release cadence, not from your calendar, and decide separately what to do for a consumer with no release process at all, such as an external webhook endpoint you can only email.
- Convert silence into evidence before you rely on it: a short, low-traffic removal window that makes a still-dependent consumer fail visibly and loudly while you are watching, rather than at three in the morning after you have moved on.
Follow-up
- How would you detect a consumer that reads the field only during a monthly export?
- One caller refuses to move and has a commercial relationship behind it. What changes in your plan and what does not?
Unblock an engineer without taking the keyboard
Easy · mentoring · leases · at-least-once
A teammate has spent two days on a job handler that occasionally writes duplicate rows. They are certain the queue is delivering twice by mistake. You suspect a lease expiring under a slow handler, so the job is running concurrently with itself. Describe how you have unblocked someone in this position: what you asked before offering a hypothesis, what you showed them rather than told them, and what you left them owning. Then say what you would do if their theory turned out to be the right one.
Approach
- Ask before diagnosing, and ask for things answerable from data they already have: the attempt count on the job rows that produced duplicates, the handler's observed duration against its lease expiry, and whether the duplicate rows share a natural key that a unique constraint could have caught.
- Teach the shape rather than the answer. A lease cannot distinguish a dead worker from a slow one, so a handler that outruns its lease is running twice by design, and deploys deliver the other half by killing handlers mid-run on every rollout. Both of their candidate theories produce identical duplicate rows, which is why the evidence has to come from timings rather than from argument.
- Hand over a checklist they execute: a natural key on every write the handler performs so the second copy collides rather than appends, the record of intent written before any external effect, a lease heartbeat while running, and the metric that shows it working.
- Keep ownership with them deliberately. Pair on the first write, then step back; if you finish it yourself you have closed one ticket and left the same person stuck on the next redelivery.
Follow-up
- How would you distinguish a genuine double-delivery from a lease expiry using only the data already stored?
- Their handler calls an external endpoint before recording that it did. What do you tell them to change first?