The 60-second brief
Zoox designs and manufactures custom, fully autonomous, bidirectional robotaxis built for dense urban environments, rather than fitting sensors to existing vehicles. Software engineers work on teams such as Driving Behavior, Vehicle Experience, Build Infrastructure, Systems Integration and ML Applications, writing C++ and Python in a large monorepo. Depending on the team, the code handles real-time sensor streams, motion planning, electromechanical state machines, or build and simulation pipelines. Prepare to handle edge cases and latency bounds in your answers, not only the happy path.
Zoox candidates report 5 rounds · ≈ 4-6 weeks. The stages below are what candidates describe, not a published process.
What a Software Engineer does at Zoox
Candidates report that the interviews go beyond standard algorithm problems. The reported question categories are language internals (C++ vtables, move semantics, smart pointers and threading, or Python memory management, the GIL, generators and context managers), object-oriented design of small domain systems (a roller coaster scoring system, an airline fee calculator, a calculator engine, a lane-change interface), a Math and Problem-Solving (MAPS) module of geometry, kinematics, probability and logic word problems, system design, and a presentation of a past project followed by questions on it.
Which language you are tested in depends on the team, so confirm it with your recruiter before you plan. Candidates describe C++ as the norm for on-vehicle, motion planning, controls and vehicle experience work, and Python for infrastructure, ML tooling and backend platform work. Whichever language you get, prepare to explain what the runtime does, not only what the code outputs. Practise MAPS problems by writing out variables, units and assumptions, because that part is not a coding exercise.
The Zoox interview process
Zoox candidates report 5 rounds · ≈ 4-6 weeks. The stages below are what candidates describe, not a published process.
Recruiter Screen
Candidates describe this as a conversation about your background, team preferences and basic qualifications, including your motivation for joining Zoox and salary expectations. Use it to find out which team the loop is for and which language it will be run in, because those two answers decide how you spend the rest of your preparation.
What it evaluates
- Whether your background maps clearly onto one of the team areas candidates name, such as Driving Behavior, Vehicle Experience, Build Infrastructure, Systems Integration or ML Applications
- Whether you can explain why you want to work on autonomous vehicles at Zoox specifically, rather than at any employer
- Whether your primary language and project history fit the role's basic qualifications
How to prepare
- Write two sentences linking your strongest project to one Zoox team area, naming the constraint you worked under (latency, memory, safety, build scale), not just the stack
- Ask directly whether the technical rounds will be in C++ or Python, and which onsite modules (presentation, coding and debugging, MAPS, design) your loop includes
- Prepare a specific answer on why a purpose-built robotaxi interests you compared with a converted vehicle, based on what you have read about Zoox's vehicle
Technical Phone Screen
Candidates report a shared coding environment with algorithmic coding and domain-specific trivia. Some describe an automated assessment instead of a live session. The live version mixes quick language questions (C++ memory management or the Python execution model) with an object-oriented design or domain coding problem.
What it evaluates
- Whether you answer language-internals questions with the mechanism behind them, such as how dynamic dispatch goes through a vtable, instead of a one-line definition
- Whether a short domain computation handles units and edge cases, like zero or negative inputs or a speed given in one unit when the answer needs another
- Whether a small problem gets a clean structure (named types, a function per responsibility) rather than a single block of code
How to prepare
- Drill twenty rapid-fire internals questions in your interview language out loud, answering each in three sentences: what it is, what the runtime does, and when it bites
- For domain coding practice, solve the reported coding questions Time to Stop From Deceleration and Two-Second Follow Distance with explicit unit conversion and a guard for invalid inputs
- Solve the reported coding questions OverallScore Computation and Extensible Score Output, then add a new score type and count how many places in your code had to change
Onsite Interview Loop
Candidates describe the onsite as four to five rounds tailored to the team. Reported modules include a technical presentation of a past project, an object-oriented coding and debugging round, a Math and Problem-Solving (MAPS) round, a low-level or backend design round, and conversations with a hiring manager and senior leadership. Candidates say the MAPS round is worked by hand rather than in code.
What it evaluates
- Whether you can read unfamiliar code and name each defect: a missing virtual destructor, a shared_ptr cycle, a dangling reference, a data race
- Whether MAPS answers define variables and units, state assumptions, and sanity-check the result against limiting cases
- Whether a project presentation covers constraints, rejected alternatives, failures and what you would redesign, and holds up under follow-up questions
How to prepare
- Write a 100-line C++ program with five planted defects (slicing, missing virtual destructor, shared_ptr cycle, use-after-move, unsynchronised counter), then have someone reorder it and find them while narrating
- Work ten MAPS-style problems by hand across geometry, kinematics, probability and rate-of-work, writing the equation before any number
- Build the project presentation in five parts: problem context, architecture, key trade-offs, failures and edge cases, retrospective. Then rehearse it with someone who interrupts
System Design Interview
Candidates describe a design round focused on systems relevant to the position. The evaluation areas reported for system design are state machine architecture with fault handling and fallback states, data flow and latency (pub/sub, IPC, bounded execution), and API and interface definition between vehicle modules and cloud services. The example scenarios reported for system design are a system that keeps in-vehicle rider interactions (lighting, audio, display) in sync with vehicle motion state, and a pipeline that replays sensor logs across many compute instances. Separately, reported object-oriented and low-level design questions (an electromechanical state machine for doors or active suspension, and lane-change class interfaces) are useful practice for the state machine and interface areas.
What it evaluates
- Whether a state machine design lists every state, transition and guard, and says what happens on a fault partway through a transition
- Whether interfaces between modules are explicit about inputs, outputs, timing and who owns each piece of state
- Whether you discuss trade-offs (latency against throughput, determinism against flexibility) before drawing components
How to prepare
- Design the rider-interaction system: the motion-state source of truth, how lighting, audio and display subscribe to it, and what each shows when a motion update is late or missing
- Practise the reported sensor-log replay pipeline design, naming the partitioning, the failure you design for, and the recovery path
- As state machine practice, design a door-closure state machine on paper: states, events, guards, a fault state for each transition, and the safe fallback when a sensor reading goes missing
Behavioral Interview
Candidates describe this round as focused on past experiences and fit with Zoox. Reported behavioral questions cover how you triaged an unexpected production failure, a strong technical disagreement over architecture, a trade-off made under deadline pressure without compromising safety, and why you want to build autonomous vehicles at Zoox. Prepare a few stories in depth rather than one shallow story per prompt, because follow-ups tend to push on a single thread.
What it evaluates
- Whether an incident story covers triage, how you communicated with stakeholders, and the specific change that prevents a recurrence
- Whether a disagreement was settled with evidence such as a benchmark, a prototype or a written proposal, not by seniority
- Whether a deadline trade-off names what you cut, what you refused to cut, and why that line sat where it did
How to prepare
- Pick four projects and map each reported prompt (failure triage, architecture disagreement, deadline trade-off, cross-functional collaboration) to one of them
- For each story, write out what you did, why, why not the alternative, and what you would change now, then have someone ask why three times in a row
- Write your answer to why Zoox in terms of the problem you want to work on and the team area it maps to, not general enthusiasm for the field
Questions Zoox candidates report
These are the prompts PracHub has collected for this role and company. They are a record of what candidates describe being asked, so treat them as the shape of the conversation rather than a script Zoox works from.
System design and architecture
At any point in a design you should be able to name the one resource that gives out first and what it costs to move it. Most follow-ups push on exactly that: traffic rises tenfold, a region is lost, a stricter consistency requirement appears. Answers hold up when the assumptions were said out loud early, because the design can then be re-derived from them rather than defended. The weak move under pressure is to add a component. The strong one is to say which part absorbs the change, which part has to be rebuilt, and roughly what the rebuild costs.
- What are decorators, generators, and context managers in Python, and how would you implement a custom context manager to handle hardware resource locks?
- Calculator Engine: Implement a clean object-oriented calculator core capable of parsing, evaluating, and extending mathematical operator expressions cleanly.
- Explain dynamic typing versus static typing, and discuss how type hinting and static analysis tools improve large-scale monorepo reliability.
- What is the Global Interpreter Lock (GIL), and how would you architect a CPU-bound versus an I/O-bound pipeline in Python to maximize performance?
Languages, concurrency and fundamentals
Before choosing threads, processes or async, classify the work by which resource is saturated. CPU-bound work needs real parallelism, so it needs cores, and a runtime that serialises bytecode execution will not provide any however many threads you start. I/O-bound work is waiting rather than computing, and concurrency alone fixes it: one thread or one event loop can hold thousands of open sockets. Weak answers reach for threads by default. Strong ones name the bottleneck first, then the model, then what the model costs, which is either shared memory and data races or isolated memory and serialisation on every hand-off.
- Predict the output of a given 2-page C++ code snippet containing smart pointer usage, inheritance patterns, and memory leaks, then correct the bugs live.
- How does multithreading work in C++11 and beyond, and what mechanisms do you use to prevent race conditions in safety-critical vehicle control systems?
Other topics
Most prompts in this group are underspecified, and the honest answer is conditional. Find the variable that flips the decision, such as read-heavy against write-heavy or data that fits in memory against data that does not, name it, then answer for the case you picked. A weak answer states a preference as though it held everywhere. A strong one gives the rule and the point where the rule changes. Say the assumption out loud and commit to it: an assumption can be corrected by the person asking, while an answer hedged in every direction at once gives them nothing to correct.
- Explain the physical and geometric principles behind why Earth experiences seasonal climate shifts.
- Solve a kinematics problem: Calculate the maximum safe velocity of a vehicle negotiating a curved path under given deceleration constraints.
Practice exercises with worked solutions
The prompts above tell you what gets asked. These are PracHub exercises written to drill the same skills, with the reasoning spelled out so you can check your own answer instead of guessing whether it was good enough.
Rebuild an hourly rollup with deduplication and late-arrival accounting
From usage_event (event_id, tenant_id, workspace_id, environment, sku, quantity numeric(20,6), idempotency_key, occurred_at, ingested_at), produce the values usage_rollup_hourly should hold for one tenant over one day: per (workspace_id, sku, hour_start) the deduplicated quantity_sum, event_count and source_max_ingested_at, bucketed by occurred_at. Duplicates share (tenant_id, idempotency_key). Also report, per hour, the running total across the day and the share of quantity that arrived more than two hours after the hour began. Write the query, and state which duplicates a daily unique index cannot catch.
How a strong candidate works it
- Deduplicate in its own CTE before any aggregation, because a SUM cannot be un-summed:
row_number() over (partition by tenant_id, idempotency_key order by ingested_at, event_id) = 1. Include the tiebreaker. Without it the surviving row is non-deterministic when two duplicates share an ingested_at, and a rollup described as deterministically recomputable then disagrees with itself between runs. - Bucket on occurred_at and nothing else, and pin the timezone explicitly.
date_trunc('hour', timestamptz)truncates in the session's TimeZone setting, so the same query run by a session set to a non-UTC zone buckets differently; use the three-argumentdate_trunc('hour', occurred_at, 'UTC')on PostgreSQL 16 or later, ordate_trunc('hour', occurred_at at time zone 'UTC') at time zone 'UTC'before that. Filterenvironment = 'production'explicitly, since metering covers three environments and billing covers one. - Aggregate to the grain with
sum(quantity),count(*)andmax(ingested_at). The last is not decoration: it is the watermark the row consumed up to, and without it there is no way to prove afterwards what a number did and did not include. - Compute the late share inside the dedup-and-aggregate step as a conditional aggregate,
sum(quantity) filter (where ingested_at > hour_start + interval '2 hours'), then divide by the hour's total. Compute the running total as a window over the already aggregated rows:sum(quantity_sum) over (partition by workspace_id, sku order by hour_start rows between unbounded preceding and current row). Running either over raw rows puts the duplicates back. - Answer the index question exactly. The unique constraint is on (ingested_day, tenant_id, idempotency_key), because a unique index on a partitioned table must contain the partition key. It therefore deduplicates only within one ingest day and admits a duplicate whose retry crosses midnight or whose replay runs a week later. That is why this CTE dedups across the whole window being recomputed, and why the dedup horizon is a correctness parameter rather than a retention cost.
- Keep the numeric type all the way through. quantity is numeric so the sums are exact; a cast to double precision anywhere in this pipeline reintroduces drift that surfaces only as a few unreconcilable cents per tenant per month, long after the query is out of anyone's mind.
Expected result (40 min): One query: a dedup CTE using row_number() over (partition by tenant_id, idempotency_key order by ingested_at, event_id); aggregation by (workspace_id, sku, hour_start) producing quantity_sum, event_count and max(ingested_at) plus a filtered late-quantity aggregate; a running total as a window over the aggregated rows; and an explicit statement that the partition-scoped daily unique index cannot catch a duplicate that crosses the ingest-day boundary or arrives in a later replay.
Check yourself
- Row count after dedup equals raw count minus injected duplicates, exactly.
- The running total at the final hour equals the ungrouped day total for that (workspace, sku).
- Two consecutive runs over static data return byte-identical rows.
- Every hour_start is hour-aligned in UTC and unchanged by the session's TimeZone setting.
Follow-ups
- A dispute forces the same recompute over 40 days for one tenant. What changes about the dedup CTE's memory use and the chosen plan, and what would you do about it?
- Two runs a minute apart return different quantity_sum values for an hour that is already closed. Give two mechanisms that produce that, and the single query that distinguishes them.
- Express the same rollup incrementally so it does not re-scan the day each time the watermark advances. What does the incremental version stop being able to answer?
Enforce a concurrent-run quota that survives simultaneous requests
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.
How a strong candidate works it
- 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-ups
- 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?
Parse and verify a timestamped multi-signature webhook header
An inbound webhook carries a signature header of at most 1 KiB shaped t=<unix seconds>,v1=<64 hex chars>, with up to five v1 values during secret rotation and possibly unknown scheme keys. You hold the raw request body bytes and the currently active signing secrets. Write the parser and the verifier: accept when any active secret reproduces a signature and the timestamp is within a five-minute tolerance in either direction, reject otherwise. Single left-to-right pass over the header, no regular expression. State what is inside the MAC and why.
How a strong candidate works it
- Parse in one scan: split on
,, then on the first=only, since a value may itself contain=under a future scheme. Accepttexactly once and treat a secondtas a reject rather than last-wins. Push everyv1onto a short list and ignore any other key, so av2can be introduced later without breaking this verifier. - Say what is signed: HMAC-SHA256 over the exact byte string
<t>.<raw body bytes>, yielding 32 bytes or 64 hex characters. The timestamp sits inside the MAC because otherwise an attacker replays yesterday's body with its still-valid signature and only has to edit the header timestamp. - Hash the bytes as received. Verifying against a re-serialised JSON body is the usual defect: key order, whitespace and number formatting all change the bytes while the parsed objects compare equal, so signatures fail for honest senders and the popular 'fix' is to stop checking.
- Compare in constant time over fixed-length digests. Decode the hex to 32 bytes, accumulate
acc |= a[i] ^ b[i]across the whole length, and testacc == 0at the end. Evaluate every candidate without an early exit; at five candidates that is five HMACs over the body, linear in body size and negligible beside the network. - Apply the tolerance as a two-sided bound, rejecting when
|now - t| > 300seconds. A sender whose clock runs ahead of yours is an ordinary case, and an unbounded future timestamp is a free replay window. - Complexity: O(L) over the header producing k candidates, plus k HMACs at O(|body|) each. Space is O(k) beyond the body itself. Do the cheap rejections, including the tolerance check, before any cryptography runs.
Expected result (15 min): The valid case accepts. Both out-of-tolerance cases reject, including the future one. The unknown v2 field is ignored and the v1 still verifies. The re-serialised body fails, which is correct and is exactly why the raw bytes must be retained.
Check yourself
- Timing the comparison over 10,000 mismatching signatures that differ at the first character versus the last shows no separation you can measure.
- Removing
tfrom the MAC input makes an old captured body with its original signature pass. Write that test, watch it pass, then puttback. - Every rejection except the signature mismatch itself returns before an HMAC is computed.
Follow-ups
- The body is 40 MB. What changes about where you verify, and what can you do before the whole body has arrived?
- A customer reports that signatures fail for exactly the requests whose body contains a non-ASCII character. What is your first hypothesis?
- How do you rotate the signing secret with no failed deliveries, and how long do both secrets stay live?
Specify webhook signature verification a customer can implement
The webhook-delivery service signs each payload before POSTing it to a customer endpoint. Write the signature specification a customer implements in their own language: the header format, exactly which bytes are signed, the algorithm, how replay is bounded, and how a signing secret rotates without a delivery gap. Then write the verification steps the customer performs, in order, including what they compare and what they return on failure. Constraint: most customers reach for their web framework's parsed JSON body by default. Deliverable: the spec section plus reference pseudocode.
How a strong candidate works it
- Sign the concatenation of the timestamp and the raw body,
t + "." + body, and emit a header of the formt=<unix seconds>,v1=<hex>. The timestamp has to be inside the MAC, or an attacker re-stamps a captured body and the tolerance window buys nothing. - Require the raw request bytes. A framework that parses JSON and re-serialises it changes key order, whitespace and number formatting, so the spec must tell the customer to capture the body before the parser runs and give the middleware note for each common framework.
- Use HMAC-SHA256, not sha256(secret || body): SHA-256 is a Merkle-Damgard construction, so the naive form admits length extension. Require a constant-time comparison as well, since a short-circuiting byte compare leaks the expected prefix under repeated probing.
- Bound replay in two layers: reject when |now - t| exceeds a stated tolerance such as 300 seconds, then deduplicate on the event identifier header. The tolerance is what makes the customer's dedup store finite rather than unbounded.
- Rotate by allowing two live secrets and emitting both signatures in one header (
v1=<old>,v1=<new>); the customer accepts if any candidate matches, so neither side needs an instantaneous cutover. A failed verification returns 400 and the body is not processed.
Expected result (15 min): A spec where the signed input is the timestamp joined to the exact raw bytes, the algorithm is HMAC-SHA256 compared in constant time, replay is bounded by a stated tolerance plus event-id deduplication, and rotation is an overlap of two accepted signatures rather than a cutover.
Check yourself
- The timestamp is inside the signed string, not merely sent alongside it.
- The spec names raw bytes and says where the customer gets them before parsing.
- Rotation requires no coordinated instant on either side.
- The reason for HMAC over sha256(secret || body) is stated as length extension, not as a preference.
Follow-ups
- A customer's verification passes locally and fails in production behind a proxy that re-encodes the response body. Where do you look first?
- Why sign with a per-endpoint secret rather than the tenant's API key?
One tenant's counter writes stall the whole connection pool
A change that made a per-tenant usage counter correct now produces site-wide latency whenever one large tenant writes: unrelated endpoints time out waiting for a connection while database CPU stays low and no statement is slow. The change wraps the counter update in a transaction that takes SELECT ... FOR UPDATE on one row, calls an external pricing service, then updates and commits. Give an ordered checklist, the arithmetic that bounds that tenant's write rate, and three repairs with the cost each one accepts.
How a strong candidate works it
- Separate waiting from working. Low database CPU alongside high application latency points at a queue, so instrument connection-acquisition wait separately from query execution time; that queue forms in the application and is invisible in database metrics, which is why the database looks healthy throughout.
- Confirm the lock rather than assuming it: sample waiting sessions and group by wait event, relation and tuple. Contention concentrated on one tuple belonging to one tenant is the signature; a deadlock would instead show the database aborting transactions after its detection timeout, which is not happening here.
- Do the arithmetic out loud. Throughput on a serialised row is one divided by the lock hold time, and the hold spans the external call, so a 20 ms pricing call caps that tenant near 50 writes per second no matter how many pods run. Every waiter also holds a pooled connection while it queues, so the shared pool drains and unrelated tenants fail at acquisition.
- Repair one: shrink the critical section to a single statement with the price resolved before the transaction opens. Cost is a stale price for the duration of one request and a second round trip; benefit is a hold time measured in the database's own execution time.
- Repairs two and three change where the contention lives rather than how long it is held. Sharding the counter into per-(tenant, bucket) rows and summing on read multiplies write throughput by the shard count, at the cost of an aggregate on every read and a shard count you must size against the largest tenant rather than the median. Accumulating in memory and flushing periodically removes the per-write round trip entirely, paid for with a bounded loss window on crash, which is acceptable for a rate limiter and not for a billing counter.
- Contain independently of which repair wins: a separate pool or per-tenant concurrency cap for this write class, a statement timeout low enough that a pathological query dies before it accumulates waiters, and an idle-in-transaction timeout so a stuck client cannot pin a connection and its locks.
Follow-ups
- What would a genuine deadlock look like here, which two code paths would produce one, and how does the database's response differ from what you observed?
- If a transaction-pooling proxy sits in front of the database, which of your three repairs changes behaviour, and what stops working that would have worked on a direct connection?
- The counter also enforces a quota. Why is SELECT the count and then INSERT still wrong after you have fixed the contention?
Ship metered billing with a named deduplication horizon
Metered billing must be on in three weeks. usage_event is partitioned daily, so its unique index must include the partition key and deduplicates only within a day: a producer retry that crosses midnight, or a replay run a week later, gets through. A cross-partition dedup store is two weeks you do not have. Describe shipping with debt you named in advance: what you shipped, what you wrote down, the detector you added, the trigger and date for paying it off, and what you would have refused to ship under the same pressure.
How a strong candidate works it
- Show you can separate the two kinds of debt, because that distinction is what the question actually probes. Debt that costs engineering time later is shippable on a deadline. Debt that silently corrupts a number a customer gets charged for is not shippable unless the corruption is detectable, and detectability is the whole negotiation.
- Make the exposure narrow and measured rather than gestural. The hole is duplicates whose occurrences straddle a UTC day boundary, plus any replay older than partition retention. Measure it before arguing about it: how often an idempotency_key recurs at all, and the distribution of the gap between first and last occurrence. If the ninety-ninth percentile of that gap is four minutes, the residual risk is a small band around midnight and you can say so numerically.
- Add the detector before the feature, not after. A nightly job counting keys that appear in more than one partition is one grouped scan over recent partitions, and it converts a silent overcount into a page. State what it costs to run and what it fires on.
- Buy the cheap half of the real fix immediately: extend partition retention so the dedup horizon exceeds the producer's maximum retry window plus the longest replay you intend to support. That reframes retention as a correctness parameter rather than a storage cost, which is the sentence you need on record before someone optimises the bill.
- Make repayment mechanical instead of aspirational: a dated entry with a named owner, plus a threshold that pulls the date forward — first detector hit above N events, or first customer dispute. Debt with a trigger gets paid; debt with only a date does not.
- Answer the second half honestly by naming what you would refuse under identical pressure: the sealing path, because a sealed row is frozen and a wrong number there stops being a bug and becomes an adjustment line, a dispute and an audit question.
Follow-ups
- The detector fires on forty duplicate events for one tenant, and two of their invoices have already sealed. What happens next?
- Whom did you tell that the billing numbers had a known hole, and in what words?
- Finance asks you to cut storage by shortening partition retention. What do you say, and to whom?
Resolve a review disagreement over a quota check
A colleague's pull request enforces a per-tenant quota by selecting the current count and then inserting when it is under the limit. You flag it as a race. They reply that the transaction already runs at repeatable read, so the snapshot makes it safe, and the tests pass. Walk through taking that disagreement to a resolution: what you write in the review, what you demonstrate rather than assert, which fix you propose and why, and what you do if they still disagree after all of it.
How a strong candidate works it
- Answer the claim precisely instead of restating your objection, because they have made a specific technical argument. In PostgreSQL, repeatable read is snapshot isolation; this is write skew, which snapshot isolation permits by design. Both transactions read a count that is stable within their own snapshot, insert disjoint rows that the other cannot see, and both commit, so the limit is exceeded by exactly the concurrency.
- Demonstrate rather than cite. Two psql sessions, both BEGIN ISOLATION LEVEL REPEATABLE READ, both select the count, both insert, both commit: it succeeds. Repeat at SERIALIZABLE and the second commit fails with serialization_failure, SQLSTATE 40001. That takes two minutes, ends the argument without anyone conceding a position, and leaves an artefact for the next reviewer.
- Offer the options with their costs rather than a verdict. Serialisable plus a retry loop on 40001 is correct but obliges every caller to retry and degrades under contention. An increment-and-compare on a counter row — update tenant_quota set used = used + 1 where tenant_id = $1 and used < limit returning used — is safe even at read committed, because a blocked updater re-evaluates the WHERE clause against the row version it finally locks, and zero rows returned means full. A unique or exclusion constraint that makes the surplus write fail is the third.
- Name the plausible non-fix explicitly, since it is what usually gets merged instead: folding the count into the insert as insert ... select ... where (select count(*) ...) < limit is still racy under read committed, because the subquery cannot see the other transaction's uncommitted rows. It looks atomic and is not.
- Say what you do if they still disagree: escalate the decision rather than the disagreement. Attach the reproduction, hand it to the service owner or a third reviewer, and state that you will not block the merge if the owner accepts the risk knowingly — and that you want that acceptance written down.
- Close with the general lesson worth leaving in the review thread: a passing suite is weak evidence for a concurrency claim because it runs one request at a time. Ask for a test that runs two.
Follow-ups
- Write the counter-row version. Does your answer change if the quota counts child rows rather than a column?
- Under serialisable, who performs the retry, and what does the API client see if the retry also fails?
- This is the third disagreement with the same reviewer this month. What changes in how you review?
Reverse a webhook ordering decision after measuring its cost
You argued for strict per-subscription ordering in webhook-delivery, which means one in-flight attempt per subscription. It shipped. Three months later a single unresponsive endpoint holds one subscription's queue at a six-hour backlog, and two customers report events arriving out of order anyway once their own retries are counted. Describe a decision you reversed: what you originally optimised for, the measurement that changed your mind, what the reversal cost in engineering time and customer change, and how you told the people who had already built on the original guarantee.
How a strong candidate works it
- State the original decision as a trade you made knowingly. Ordering across a network requires a single in-flight attempt per subscription, and its price is head-of-line blocking whenever one endpoint is slow. 'We priced it wrong' is a much stronger opening than 'we did not realise', and it is usually the true one.
- Bring the measurement that flipped it, not the anecdote: backlog age at the ninety-ninth percentile per subscription, the share of subscriptions where one slow endpoint gated an otherwise healthy queue, and the delivery throughput lost to serialisation. A reversal justified by complaints is indistinguishable from a reversal justified by fatigue.
- Name what you learned about the guarantee itself, which is the engineering content of this story. At-least-once delivery means a retried event already arrives after newer ones and the consumer already must be idempotent, so a guarantee the customer has to defend against anyway was never worth what it cost to provide.
- Describe the migration, because reversing a published contract is the hard half and the part candidates skip. Parallel attempts behind a per-subscription flag, a monotonically increasing sequence number added to the envelope so order-sensitive consumers can sort or discard, documentation that states at-least-once and unordered in those words, and a deprecation measured in quarters because the client is a pinned SDK inside a build pipeline you cannot see or redeploy.
- Give the cost in the two currencies that matter: engineer-weeks, and how many customers had to change code. Then say who you told before it shipped rather than in a changelog afterwards, and which large customer you left on the old behaviour and for how long.
- Close with the signal you now weight differently, stated as something you would do earlier next time: measuring the blocking cost on the slowest decile of endpoints before committing to the guarantee, rather than after a customer noticed.
Follow-ups
- A customer insists they need ordering. What do you offer them that is not global serialisation?
- How did you choose the deprecation window given that you cannot see or redeploy the clients?
- What would have to be true for you to reverse back?
Where candidates lose points
Giving a one-line definition when asked about vtables, move semantics or the GIL, then stalling on the follow-up
Answer every internals question with the mechanism. For dynamic dispatch: each polymorphic object carries a pointer to its class's vtable, a virtual call is an indirect call through it, and that indirection usually blocks inlining. That is the performance trade-off against templates, which resolve at compile time at the cost of code size and compile time. For the GIL: only one thread runs Python bytecode at a time in standard CPython, so CPU-bound work goes to processes and I/O-bound work can use threads or asyncio. Practise saying the what, the how and the when-it-bites for each topic.
Reading a long C++ snippet top to bottom and guessing at the output instead of tracing object lifetimes
Trace construction and destruction order for every object and write down who owns each pointer. Check the usual defects on purpose: deleting a derived object through a base pointer without a virtual destructor, object slicing when passing by value, shared_ptr cycles that never free (break them with weak_ptr), using an object after it was moved from, and shared state touched by two threads without a lock or atomic. Name each defect before you fix it, and say how you would confirm it, for example with a sanitizer run.
Answering an object-oriented design prompt (coaster scoring, airline fees, calculator engine) with one function full of if/else branches
Before writing code, name the abstraction that varies: coaster type, fee rule, operator. Give it an interface, write one class per variant, and keep the scoring or pricing loop unaware of concrete types. Then show that adding a new coaster type or operator means adding one class and one registration line. For the calculator, keep parsing (tokenise, then build an expression tree) separate from evaluation so that new operators do not touch the parser's control flow.
Jumping straight to a number in the MAPS round without defining variables, units or assumptions
Write the givens with units, state assumptions (constant deceleration, flat road, point mass), write the governing equation, and only then substitute numbers. Stopping time is t = v/a and stopping distance is v²/(2a), so 20 m/s at 5 m/s² gives 4 s and 40 m. Check the result against limiting cases: zero deceleration should give infinite stopping time, and doubling speed should quadruple stopping distance. If you get stuck, say which assumption you would relax next rather than going quiet.
Designing a vehicle subsystem state machine that only covers the normal sequence of states
List states and events as a table, then fill in every cell, including events that arrive in the wrong state. For each transition, say what happens if a sensor fails or a command times out halfway through, which fault state it enters, and which fallback is safe for that subsystem. Say whether a transition can be interrupted and who is allowed to trigger recovery. A design that leaves out fault handling suggests you have not thought about the safety side of the work.
Debugging and fundamentals, for someone arriving from a scripting background
For someone fluent in a dynamic language who has shipped real work but has never had to say what the runtime is doing underneath. The week is built on measuring and deliberately breaking things, because the questions that expose this background are the ones where the interviewer asks why a second time.
Day 1 — Language internals for your interview language
- Confirm with your recruiter whether the loop is in C++ or Python, then list twenty internals topics for that language from the reported questions (C++: vtables, static vs dynamic polymorphism, smart pointers, RAII, move semantics; Python: reference counting, GC, GIL, generators, decorators, context managers).
- C++ track: answer the bank's Explain C++ References, Pointers, Virtual Methods, and Overrides out loud, then implement a class that owns a raw buffer with correct copy constructor, move constructor and both assignment operators.
- Python track: answer the reported context manager question by writing a custom context manager for a hardware lock that releases on exception, then answer the reported GIL question by measuring threads against processes on a CPU-bound task.
- Answer the reported question on dynamic versus static typing and type hints in a large monorepo, naming one bug a type checker catches and one it cannot.
Deliverable: A written three-sentence answer (what, how, when it bites) for each of the twenty topics, plus the resource-owning class or the context manager with a test.
Day 2 — Reading and debugging C++ code live
- Write a 100-line C++ program with planted defects: object slicing, a missing virtual destructor, a shared_ptr cycle, use-after-move, and an unsynchronised shared counter. Predict the output before compiling.
- Run it under AddressSanitizer and ThreadSanitizer and compare what the tools report with what you predicted.
- Practise for the reported C++ question about predicting the output of a two-page snippet with smart pointers, inheritance and memory leaks: trace construction and destruction order for every object and write down who owns each pointer.
- Answer the reported C++11 multithreading question: mutex, lock_guard, atomics, condition variables, and when each is the cheapest primitive that closes the race.
Deliverable: The planted-defect program with a list of each defect, how you found it, and the fix, plus a one-page threading note.
Day 3 — Object-oriented design of small domain systems
- Design the roller coaster scoring system from the reported question: an interface per coaster type (Wooden, Steel, Suspended), a scoring loop that knows only the interface, and a test showing a new type needs one new class.
- Design the airline ticket fee system with pricing rules as composable strategies (baggage, distance, seat class, dynamic factor) and say how rule order affects the result.
- As a warm-up, write a single-pass tokenizer that splits an expression such as 3 + 4 * (2 - 1) into number, operator and parenthesis tokens. Then work the reported calculator engine question: parse the tokens into an expression tree, evaluate it, and add an exponent operator.
- Solve the reported OverallScore Computation and Extensible Score Output questions with the same extensibility check.
Deliverable: Three class designs with interfaces drawn, one fully coded with tests, and a note on what changed when you added a new variant.
Day 4 — MAPS: geometry, kinematics, probability, logic
- Kinematics: work the reported Time to Stop From Deceleration and Two-Second Follow Distance questions by hand (60 mph is about 26.8 m/s, so a two-second gap is about 53.6 m), then the reported maximum-safe-speed-on-a-curve question using v = sqrt(a_lat × r) for the lateral limit.
- Geometry: compute the overlap area of two axis-aligned boxes, then of two rotated rectangles using polygon clipping and the shoelace formula.
- Explain the reported seasons question from axial tilt and the angle of incoming sunlight, and say why distance from the Sun is not the main cause.
- Probability and rates: one Bayes problem on detecting a vehicle across several noisy sensor frames, one rate-of-work problem with three pipelines, and the bank's Can a Car Meet a Truck?
Deliverable: Ten worked MAPS problems on paper, each with variables, units, assumptions and a limiting-case check written down.
Day 5 — System design for vehicle and pipeline problems
- Design an electromechanical state machine for door closure: a table of states and events, guards, a fault state for each transition, and the safe fallback on sensor loss.
- Sketch the class interfaces for the reported lane-change question: identifying relevant adjacent vehicles, the safety evaluation checks, and the conservative control inputs. Then work the bank's Lane-Change Safety and Longitudinal Motion Simulation as further practice.
- Work one system design: either the reported sensor-log replay pipeline across many compute instances, or the bank's system design question Design Nearby Search for Autonomous Vehicles. Cover the data layout, failure handling and the latency path.
- For the reported rider-interaction prompt, write the interface contract that keeps lighting, audio and display state in sync with motion state.
Deliverable: Two design write-ups (the state machine and one system design), each with interfaces, failure modes and the trade-offs you chose.
Day 6 — Project presentation and behavioral stories
- Build the past-project deck in five parts: problem context, architecture, key trade-offs, failures and edge cases, retrospective. Rehearse it with someone who interrupts with questions.
- Write stories for the reported prompts: a production failure you triaged, an architecture disagreement, a deadline trade-off that did not compromise safety, and cross-functional collaboration. Use the reported behavioral questions Resolving Senior Engineer Architecture Conflict and Explain Technical Findings to Executives as practice prompts.
- For each story, have someone ask why three times on one thread and note where your answer runs out.
- Write your answer to why Zoox, linking your experience to the purpose-built vehicle and to one team area.
Deliverable: A rehearsed presentation deck and a one-page index mapping each behavioral prompt to one of four projects.
Day 7 — Mock loop and gap repair
- Run a mock phone screen: ten rapid internals questions in your language, then one short domain coding problem, such as a new kinematics computation, under interview conditions.
- Run a mock onsite block: one debugging snippet, one MAPS problem worked out loud, and one object-oriented design prompt you have not seen, such as a parking-garage fee system.
- Redo every item you missed on days 1 to 5 without looking at your notes first.
- Prepare recruiter questions: which onsite modules your loop includes, and which team the loop is for.
Deliverable: A scored list of mock-loop misses with the fix for each, and your final list of questions for the recruiter.
Frequently asked questions
Is this an official Zoox interview guide?
No. It is PracHub's own research and practice material for the Software Engineer role at Zoox. Rounds and questions reflect what candidates have reported, not a process Zoox has published, and they change over time. Confirm the current format and scope with your recruiter.
How is the Zoox Software Engineer interview different from a standard algorithms loop?
Candidates report that algorithm problems are only part of it. The reported rounds also cover language internals (C++ or Python), object-oriented design of small domain systems, live reading and debugging of existing code, a Math and Problem-Solving (MAPS) module of geometry, kinematics and probability word problems, and a presentation of a past project. Split your preparation across those categories instead of spending it all on algorithm drills.
Should I prepare in C++ or Python?
Use the language your recruiter and target team specify. Candidates describe C++ for on-vehicle, motion planning, controls and vehicle experience roles, with questions on pointers, memory and object-oriented mechanics. They describe Python for infrastructure, ML tooling and backend platform roles, with questions on internals, async patterns and code optimisation. Ask early, because internals preparation for one language does not carry over to the other.
What is the Math and Problem Solving (MAPS) interview like?
Candidates describe MAPS as solving real-world analytical, geometric and logic problems out loud, without writing code. Reported examples include the intersection area of two shapes, the maximum safe speed through a curve under a deceleration constraint, why Earth has seasons, sensor-noise probability, and throughput across parallel pipelines. Practise by writing out variables and units, stating assumptions, and checking answers against limiting cases.
How should I structure the past-project presentation?
Build it in five parts: problem context, system architecture, key technical trade-offs, unexpected failures or edge cases, and what you would redesign now. Keep the slides visual and short. Most of the value is in the questioning afterwards, so for every trade-off be ready to name the alternative you rejected and why, and for every number be ready to say how it was measured.
How much physics and geometry do I need to review?
The reported problems use high-school level tools: 2D vectors and coordinate frames, shape and bounding-box intersections, constant-acceleration kinematics, basic probability including Bayes' theorem, and rate problems. Candidates list Kalman filters, matrix transformations and differential equations as less common. Aim to set up the equations quickly and correctly before you worry about advanced material.
Keep practising
- Zoox questions on PracHub — 21 Software Engineer questions in the bank.
- Software Engineer question bank — cross-company practice.
- Interview preparation framework
- All interview guides