cswork / libraryOpen practice workspace

Interview guides · Indeed

Indeed Software Engineer Interview Guide

Software engineer prep for Indeed: design exercises weighted toward the constraints of B2B software and infrastructure work.

The 60-second brief

As a Software Engineer at Indeed, you are tasked with building the infrastructure and features that connect millions of job seekers with their next career opportunity. You will work at large scale, where your code influences the global labor market and affects individuals searching for work, as well as the employers who rely on Indeed's platform to find talent.

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

Focus areas

  • Build at-least-once pipelines with explicit deduplication horizons
  • Evolve APIs without breaking pinned SDK clients
  • Keep money in integer minor units

Overview

This role is critical to the Indeed mission of "We help people get jobs." You will contribute to complex, high-traffic systems that must be performant, reliable, and user-centric. Whether you are focusing on the Employer Jobs Platform, AI-driven matching, or core backend services, your work will involve solving engineering challenges, from optimizing search latency to designing scalable architectural solutions that support Indeed's global footprint.

Success in this role requires more than just technical proficiency; it demands a deep commitment to the end-user. You will collaborate across cross-functional teams, including product managers and data scientists, to drive initiatives that make the hiring process more efficient and equitable. This is an environment where precision, clarity in communication, and a proactive approach to problem-solving are valued as highly as your ability to write clean, maintainable code.

The Indeed interview process

Indeed candidates report 3 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.

Recruiter Screen

The title covers product work, platform work, infrastructure, mobile and frontend, and those are different jobs with different loops behind them. A screening call is the cheapest place to find out which one the seat is, and asking reads as experienced rather than fussy. The questions that separate them: what the team is on call for, what the last three projects were, and whether any round happens inside an existing repository instead of a blank file. Then say which of that you have done and which you have not. Claiming the whole posting is the fastest way to be found out one round later.

What to demonstrate

  • Whether you can locate your experience inside one flavour of the role honestly instead of claiming the entire requirements list
  • Whether you name what you have not done, which an experienced screener reads as a level signal and can plan the loop around
  • Whether what you want next matches what the seat is: someone who wants greenfield work landing on a team that mostly operates an existing system is a hire that leaves within the year

How to prepare

  • Mark every line of the posting as done, adjacent or new, and write one sentence for each adjacent line naming the closest thing you actually built
  • Split your last two years into rough percentages across feature work, operating and debugging live systems, and design or review, so a question about scope gets numbers rather than adjectives
  • Bring three questions that discriminate between seats: what the team is paged for, how much of the work is changing existing code versus standing up something new, and what shipped in the last quarter

Technical Screening

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

What to demonstrate

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

How to prepare

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

Virtual Onsite

Coding rounds mostly set a floor. They decide whether you clear the bar, not where you land on the ladder. Level tends to come out of the design discussion and the ownership stories, so the question worth auditing beforehand is whether the scope you describe matches the scope of the job. Work that stops at your own service, or a story whose hard part was writing the code rather than getting several people to agree on an interface, reads a level below where you think you are interviewing, and that gap is usually resolved downwards.

What to demonstrate

  • Whether the largest thing you describe owning ran end to end — the decision, the migration path, the rollout, and what you did when it went wrong — or stopped at the change you merged
  • Whether design answers include what you would not build, what you would defer, and what you would measure before committing, rather than only what the boxes are
  • Whether a disagreement in a story was settled with something checkable — a benchmark, a prototype, a written proposal — instead of by seniority or by waiting it out
  • Whether you can say which calls you made alone and which you escalated, and why the line sat where it did

How to prepare

  • Write your largest piece of owned work as a timeline of decisions — who decided what, when, and what you did when the plan broke — then delete every sentence whose subject is "we" and see how much survives
  • Take one system you know well and drill the migration answer: how old and new paths run side by side under live traffic, how you compare their outputs, what the rollback is once writes are going to both, and which step you would not automate
  • Map each line of the ladder in the job posting to a specific thing you have done, find the line you cannot support, and prepare the closest evidence you have plus an honest account of the gap

Where candidates lose points

One shared connection pool for every tenant and every query class

A single tenant with a large table and a missing index can occupy every connection with slow queries, and every other tenant then waits in connection acquisition -- a queue invisible in database metrics, because the database itself looks healthy while the application starves. Containment is bulkheads: separate pools or per-tenant concurrency caps for interactive requests, background jobs and exports, a statement timeout low enough that a pathological query dies before it accumulates, and an idle-in-transaction timeout so a stuck client cannot pin a connection and its locks indefinitely. One caveat worth knowing in advance: if a transaction-pooling proxy sits in front of the database, session-scoped behaviour changes, so session-level advisory locks and settings applied outside a transaction do not survive the way they do on a direct connection.

Serialising a tenant's writes through select ... for update on a single counter row

It is the first change that makes a counter correct, and it caps that tenant's write throughput at roughly one divided by the lock hold time. A transaction that takes the lock, makes a network call and then commits holds it for the entire round trip: at 2 ms that is about 500 writes per second for the whole tenant, and the largest tenants are exactly the ones that exceed it. The damage then spreads, because every waiter holds a database connection while it queues, so one hot tenant drains the shared pool and the symptom presents as a site-wide latency incident rather than as a lock problem. The repairs are to shrink the critical section to a single statement, to shard the counter into per-(tenant, hour) or per-(tenant, bucket) rows and sum on read, or to batch in memory and flush periodically while accepting the bounded loss that batching implies.

Optimising an axis nobody named

Ask which resource is actually scarce here: wall-clock latency, throughput, memory footprint, cost per request, or engineering time. Shaving a constant factor off an in-memory step is wasted effort when the same function makes a blocking remote call inside the loop.

Sorting when the problem never required a total order

Match the algorithm to the guarantee actually needed: the top k comes from a size-k heap in O(n log k) time and O(k) space, distinctness needs a set rather than an ordering, and a small bounded integer key range admits a linear counting pass. A full O(n log n) sort is the right default only when you genuinely need everything in order.

Questions & practice

How would you verify the syntax of an equation while ensuring full test coverage?

Medium · data structures and algorithms

Approach

  1. State the target complexity and say which constraint rules the naive version out.
  2. Name the brute-force solution and its complexity before improving on it.
  3. Choose the data structure from the access pattern, not from familiarity.

Follow-up

  • How does this change if the input no longer fits in memory?
  • Which test case would catch an off-by-one here?

Implement a solution for a graph-based problem using Depth-First Search (DFS).

Medium · data structures and algorithms

Approach

  1. Walk one small example through your approach before writing the whole thing.
  2. Restate the input: its shape, its size, and what is guaranteed about it.
  3. Choose the data structure from the access pattern, not from familiarity.

Follow-up

  • What is the worst case, and how likely is it on real data?
  • Which test case would catch an off-by-one here?

Write a function to construct a list of objects from a tree representation.

Medium · data structures and algorithms

Approach

  1. Walk one small example through your approach before writing the whole thing.
  2. State the target complexity and say which constraint rules the naive version out.
  3. Choose the data structure from the access pattern, not from familiarity.

Follow-up

  • How does this change if the input no longer fits in memory?
  • Which test case would catch an off-by-one here?

Find peak concurrent sandbox usage from run intervals

Medium · sweep-line · intervals · concurrency-caps · null-semantics

Given up to 5 million job_run rows for one tenant over one day, with run_id, started_at, finished_at, status and wall_clock_limit_seconds, report the maximum number of sandboxes running at once, the earliest instant that maximum is reached, and the first run_id that would breach a per-tenant cap of C. started_at is null while a run is queued; finished_at is null both for runs still executing and for runs in status lost. Treat a run as occupying [started_at, finished_at). Give the complexity and state how you handle each null.

Approach

  1. Turn each run into two sweep events, (started_at, +1) and (end, -1), then sort the 2n events by timestamp with -1 ordered before +1 at equal timestamps. That tie-break is what makes the interval half-open, so a run finishing at 10:00:00 and one starting at 10:00:00 never overlap.
  2. Decide each null out loud before sweeping, because each choice moves the answer. A null started_at means queued and contributes nothing. A null finished_at with status running or leased is clipped to the window end. Status lost has no observed end at all, so clip it at started_at + wall_clock_limit_seconds on the grounds that the supervisor owns the timeout, and record that you did. The table's check (finished_at is null or started_at is not null) guarantees you never see an end without a start.
  3. Sweep once, maintaining a running counter, the maximum, and the timestamp at which the maximum was first attained (update peak_at only on a strict increase, or you will report the last such instant instead of the earliest). Capture the first run_id whose +1 takes the counter to C+1 during the same sweep rather than in a second pass.
  4. Complexity: O(n log n) dominated by the sort, O(n) space. If rows already arrive ordered by started_at, a min-heap of end times gives O(n log k) time and O(k) space with k the peak concurrency, which is the better shape when the rows come from an index scan on (tenant_id, started_at).
  5. If second resolution is acceptable, counting-sort the endpoints into an 86,400-slot delta array and prefix-sum it: O(n + T) time and O(T) space, which beats the comparison sort at 5 million rows. It answers only at second granularity, so state which resolution the cap is defined in.

Worked solution (20 min)

  1. Write the null policy as three lines of prose first, one per case, and keep them beside the output.
  2. Emit 2n endpoint tuples (timestamp, delta, run_id) and sort on the key (timestamp, delta) so -1 precedes +1.
  3. Sweep, tracking cur, peak, peak_at updated only on a strict increase, and the first run_id whose +1 takes cur to C+1.
  4. Build a fixture with two runs where one ends exactly when the next starts, three genuinely overlapping runs, one run with a null finished_at and status running, and one with status lost and a 300-second wall_clock_limit_seconds.
  5. Re-run with every timestamp shifted by a constant and confirm the peak is unchanged while peak_at shifts by the same constant.

Expected result: Peak is 3, from the overlapping trio. The exact-handoff pair yields a peak of 1, not 2. peak_at is the start instant of the third overlapping run. The lost run occupies exactly [started_at, started_at + 300s) under the stated policy.

Verify it yourself

  • Setting peak_at on >= rather than > moves the reported instant to the last time the peak was attained. Flip it and watch the fixture change.
  • Sorting by timestamp alone, without the delta tie-break, raises the reported peak on the handoff fixture.
  • The deltas over the whole sweep sum to zero once every open-ended run has been clipped.

Follow-up

  • Now report peak concurrency per tenant for 10,000 tenants from one globally sorted stream. What changes about memory and about the sort?
  • The cap has to be enforced at dispatch rather than reported afterwards. What does the admission check look like, and where does it race?
  • How would you answer 'peak concurrency within any 5-minute window' without re-sorting?

Enforce a concurrent-run quota that survives simultaneous requests

Medium · isolation levels · write skew · quotas · lock contention

A plan allows at most 20 concurrently running rows in job_run per tenant. The table holds run_id, tenant_id, workspace_id, status (queued, leased, running, succeeded, failed, timed_out, cancelled, lost), lease_token, leased_until, started_at and finished_at. Today the service runs select count(*) from job_run where tenant_id = $1 and status = 'running', compares the result to 20, then inserts. Under load a tenant exceeds the cap by exactly the number of concurrent requests. Name the anomaly, say which isolation levels do and do not prevent it, and give a version that holds, as SQL.

Approach

  1. Name it: write skew. Each transaction reads a predicate (the count of running rows), neither modifies what the other read, and both then insert rows that jointly violate an invariant no single row expresses. Read committed permits it. So does repeatable read, because snapshot isolation's first-updater-wins check fires only on conflicting row updates, and these are inserts touching disjoint rows.
  2. Enumerate the fixes with their real costs. SERIALIZABLE works: PostgreSQL's SSI tracks the predicate read and aborts one transaction with SQLSTATE 40001, which obliges the caller to retry and makes the abort rate rise with contention on a hot tenant. Folding the predicate into the write as insert ... select ... where (select count(*) ...) < 20 narrows the race to the statement's snapshot but does not close it under read committed.
  3. Give the version that holds at read committed: serialise on a row both transactions must touch. update tenant_concurrency set running = running + 1 where tenant_id = $1 and running < 20 returning running updates zero rows when the cap is reached, and zero rows is the rejection. This works because at read committed a blocked UPDATE re-evaluates its WHERE clause against the newly committed row; at repeatable read the same statement raises a serialisation error instead, so the isolation level changes the calling contract.
  4. State the cost you just bought. That row is now a per-tenant serialisation point, so admission throughput for the tenant is bounded by one divided by the lock hold time; at a 2 ms hold that is roughly 500 admissions/second. Keep the critical section to the single UPDATE, with no network call or scheduling decision inside the transaction, and decrement in the same transaction that writes the terminal status.
  5. Close the leak the status enum implies: a run can end as lost, so a crashed worker otherwise consumes a slot forever. Reconcile on a schedule against status = 'running' and leased_until < now(), and treat the counter as a fast path over job_run, which stays the system of record.

Follow-up

  • Write the retry loop for the SERIALIZABLE version. What does the caller see when it keeps aborting, and what bounds the retries?
  • Two regions each keep a counter. What is the effective cap, and what does admission do when the counter store is unreachable?
  • The cap changes mid-flight on a plan upgrade. Do running jobs get killed, and what does the counter row look like during the change?

Explain why the metering dashboard scans every daily partition

Medium · indexing · partition pruning · sargability · tenant skew

usage_event is range-partitioned daily on ingested_at and holds tenant_id, workspace_id, environment, sku, quantity numeric(20,6), occurred_at and ingested_at. The only relevant index is on (occurred_at). A dashboard runs select sku, sum(quantity) from usage_event where tenant_id = $1 and date_trunc('hour', occurred_at) >= $2 and environment = 'production' group by sku, and EXPLAIN shows a sequential scan of every partition. Give each distinct reason, rewrite the predicate so an index can serve it, propose the index, and state the write cost its column order adds.

Approach

  1. Separate the three causes rather than blaming one. First, date_trunc('hour', occurred_at) wraps the column, so the predicate is not sargable against a btree on the bare column. Second, pruning keys off ingested_at while the query constrains occurred_at, so no partition can be excluded. Third, even made sargable, (occurred_at) is not tenant-leading, so for one tenant among thousands the scan reads the whole time range and discards nearly all of it.
  2. Rewrite the bound carefully, because the obvious rewrite is only conditionally equivalent. date_trunc('hour', x) >= $2 equals x >= $2 only when $2 is already hour-aligned; for an arbitrary $2 it means x >= date_trunc('hour', $2) + interval '1 hour'. Normalise the parameter in the caller and leave the column bare.
  3. Restore pruning with a second, redundant predicate on the partition key: ingested_at >= $2 - interval '<late-data horizon>'. State both sides of it. It prunes to a handful of partitions, and it silently omits any event whose ingest lagged past that horizon, which is precisely what a producer replay produces. Either document the horizon as a stated bound, or partition on occurred_at and move the problem into the dedup window instead.
  4. Propose (tenant_id, occurred_at) include (sku, quantity) per partition. A partial index where environment = 'production' mostly saves size rather than selectivity, since production dominates the three environments; take it if non-production is a meaningful share and skip it otherwise.
  5. Price the write path honestly. At roughly 250M rows/day each extra index is another insert plus WAL per row, and a tenant-leading key scatters inserts across one hot leaf per active tenant instead of appending to a single rightmost leaf, so page dirtying and random I/O both rise. An INCLUDE payload widens every leaf entry and enlarges the index accordingly.
  6. Add the index-only-scan caveat before someone reports it as a regression: on a freshly appended table the visibility map is not yet set for recent pages, so the INCLUDE columns still cost heap fetches until autovacuum has been through, and the newest hour is exactly the data the dashboard reads.

Worked solution (30 min)

  1. Build 30 daily partitions with skewed tenants, one holding about 40% of the rows, then ANALYZE.
  2. Run explain (analyze, buffers) on the original query and record how many partitions were scanned and the rows removed by filter.
  3. Apply the rewritten predicate and the index, re-run, and confirm the plan lists only the partitions inside the ingested_at bound.
  4. Re-run with $2 set to a non-hour-aligned timestamp and confirm the rewritten and original predicates return identical rows.
  5. Insert an event with ingested_at six hours past occurred_at and check whether the pruning predicate excludes it.

Expected result: Three causes named separately: a non-sargable date_trunc, pruning on ingested_at against a predicate on occurred_at, and a non-tenant-leading index. A rewrite to occurred_at >= <hour-normalised $2> plus a redundant ingested_at >= $2 - horizon, an index on (tenant_id, occurred_at) including sku and quantity, a per-row write cost stated, and the late-data omission the pruning predicate introduces stated as a known bound.

Verify it yourself

  • The plan lists only partitions inside the ingested_at bound.
  • Rows removed by filter on the index scan is small relative to rows returned.
  • The rewritten and original predicates agree for both hour-aligned and non-aligned bounds.
  • A deliberately late-ingested event is visible or omitted exactly as the stated horizon predicts.

Follow-up

  • CREATE INDEX CONCURRENTLY is not supported on a partitioned parent. Give the sequence that gets this index onto 400 existing partitions without blocking ingest.
  • One tenant holds 200 times the median row count and the dashboard still times out for them with the index in place. What changes?
  • Should this read hit usage_rollup_hourly instead? State what that costs in freshness and what the watermark lets you promise.

How would you architect an API for a CRUD-heavy inventory management system?

Medium · system design and architecture

Approach

  1. Choose a partition key and say what query it makes expensive.
  2. State the consistency you need, and where you are willing to be stale.
  3. Fix the scope first: who calls this, how often, and what they do when it fails.

Follow-up

  • What breaks first when traffic grows ten times?
  • What would you drop to keep the system up under load?

How do you approach scaling a service to handle sudden spikes in traffic?

Medium · system design and architecture

Approach

  1. State the consistency you need, and where you are willing to be stale.
  2. Choose a partition key and say what query it makes expensive.
  3. 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?
  • What breaks first when traffic grows ten times?

Design a system for a company review platform where users can post and view feedback.

Medium · system design and architecture

Approach

  1. Name the read and write paths separately; they rarely have the same bottleneck.
  2. Name the failure you are designing for, then the recovery path.
  3. Choose a partition key and say what query it makes expensive.

Follow-up

  • What would you drop to keep the system up under load?
  • What breaks first when traffic grows ten times?

Review this component: what issues do you see regarding sorting order or data fetching?

Medium

Approach

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

Follow-up

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

Build a resumable usage export the customer can reconcile against

Medium · pagination · cursors · late data

Customers reconcile invoices against usage_event (event_id uuid, tenant_id, workspace_id, environment, sku, quantity numeric(20,6), idempotency_key, occurred_at, ingested_at, source_service, request_id), partitioned daily on ingested_at. The current export is ?page=N&per_page=1000 ordered by occurred_at, and a customer syncing hourly reports rows that never appear in their export but do appear on their invoice. Design the replacement: the ordering, the cursor's contents, the index it requires, and the rule deciding where a page stops. State what the client does on a timeout and on a cursor older than retention.

Approach

  1. Separate the two defects in the current shape. Offset makes the database produce and discard N * per_page rows, so page cost grows linearly and a deep page degrades from milliseconds to seconds. Concurrent inserts also shift the window between requests, so a walker skips rows with no error raised anywhere, which for a customer sync is silent data loss.
  2. Order by ingestion, not by occurrence. During a replay events arrive hours out of occurred_at order, so a consumer holding an occurred_at high-water mark can never see a late event that falls below it; (ingested_at, event_id) is the only ordering under which 'everything after my cursor' is a complete statement.
  3. Page with a row comparison: where tenant_id = $1 and (ingested_at, event_id) > ($2, $3) order by ingested_at, event_id limit $4, backed by an index on (tenant_id, ingested_at, event_id). That seeks directly to the resume point, so every page costs the same regardless of depth.
  4. Trail the head of the table. With ingested_at defaulting to now(), which is transaction start time, a long insert transaction receives an earlier timestamp and becomes visible after a reader has already passed it. Cap each page at ingested_at <= now() - delta, with delta larger than the longest write transaction as bounded by statement_timeout and idle_in_transaction_session_timeout, or the export skips exactly the rows written under load.
  5. Make the cursor opaque and self-describing: base64 of the timestamp, the event id and a fingerprint of the filters, rejected when the filters differ from the current request. Return 410 with a cursor_expired code once the cursor's partition has been dropped, so the client restarts from a known time instead of resuming into a hole.
  6. Keep a page a pure GET with no server-side consumption, so a timeout is resolved by retrying the identical cursor.

Worked solution (30 min)

  1. Construct the failing case on paper: an event with occurred_at at 09:00 ingested at 14:00, and a consumer that read up to 10:00 at 11:00.
  2. Write the keyset query with the row comparison and the exact index it needs, then say which column of the index each predicate uses.
  3. Add the trailing-head predicate and pick delta from a named timeout setting rather than a round number.
  4. Define the cursor's encoded contents and the two error cases: filter mismatch and expired partition, with their status codes.
  5. Write the client's algorithm in four lines: request, persist cursor after processing the page, retry the same cursor on timeout, restart from a time on 410.

Expected result: Keyset pagination over (ingested_at, event_id) with an index on (tenant_id, ingested_at, event_id), a trailing head predicate sized above the longest write transaction, an opaque filter-bound cursor, and 410 for a cursor past retention.

Verify it yourself

  • A late event with an old occurred_at is still delivered by the export.
  • Page cost is independent of how many pages have already been read.
  • The cursor persists across a client restart and survives retrying the same page.
  • The tie-break column is unique, so no row can be skipped or repeated at a page boundary.

Follow-up

  • The customer asks for a total count alongside the first page. What do you offer instead, and why is an exact count both expensive here and wrong by the time it is read?
  • How would you let a customer re-read a window they have already consumed without giving up the forward-only cursor?

A rare job-run overwrite that logging makes disappear

Hard · fencing tokens · leases · concurrency

About one job run in fifty thousand records billable_seconds matching no observed sandbox lifetime, and a few rows show worker_id changing after finished_at was already set. It does not reproduce: debug logging around the terminal write made it vanish for two weeks before it returned. Runs last from 200 ms to 30 minutes, the lease is 60 seconds and is renewed while a run executes. Give an ordered checklist, the mechanism, and a fix that makes the illegal write impossible rather than merely rarer.

Approach

  1. Mine the evidence instead of chasing a repro: select rows where updated_at is later than finished_at, or where a terminal status was written twice, and join them to attempt history to recover both writer identities. The defect has already happened tens of times and the rows are the recording.
  2. State the signature before measuring it. If the cause is a lease that expired while the original worker was stalled, affected runs should cluster where the gap between the last renewal and the terminal write exceeds the lease, and should correlate with worker pause metrics rather than with workload shape.
  3. Read the disappearance honestly. Logging inside the window changed the timing and lowered the probability; it is evidence about how narrow the window is, not a fix. Reproduce by widening the window on purpose, shortening the lease and injecting a pause between sandbox exit and the terminal write, rather than by adding more instrumentation.
  4. Name the mechanism precisely: the lease expires during a stall such as a long garbage-collection pause or a brief partition, the run is re-dispatched, and the original worker then wakes and writes its terminal state over the new attempt's row. A lease alone cannot stop this, because the check and the write are separated by the stall.
  5. Fix by fencing the write itself: UPDATE job_run SET status = $2, finished_at = $3, billable_seconds = $4 WHERE run_id = $1 AND status = 'running' AND lease_token = $5, with zero rows affected interpreted as having been fenced rather than as success. The token lives on the row so the store arbitrates, not the worker's memory.
  6. Keep the state machine honest: a retry inserts a new row pointing at parent_run_id rather than resetting the old one, and a run whose worker vanished terminates as lost with billable_seconds null, because recording failure asserts an outcome nobody observed and then bills and retries on that assertion.

Follow-up

  • The supervisor also emits a usage event on completion. What does the fenced worker do about the event it already emitted, and how does metering absorb it?
  • Lease renewal is itself a network call. What happens when a renewal times out, and how does the worker decide whether it still holds the lease?
  • Why is lengthening the lease past the longest legitimate run the wrong lever, and what breaks if you do it anyway?

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.

Describe a project where you had to collaborate with stakeholders to define requirements.

Medium · behavioural and engineering judgement

Approach

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

Follow-up

  • What would you do differently if you ran that again?
  • How did you know your change caused the improvement?

How do you handle situations where you disagree with a teammate’s technical direction?

Medium · behavioural and engineering judgement

Approach

  1. Give the blast radius: what could have broken, and what you measured.
  2. Name the disagreement and how you resolved it with evidence.
  3. 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?

Argue against failing open when the control plane is unreachable

Hard · revocation · cache staleness · fail-open vs fail-closed · influence

The gateway caches credential-to-context decisions with a sixty-second TTL. A design proposal says that when control-plane reads fail, pods should keep serving from expired entries indefinitely so a control-plane outage never becomes a product outage. You believe that converts every revocation into an unbounded one. Describe a design you argued against while it was still a live proposal: what you measured or modelled to make the case, what you conceded, who decided, and what happened afterwards. Say what would have changed your mind before the decision, not after it.

Approach

  1. Reframe it from a values argument into a bounded-staleness argument. Both sides already accept the cache; the disagreement is only about the ceiling on how long a revoked credential keeps authorising. Put a number on the table — serve stale for up to fifteen minutes, then fail closed — and make the other side argue against a number rather than against a principle.
  2. Bring arithmetic rather than adjectives: the rate of revocations with revoked_reason in ('suspected_leak','auth_version_bump'), the observed distribution of control-plane unavailability, and the product of the two, which is expected requests served by revoked credentials per outage-hour. At 30k requests/second the unbounded version is not a subtle exposure and the number says so.
  3. Concede the strong half of the opposing case first, because that is what buys you the room: failing closed turns one service's outage into a total outage across three regions, and a control plane doing tens of writes per second is not engineered to the gateway's availability target. A proposal you have not steelmanned reads as reflex.
  4. Propose the asymmetry that usually resolves this: stale entitlements cost bounded money (a quota fifteen minutes out of date over-serves by a computable amount), while a stale revocation costs unbounded access. Split the cached decision by what it authorises, give the two halves different staleness ceilings, and let the entitlement half fail open while the revocation half fails closed.
  5. State the propagation dependency plainly, since it is the part that is missed: validity is also derived from the principal's auth_version, so password reset and sign-out-everywhere flow through this same cache. A design that bounds staleness for explicit revocation and not for auth_version bumps has only fixed half of it.
  6. Say who decided, and what you did afterwards in either outcome: write the decision down with its number and a review date, and instrument the exposure you were worried about so the next round of the argument is settled by data instead of by seniority.

Follow-up

  • Publish-subscribe invalidation is lossy under a partition, and a TTL is the only hard bound. What TTL do you pick, and what does it cost you at 30k requests/second?
  • The key was revoked because it was found in a public repository. Does your answer change, and where does that urgency live in the design?
  • You lost the argument and six weeks later the failure you predicted happens. What do you say in the review, and what do you not say?