Interview guides · Nuro
Nuro Software Engineer Interview Guide
Nuro Software Engineer interview guide: reported coding, C++ concurrency and AV system design questions, the three stages, and a 7-day plan.
The 60-second brief
Nuro builds the software behind Level 4 autonomous electric delivery vehicles and personal mobility platforms. Its autonomy stack is called the Nuro Driver. Software Engineers work across three areas: real-time C++ that runs on the vehicle, cloud platforms for large-scale simulation and safety validation, and sensor-processing pipelines. The reported interview questions cover the same range, from thread-safe C++ and custom data structures to designing log pipelines and over-the-air update delivery for a fleet.
This guide covers the general Software Engineer loop at Nuro as candidates report it: an initial screening, one or two technical screens, and an onsite or virtual onsite. It groups the reported questions into algorithmic coding, low-level C++ and concurrency, system design for vehicle and cloud infrastructure, domain knowledge, and behavioural. Its preparation advice assumes you will write complete programs from a blank file in C++ or Python and defend your design choices against real hardware and network constraints.
Focus areas
- Evolve APIs without breaking pinned SDK clients
- Make every write idempotent under client retries
- Bound blast radius with per-tenant concurrency limits
Overview
Nuro builds software for Level 4 autonomous electric delivery vehicles and personal mobility platforms. Its autonomy stack is called the Nuro Driver. A Software Engineer there might write onboard C++ that runs in real time on the vehicle, build cloud platforms for simulation and safety validation, or develop sensor-processing pipelines. Teams mentioned for the role include Infrastructure, Autonomy, Behavior Verification and Embedded Systems, and the focus of your loop depends on which team you interview for.
The reported work covers the whole path from vehicle to cloud: low-level vehicle controls and CAN or Ethernet communication modules on board, distributed systems for over-the-air update deployment, and ML-based evaluation infrastructure that processes drive logs. That range explains why the question set mixes pointer ownership and cache locality with log pipelines and fleet-wide updates.
For preparation, expect less emphasis on template algorithm problems and more on runnable, production-shaped code. Candidates report writing full programs from an empty file, answering C++ memory and threading questions, and designing systems that must keep working with intermittent connectivity or a fixed onboard memory budget. The plan below is organised around those categories.
The Nuro interview process
Nuro candidates report 3 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.
Initial Screening
Candidates describe the first stage as a call with a recruiter or an engineering manager. It covers your engineering background, the projects you have delivered, and why Nuro's work interests you. Treat it as the first pass of a resume deep dive, not small talk: the technical screens reportedly open with a short resume deep dive too, so the projects you name here are the ones you will be asked to defend. Use the call to find out which team the loop is for, which language the coding sessions expect, and whether a C++ internals or domain session is part of the process. The reported questions range from cloud pipelines to onboard C++, and your preparation should follow the team.
What to demonstrate
- How clearly you summarise your background and point to project work you can defend in technical depth
- Whether your interest is tied to a specific part of the stack, such as onboard software, cloud simulation, sensor processing or verification, and whether you can say why
- Whether the projects you describe line up with the team and language the loop is for
How to prepare
- Pick two projects and write one line each for scope, your personal contribution, the hardest technical problem and the result, so the same facts come out in every later round
- Ask which team the role sits on and whether coding is expected in C++, Python or either
- Ask whether later sessions include C++ internals, a system design round or a robotics/perception deep dive, and adjust the seven-day plan to match
Technical Interviews
Candidates report one or two technical screens. Each typically opens with a short resume deep dive and then moves to live coding or low-level systems questions in a collaborative editor. One screen can cover several different areas, such as algorithmic coding, a deep dive into a system you built, and C++ internals, so pace yourself per topic rather than spending everything on the first problem. The environment is reportedly minimal: you may be asked to write a complete, executable program from a blank file in C++ or Python instead of filling in a stub. Practise the reported coding and low-level questions in this guide under those conditions: a trie that supports mkdir and ls, a multi-threaded job scheduler with cancellation, and a query for a vehicle's position at any timestamp in the last 10 seconds.
What to demonstrate
- Whether you can go from an empty file to a program that compiles and runs, including includes, class structure and a small test entry point
- Whether you pick data structures deliberately, state time and space complexity, and handle boundary cases without being prompted
- Whether you can answer C++ follow-ups such as ownership, move semantics or thread safety in the middle of your own code
- Whether you keep each topic on schedule when one screen covers several areas
How to prepare
- Write three reported-style problems from a blank file in your interview language with no autocomplete, and include a main that runs your own edge cases
- Practise stating complexity and the boundary cases you are handling out loud before you start typing
- Prepare a two-minute walkthrough of one project that covers an architectural choice, a performance bottleneck you resolved and a bug you fixed
- Rehearse moving on from a stuck subproblem by saying what you would do next and switching to the next topic
Onsite or Virtual Onsite
Candidates describe the final stage as a full onsite or virtual onsite of back-to-back sessions. These cover algorithmic coding, low-level system design or architecture, a domain deep dive (such as robotics, perception or C++ internals, depending on team), and behavioural conversations with hiring managers or team leads. Because the sessions run back to back, rehearse one session straight after another. Ask your recruiter ahead of time what each session focuses on. Reported design questions deal with vehicle and fleet constraints: streaming sensor logs to cloud storage, an evaluation pipeline over large volumes of drive logs, over-the-air updates with intermittent connectivity, and trajectory logging within onboard memory limits.
What to demonstrate
- Whether your designs name the physical constraints (bandwidth, intermittent links, fixed onboard memory) and give a fallback for each failure
- Whether low-level design answers explain memory, threading and determinism rather than only listing components
- Whether you can reason through a domain question you have not seen, such as sensor fusion trade-offs or actuator fault detection, from first principles
- Whether your behavioural stories show ownership, disagreement resolved with evidence, and a clear stance on speed versus safety
How to prepare
- Run a mock of two sessions back to back, one coding and one design, so you practise staying sharp through the second
- For each reported design question, write the failure modes first (lost connectivity, full disk, partial update) and the fallback state for each
- Review sensor fusion trade-offs between LiDAR, radar and camera, and how you would test an actuator and detect its faults, so you have a starting point if a domain question comes up
- Prepare one story each for disagreement, a production bug, speed versus safety, and cross-disciplinary delivery, using the same project facts you gave earlier in the loop
Where candidates lose points
Freezing when there is no function stub, harness or starter code
Candidates report being asked to write complete, executable programs from an empty file. Practise that exact setup in C++ or Python: includes, a class or struct, the function, and a main that runs a few inputs, including empty and single-element cases. Build the skeleton quickly so the problem gets most of your attention. If the prompt gives an interface, such as one that returns real-time vehicle position updates, write a minimal version of it yourself so the program actually runs.
A job scheduler or thread-safe structure that works on the happy path but has no answer for cancellation, shutdown or contention
The reported low-level questions include a multi-threaded job scheduler with queueing, execution and cancellation, plus thread-safe allocators, hash maps and ring buffers. Before coding, say which state is shared, which lock or atomic protects it, and what the condition variable waits on. Then handle the hard cases explicitly: cancelling a task that is already running, joining workers on shutdown, and spurious wakeups. When given a buggy snippet, name the specific race, deadlock ordering or uninitialised pointer, and say how you would reproduce it.
Answering C++ internals as memorised definitions with no performance consequence attached
Reported questions ask about vtables, smart pointers, move semantics and cache locality, and one asks how virtual calls affect a low-latency loop. For each topic, prepare the mechanism, the cost and an alternative: dynamic dispatch versus templates or a variant, a unique_ptr versus shared_ptr ownership decision, allocation inside a real-time loop versus pre-allocated buffers, contiguous layout versus pointer chasing. Link each one to code you actually wrote in the interview.
Designing vehicle-to-cloud systems as if the network were always up and memory unlimited
The reported design prompts state their constraints outright: intermittent connectivity for OTA updates, strict onboard memory limits for trajectory logging, and very large volumes of drive logs for evaluation. Start from those limits. Cover resumable, verified downloads with a rollback slot for updates; a bounded ring buffer with a clear policy for what gets dropped or kept when the logger is full; and backpressure plus replay for a sensor-log pub/sub. Name the fallback state for each failure instead of assuming retries will cover it.
Being vague on your own projects in the resume deep dive
Candidates report that the technical screens open with a resume deep dive and that interviewers press on architectural choices, performance bottlenecks and bugs you fixed. Pick two projects and fix the numbers, your exact contribution, the design you rejected and why, and one bug you found and prevented from coming back. Say it the same way every time, because the same project can come up again in a behavioural session later in the loop.
Questions & practice
Implement an efficient job scheduler in C++ or Python that uses multi-threading to handle task queueing, execution, and cancellation.
Medium · data structures and algorithms
Approach
- Name the brute-force solution and its complexity before improving on it.
- Walk one small example through your approach before writing the whole thing.
- Restate the input: its shape, its size, and what is guaranteed about it.
Follow-up
- What is the worst case, and how likely is it on real data?
- How does this change if the input no longer fits in memory?
Implement a custom trie data structure that supports basic file system commands such as mkdir and ls.
Medium · data structures and algorithms
Approach
- Name the brute-force solution and its complexity before improving on it.
- State the target complexity and say which constraint rules the naive version out.
- 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?
Solve array and string manipulation challenges, such as sorting characters by frequency or traversing multi-dimensional grid matrices under constrained time complexity.
Medium · data structures and algorithms
Approach
- State the target complexity and say which constraint rules the naive version out.
- Name the brute-force solution and its complexity before improving on it.
- 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?
Debug a live multi-threaded C++ snippet suffering from race conditions, deadlocks, or uninitialized pointer access.
Medium · languages, concurrency and fundamentals
Approach
- Identify the window where an invariant is briefly untrue.
- Distinguish a value from a reference to it, and say which one you handed out.
- Name what is shared across threads and what owns each piece of state.
Follow-up
- How would you prove the race exists rather than suspect it?
- Where could this allocate more than you expect?
Implement a thread-safe, custom vector memory allocator or hash map from scratch without relying on standard library containers.
Medium · languages, concurrency and fundamentals
Approach
- Distinguish a value from a reference to it, and say which one you handed out.
- Name what is shared across threads and what owns each piece of state.
- Say what the runtime actually does before reasoning about the code.
Follow-up
- What happens if two callers reach this at the same time?
- Where could this allocate more than you expect?
Hold a tenant to a trailing sixty-second request limit
Medium · sliding-window · two-pointer · rate-limiting · tenant-skew
The gateway must hold each tenant to R requests in any trailing 60 seconds, in aggregate across three regions and every pod, within a budget of under 10 ms added p99. Peak is 30,000 requests/second across 200,000 active tenants, and traffic is heavily skewed toward a handful of them. Give an exact single-process algorithm with its amortised per-request cost and its memory per tenant, then a bounded-memory approximation and the worst-case overshoot it actually admits. Say what the distributed version does when the counter store is unreachable.
Approach
- Exact, single process: a per-tenant deque of request timestamps. On arrival, pop from the front while
front <= now - 60s, then admit if the remaining length is below R and push. Each timestamp is pushed once and popped once, so the cost is O(1) amortised. The O(R) version is the one that re-filters the whole deque on every request. - Quote the memory. R = 1,000 across 200,000 active tenants is up to 2 x 10^8 timestamps at 8 bytes, about 1.6 GB, and that is the worst case rather than the mean, because the long tail of small tenants holds almost nothing. Skew helps you here and hurts you in the sharding decision.
- Bounded alternative, with its real bound stated: a fixed 60-second counter is O(1) memory but admits close to 2R across a 60-second span straddling a boundary. The weighted two-bucket estimate,
prev * (60 - elapsed)/60 + cur, is better on smooth traffic but assumes the previous window's arrivals were uniform; an adversary packing them at the end of that window is undercounted and can still approach 2R. Say that rather than calling it exact. - Token bucket is the usual gateway answer and a different contract: O(1) state per tenant (
tokens,last_refill), a sustained rate, and a deliberate burst allowance equal to the bucket size. Choose it when a burst is acceptable and the log when the limit is contractual. - Distributed: the limit is per tenant in aggregate, so a local bucket of R/N per pod is wrong in both directions under skew. A tenant landing on one pod is throttled at R/N, and a tenant spread evenly across pods exceeds R. The shared check must be a single atomic round trip, one script or one increment-and-compare, never read-then-write, and it must fit inside the 10 ms p99 budget.
- Decide the unavailable case in advance and write it down. Failing open keeps the product up and lets a tenant exceed its limit for the duration; failing closed converts a counter-store outage into a full outage. Most gateways fail open on rate limits and closed on authorisation, and those are two separate decisions made separately.
Worked solution (25 min)
- Implement the deque version and instrument the per-request pop count, then confirm total pops equal total pushes over a run.
- Generate a burst that places R requests in the last 100 ms of one minute and R more in the first 100 ms of the next.
- Run that burst through the exact deque, a fixed 60-second counter, and the weighted two-bucket estimate, recording admissions in the trailing 60 seconds at every instant.
- Size the memory as R x active tenants x 8 bytes at R = 1,000 and 200,000 tenants, and compare it against what a token bucket would need.
Expected result: The exact deque never admits more than R in any trailing 60-second window. The fixed counter admits close to 2R across the boundary. The weighted estimate lands between the two on this burst and approaches 2R once the previous window's requests are packed at its end.
Verify it yourself
- Total pops equal total pushes, which is what makes the amortised O(1) claim true rather than merely asserted.
- Sliding the whole burst by 30 seconds changes the fixed counter's result and leaves the deque's unchanged.
- Running two processes each limiting to R/2, against a tenant that sends everything to one of them, shows that tenant throttled at half its contractual limit.
Follow-up
- One tenant sends 40% of all traffic. What does that do to a single counter key, and what do you shard on instead?
- Quotas rather than rate limits: the check is
select used; if used < limit then insert. Name the isolation level that still permits the overshoot, and the two fixes. - How do you return an accurate
Retry-Afterfrom the exact algorithm without a second scan?
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
- 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.
- 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(*) ...) < 20narrows the race to the statement's snapshot but does not close it under read committed. - 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 runningupdates 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. - 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.
- 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 againststatus = 'running' and leased_until < now(), and treat the counter as a fast path overjob_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?
Decide which facts an invoice line copies instead of joining
Medium · denormalisation · immutable records · foreign keys · rounding
invoice_line_item already denormalises tenant_id, which is reachable through invoice_id, and stores amount_minor even though quantity times unit_price_micros would recompute it. A reviewer asks you to normalise both away, and separately asks whether the tenant's legal name and billing address should be copied onto the invoice header. Decide each case. For every field you keep denormalised, name the read pattern or the invariant that justifies it, the anomaly the copy can develop, and the mechanism that prevents that anomaly here.
Approach
- Split the question into two kinds of copy, because they fail differently. A copy of a currently mutable fact is a cache: it drifts and needs invalidation. A copy of a fact frozen at write time is not a cache at all, it is the record of what happened, and normalising it away destroys information the source no longer holds.
- Keep tenant_id on the line. It costs 8 bytes, it leads every index on the table so no read is ever accidentally cross-tenant, and it turns a wrong join into an empty result rather than another tenant's money. Prevent the drift structurally: a unique constraint on invoice (invoice_id, tenant_id) plus a composite foreign key from the line on (invoice_id, tenant_id) makes a mismatched pair impossible, so the database enforces agreement instead of a code review.
- Keep amount_minor. Rounding must happen exactly once, at a named site, with a stated mode (half-even here). If readers recompute from quantity and unit_price_micros, every reader owns a rounding decision, and half-up and half-even diverge systematically across thousands of lines rather than cancelling out. A check constraint can bound the stored value but deliberately cannot re-derive it.
- Copy the legal name and billing address onto the invoice header, written once and never updated. The statement must show what was true when it was sealed, and the tenant record will change afterwards. This is a snapshot for the same reason
source_rollup_watermarkis stored per line: without it, nobody can reconstruct what the customer was told. - Name the read pattern that pays for all of it. Rendering, dispute response and export are per-tenant, per-period reads over thousands of lines that would otherwise join back to slowly changing dimensions that no longer hold the historical value. The write side is a once-per-period batch, so the extra columns cost nothing that matters.
- Concede the case where the reviewer is right: a mutable operational attribute such as the tenant's current plan name has no business on a line. If a report wants it, join. If a statement needs the plan as of the period, that is another snapshot and it belongs on the header with the rest.
Worked solution (25 min)
- Write the DDL: unique (invoice_id, tenant_id) on invoice, the composite FK from the line, and a comment on each denormalised column saying whether it is a snapshot or a cache.
- Attempt to insert a line whose tenant_id differs from its invoice's and confirm the foreign key rejects it.
- Rename a tenant, re-render a sealed invoice, and confirm the rendered name is the one stored on the header.
- Recompute amount_minor from quantity times unit_price_micros for a thousand synthetic lines rounding half-up, sum both ways, and record the divergence from the stored half-even values.
Expected result: tenant_id kept and enforced by a composite foreign key to (invoice_id, tenant_id); amount_minor kept because rounding happens once with a stated mode; legal name and billing address snapshotted onto the invoice header and never updated; mutable operational attributes left normalised. Each decision paired with its read pattern, its anomaly, and the mechanism that prevents the anomaly.
Verify it yourself
- A line cannot be attached to an invoice belonging to another tenant, and the rejection comes from the database rather than the application.
- Re-rendering a sealed invoice after editing the tenant record produces identical output.
- Rounding per line and rounding once at the end differ measurably over a thousand lines, which is the argument for storing the value.
- No column on the line is recomputed by any reader.
Follow-up
- Write the composite foreign key and the unique constraint it requires on the parent. What does it cost on every line insert, and what does it do to a bulk load?
- A tenant is renamed after being invoiced. Which rows change, and what does the customer see on last quarter's PDF?
- Where does currency live, and what breaks if a tenant's billing currency changes between two periods?
Architect an evaluation pipeline capable of processing petabytes of drive logs and running parallel simulation tests for safety verification.
Medium · system design and architecture
Approach
- Fix the scope first: who calls this, how often, and what they do when it fails.
- State the consistency you need, and where you are willing to be stale.
- 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 real-time trajectory logging system that satisfies strict hardware memory boundaries on onboard vehicle computing hardware.
Medium · system design and architecture
Approach
- State the consistency you need, and where you are willing to be stale.
- 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.
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?
Design an over-the-air (OTA) software update delivery platform for a fleet of autonomous vehicles operating under intermittent network connectivity.
Medium · system design and architecture
Approach
- State the consistency you need, and where you are willing to be stale.
- Name the failure you are designing for, then the recovery path.
- Name the read and write paths separately; they rarely have the same bottleneck.
Follow-up
- What would you drop to keep the system up under load?
- What breaks first when traffic grows ten times?
Per-tenant rate limiting that holds across one hundred pods
Medium · rate limiting · hot keys · atomicity · write skew
The same gateway enforces each tenant's plan rate limit in requests/second and its monthly quota. Traffic is heavily skewed: a handful of tenants exceed all others combined, while a small tenant's requests may all land on one of the 120 pods. Both limits must hold in aggregate rather than per pod, per credential or per region, and the check may add at most 2 ms at p99. Specify the algorithm, where the state lives, the exact operation performed per request, and the behaviour when the counter store is unreachable - separately for the rate limit and the quota.
Approach
- Do the arithmetic on per-pod buckets before discarding them, and be precise about which half fails. Giving each pod rate/N keeps the sustained aggregate correct - 120 buckets of 5 rps sum to the 600 rps limit - but a tenant whose traffic all lands on one pod is throttled at 5 rps, a hundred and twentieth of what it pays for. The over-admission comes from burst depth, not sustained rate: if each pod carries a full bucket depth B, the aggregate burst is 120B against a limit that intended to allow B.
- Move the rate limit to a shared counter accessed in exactly one atomic round trip - a token bucket or GCRA as a single server-side script returning allow/deny plus a retry-after - never a read followed by a write. Keep the key tenant-scoped and region-local; a globally shared counter costs a cross-region round trip that alone exceeds the 2 ms budget, so shard the global limit per region in proportion to observed regional share and accept that a tenant whose traffic shifts regions is briefly throttled until the shards rebalance.
- Cut round trips for the largest tenants with leased tokens: a pod leases 50 at a time and spends them locally, reducing counter round trips by 50x at the cost of bounded over-admission of pods x lease_size, which is 6,000 requests in the worst case here. Shrink the lease for tenants with small limits, where that bound would swamp the limit itself.
- Treat the quota as a different problem, not a longer window. It is a count that other in-flight requests are changing, so select-the-usage-then-insert is write skew: read-committed permits it and repeatable read permits it too, because snapshot isolation is exactly what allows two transactions to read a consistent count and both write. Enforce it inside one statement - an atomic increment whose returned value is compared, or UPDATE ... SET used = used + $1 WHERE used + $1 <= limit RETURNING - or with a constraint that makes the surplus write fail.
- Write the unavailability policy per limit rather than one policy for both. The rate limit fails open to a conservative local bucket, because it protects capacity and failing closed turns a counter blip into a total outage. The quota fails open for tenants whose last known usage was well below the cap and closed for those at or above it, since that is the only decision with revenue attached, and reconciles from the usage ledger on recovery - which is why the monthly counter must be re-seedable from that ledger rather than being the only copy.
Worked solution (30 min)
- For a 600 rps limit across 120 pods, compute the sustained rate and the aggregate burst under local buckets of depth B, then the throughput of a tenant whose traffic all lands on one pod.
- Write the shared-counter operation as a single atomic script: its inputs, its return value, and the number of network round trips per request.
- Size a lease of 50 tokens: compute the worst-case over-admission as pods x lease_size and the reduction in round trips, then decide the smallest limit at which the lease is still acceptable.
- Write the two unavailability policies side by side and the reconciliation step that runs when the counter store returns.
Expected result: A per-region shared token bucket keyed (tenant, region, window) executed as one atomic script at roughly one sub-millisecond intra-region round trip; the monthly quota enforced by a single atomic increment-and-compare that is re-seedable from the usage ledger; leases only for high-limit tenants with over-admission bounded at 120 x 50 = 6,000 requests; the rate limit failing open to a conservative local bucket and the quota failing closed only above the last known cap.
Verify it yourself
- Separate the two halves of the local-bucket claim: sustained aggregate is correct at rate/N per pod, burst is not. If your answer says local buckets let a tenant exceed its sustained rate, the arithmetic does not support it.
- Re-read your quota check and confirm no branch reads the counter and then writes it. If the predicate is not inside the write or enforced by a constraint, the race is still there.
- Compare worst-case lease over-admission against the smallest tenant limit you apply leases to. If 6,000 exceeds that tenant's per-minute allowance, the lease size is wrong for that tier.
Follow-up
- One tenant is 40% of all traffic and its counter key lands on a single shard. What do you change, and what does that cost in accuracy?
- Clients receive 429s and retry. What stops the well-behaved ones from synchronising into a herd?
- A tenant reports being throttled below its limit. Which evidence do you produce, and does your design emit it today?
Regional error rate explodes after a dependency merely slows
Hard · retry storm · circuit breaker · partial outage
A control-plane read replica in one region degrades from 4 ms to 120 ms. Within ninety seconds that region's gateway error rate rises from 0.01% to 40% and its p99 becomes bimodal, one mode near the old p99 and one at the client timeout. The other two regions are unaffected. The gateway retries control-plane reads three times with exponential backoff and no jitter. Give an ordered checklist that separates trigger from amplifier, the offered-load arithmetic, and the controls that break the loop.
Approach
- Split the incident into three questions before touching a control: what started it, what amplified it, and what would make recovery slow. Here they are the replica slowdown, the retry policy interacting with queueing, and a synchronised unjittered herd at recovery. They are different mechanisms and each needs its own control.
- Read the distribution rather than the mean. A bimodal p99 with one mode pinned at the client timeout is two populations, not one degraded path; split latency by cache hit and miss and confirm the fast mode is hits and the timeout mode is misses that reached the replica.
- Do the load arithmetic. Three retries turn one client request into up to four upstream requests, so offered load reaches roughly 4x on a dependency that is already slower, and it arrives at the worst moment. With utilisation approaching one, queueing delay grows superlinearly, which is why a 30x latency increase upstream does not produce a 30x increase downstream, it produces timeouts.
- Break the loop with controls that bound offered load rather than with more attempts: a concurrency limit on the control-plane client so at most N calls are in flight and the remainder fail fast, a circuit breaker scoped per dependency and region, and a retry budget capping retries at a small fraction of base traffic so amplification has a ceiling that does not depend on how many clients are retrying.
- Add full jitter to whatever retries survive, sleeping uniformly in [0, min(cap, base x 2^attempt)], so attempts de-correlate instead of arriving in waves aligned to the moment of failure.
- Decide the unreachable-dependency behaviour in advance, because it is the actual product decision underneath: serving from an expired credential cache keeps the product available while extending a revoked key's life past the stated bound, and failing closed converts a dependency degradation into a total outage. State the mode and the staleness number rather than letting the timeout choose.
Follow-up
- The replica recovers. Describe what happens in the first ten seconds with your controls in place versus without them.
- Which single metric would have paged before the error rate moved, and why is upstream latency by itself not it?
- Requests that fail fast under the concurrency limit still need an answer. What does the gateway return, and what does it do to the usage event it would otherwise have emitted?
Suggested practice checklist
For a candidate senior enough that the loop turns on design and judgement rather than on whether the coding round gets finished. Five days build one system properly and then stress it; coding gets a single maintenance day, on the assumption that the risk at this level is an unexamined tradeoff rather than a missed algorithm.
Day 1 — Screening prep and a blank-file baseline
- Write the one-page project sheet for two projects: scope, your contribution, the hardest problem, the result, and one bug you fixed. This covers the screening call and the resume deep dive in the technical screens.
- List the questions to ask your recruiter: team, coding language, whether C++ internals or a domain deep dive is included, and the focus of each onsite session.
- In an empty file with no autocomplete, solve a graph traversal (BFS and DFS) and a palindrome check. Include a main that runs empty, single-element and normal cases.
Deliverable: Two project sheets, a recruiter question list, and two complete runnable programs written from blank files.
Day 2 — Reported coding patterns
- Implement a trie that supports mkdir and ls on slash-separated paths, returning ls results in sorted order. State the cost of each operation in terms of path length.
- Solve an interval problem (minimum meeting rooms with a min-heap, or gaps and intersections between interval lists), then a grid traversal such as revealing a Minesweeper region.
- Sort characters by frequency and group points within a distance threshold using union-find. State the complexity of each before coding.
- Try the worked exercise 'Hold a tenant to a trailing sixty-second request limit' to practise sliding-window reasoning under a stated memory bound.
Deliverable: Four or more runnable solutions, each with its complexity and the edge cases it handles written above the code.
Day 3 — Concurrency: schedulers and buffers
- Build a multi-threaded job scheduler in your interview language with a task queue, a worker pool, cancellation of queued and running tasks, and a clean shutdown that joins every worker.
- Extend it into a periodic scheduler that re-queues recurring jobs. Explain what happens when a job runs longer than its period.
- Implement a thread-safe ring buffer between a producer and a consumer. Use it to answer the reported question about a vehicle's position at any timestamp in the last 10 seconds: evict old entries, binary search by time, and interpolate between neighbouring samples.
Deliverable: A working scheduler and ring buffer, each with a written note on which state is shared, what protects it, and how shutdown works.
Day 4 — C++ internals and debugging threaded code
- For vtables, smart pointers, move semantics and cache locality, write the mechanism, the runtime cost and an alternative. Include how virtual calls affect a low-latency loop.
- Implement a fixed-capacity open-addressing hash map or a simple pool allocator without standard containers, then make it thread-safe and explain the cost of contention.
- Take three short multi-threaded snippets with a data race, a lock-order deadlock and an uninitialised pointer. Fix each one and say how you would confirm the bug with a sanitizer or a stress test.
- Work through the debugging drill 'Regional error rate explodes after a dependency merely slows' to practise separating the trigger from the amplifier.
Deliverable: A C++ internals cheat sheet written in your own words, one from-scratch container, and three diagnosed and fixed snippets.
Day 5 — Cloud-side design: logs, pipelines, pub/sub
- Design a pub/sub system that streams sensor logs from vehicles to cloud storage. Cover partitioning, delivery guarantees, backpressure, and replay after an outage.
- Design an evaluation pipeline that runs parallel simulation tests over very large volumes of drive logs. Cover how jobs are split, how failed shards are retried, and how results are compared between software builds.
- Sketch fast search over raw AV logs or a bounded-memory search over vehicle records, and say which index you would build and why.
- Review the worked design exercise 'Per-tenant rate limiting that holds across one hundred pods' for how to keep shared state atomic across many workers.
Deliverable: Two designs, each starting from its constraints and failure modes, with the fallback state written next to every component.
Day 6 — Onboard design and domain questions
- Design OTA update delivery for a fleet with intermittent connectivity: resumable chunked downloads, integrity checks, staged rollout, an A/B slot with rollback, and what the vehicle does if an update fails partway.
- Design a real-time trajectory logging system within a fixed onboard memory limit. Cover the buffer layout, what gets dropped or downsampled when it fills, and what gets flushed first when connectivity returns.
- Write short answers on sensor fusion trade-offs between LiDAR, radar and camera in bad weather, and on how you would test an actuator and detect its faults.
Deliverable: Two onboard designs with explicit memory and connectivity budgets, plus one page of domain notes you can reason from.
Day 7 — Behavioural stories and a back-to-back mock
- Write one story each for disagreeing with a senior engineer, a production bug and its prevention, speed versus safety, and cross-disciplinary delivery. Check that the facts match your project sheets.
- Run a back-to-back mock: one blank-file coding problem from days 2-3, then one design question from days 5-6. Ask the interviewer to change a constraint halfway through.
- After the mock, write down where you lost time and which follow-up you could not answer, then redo that part.
Deliverable: Four behavioural stories and mock notes that record where the design changed and which gaps you closed.
Bounding a vague ticket before writing code
The reported behavioural questions cover four themes: disagreeing with a senior engineer's design, diagnosing a hard production bug, balancing fast iteration against safety and validation, and delivering across hardware, software and operations teams. Prepare one specific story for each. Name the decision you made, the evidence you used, and what you changed afterwards. Keep the project facts the same as in your resume deep dive, since the same work may come up again.
- Describe a time you disagreed with a technical design choice made by a senior team member. How did you resolve it, and what evidence did you use?
- Tell me about a complex bug you encountered in production: how you diagnosed it and what long-term prevention you put in place.
- How do you prioritise between rapid iteration and strict safety or validation requirements? Walk through one specific decision.
- Describe a project where you collaborated across hardware, software and operations teams to deliver a critical feature.
- Explain the scope of a project you owned, which parts were yours, and the hardest technical challenge you solved in it.
- Tell me about a time you had to prioritise testing or QA work with incomplete information when a safety-relevant outcome depended on it.
Describe a project where you had to collaborate across multi-disciplinary teams (e.g., hardware, software, operations) to deliver a critical feature.
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.
- Close with what you would do differently, concretely.
Follow-up
- What would you do differently if you ran that again?
- How did you know your change caused the improvement?
Disclose a cross-tenant webhook delivery to affected customers
Medium · cross-tenant leak · disclosure · blast radius · authorisation checks
An enqueue path took the subscription from one lookup and the payload from another. For nineteen minutes, webhook_delivery rows were created whose tenant_id did not match the subscription's tenant, and eleven payloads were signed and sent to four endpoints belonging to other customers. You hold payload_digest, delivery timestamps and response codes. Describe how you handle a disclosure of this kind: what the records prove, what they cannot prove, what you say before you know everything, the one code change that closes it, and which parts you personally drove.
Approach
- Bound the population before saying anything externally. The affected set is deliveries in the window where the event's tenant and the subscription's tenant differ; the ones that actually left are those with delivered_at set and a 2xx in last_response_code. Attempted and delivered are two different counts and a disclosure has to use the right one in the right sentence.
- Separate what the records prove from what they do not, and say both halves rather than the flattering one. They prove which payloads were signed, where they went, and — through payload_digest — exactly which bytes. They do not prove what the receiving system did with them, and they do not bound the window more precisely than your deploy timestamps do.
- Communicate on the facts you hold, with the scope stated as an upper bound: 'at most eleven payloads, four recipient endpoints, these fields, this window' is more useful and more honest than waiting a day for certainty. The field list matters more than the event count, because a customer cannot assess exposure from 'an event'.
- Name the code change precisely, because this class never originates in the delivery worker. Compare the event's tenant against the subscription's tenant at enqueue and again immediately before the payload is signed, and make the second comparison drop the delivery rather than log a warning. Say why one check is insufficient: the enqueue check protects against the bug you know about, the pre-signing check protects the boundary itself.
- Run the history question in parallel and say so: a query over historical deliveries for the same mismatch tells you whether this was nineteen minutes or a year, and you would rather find the second case yourself than have a customer find it after your disclosure.
- Split the response into workstreams with owners — recipients asked to delete, affected customers notified, the check landed with a test, history swept — and say which you personally drove and which you handed off. Claiming all four is not credible and claiming none is not ownership.
Follow-up
- The historical sweep finds two more instances from last year. What changes in what you have already told people?
- Who approves the wording, and what do you do when you are asked to soften the scope?
- A customer asks you to prove a redelivery contained the same bytes as the original. What do you show them?
Unblock an engineer on a job run that finished twice
Easy · mentoring · fencing tokens · lease expiry · debugging method
An engineer two weeks into the team brings you a job_run row showing status succeeded with an exit_code written by a worker declared dead ten minutes earlier; the retry attempt also shows succeeded. They have spent a day adding logging and are no closer. You have twenty minutes and you do not want to take the keyboard. Describe how you unblock someone: the question you ask first, what you let them find themselves, the concept you name and when, and how you check the next day that they own the fix rather than having watched you produce it.
Approach
- Ask what they expect rather than what they see: which statement set status to succeeded, and what did it check before writing? That question points directly at the update's WHERE clause, which is where the answer lives, and it costs them nothing to answer, so it does not read as a test.
- Let them build the timeline themselves from the row: queued_at, started_at, leased_until, finished_at and worker_id, on both the original run and the retry. Two different worker_ids with a lease expiry between them tells the whole story, and they will see it before you say it.
- Name the concept once the evidence has earned it. A lease bounds time; it does not prevent a write. The store has to reject a stale writer, which means the update carries a fencing token the row compares — update job_run set status = 'succeeded' where run_id = $1 and lease_token = $2 and status = 'running' — and a long garbage-collection pause or a brief partition is enough to produce what they are looking at.
- Point at the second, less obvious half and let them decide it: 'lost' exists in the status enum precisely so a run whose worker vanished is not recorded as failed, because failed asserts an outcome nobody observed and the system then bills and retries on that assertion. Ask them what these two rows should have said.
- Leave them with the next step rather than the patch — a test that kills the first worker after the sandbox exits and before the row is written — and say when you are available again, so the offer is real rather than polite.
- Check ownership the next day by what they produced, not by asking if it went well: a test that reproduces the window proves they understood it; a test that only asserts the new WHERE clause proves they copied it. Ask them to explain it to a third person and listen for whether the explanation is theirs.
Follow-up
- They propose a longer lease instead of a token. What do you say, and what breaks when legitimate runs last thirty minutes?
- How can you tell whether your explanation landed or they simply deferred to you?
- The same engineer hits a variant of this next month. What did you fail to teach the first time?