The 60-second brief
Waymo builds the Waymo Driver, the autonomous driving technology behind its fully driverless ride-hailing and commercial transport. Software Engineers build the systems that let those vehicles perceive their surroundings, predict what other road users will do and move safely through changing road conditions. Some of that software runs on the vehicle; the rest is offboard infrastructure: ML pipelines, labeling and simulation.
Waymo candidates report 4 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.
What a Software Engineer does at Waymo
The work splits into onboard and offboard. Onboard roles include C++ motion planners, real-time perception, telematics Linux gateways and hardware control loops that have to finish inside fixed execution budgets. Offboard roles include distributed Python machine learning pipelines, multi-modal auto-labeling infrastructure, and simulation systems that test driving scenarios against real-world sensor logs.
That split shows up in the interview questions. Reported coding questions include graph and grid traversal, string dynamic programming, and debugging a C++ history buffer. Reported design questions cover fleet telematics, distributed inference for bulk model evaluation, annotation infrastructure and a backend for fleet operations. Reported domain questions include trajectory-prediction loss functions, geometric computation in C++, active learning and vehicle stopping distance. Candidates report that questions vary by team, such as Planner, Perception, ML Platform or Fleet Infrastructure, so ask early whether your loop is aimed at an onboard, ML or infrastructure group, and weight your preparation to match.
The Waymo interview process
Waymo candidates report 4 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.
Recruiter Screen
Candidates describe this as an initial look at your background and how it fits the open roles. Candidates report that questions vary by team (Planner, Perception, ML Platform, Fleet Infrastructure are the examples given), and some onboard and telematics roles may require live coding in C++. That makes this call the best place to learn what the rest of your loop will test. Ask which area the role sits in, which language you are expected to code in, and whether a domain knowledge round is part of your loop. Raise any hard constraints on timing or location here too, before any engineers spend time interviewing you.
What it evaluates
- Whether your background maps clearly onto an onboard, ML or infrastructure area, rather than a general claim that you can work on anything
- Whether you can summarise one or two projects in plain terms that a recruiter can pass on accurately
- Whether your constraints on start date, location and competing timelines are compatible with the role
How to prepare
- Write a two-sentence summary for each headline project, with no internal system names, and practise saying them aloud
- Prepare three questions for the recruiter: which team area the loop targets, which coding language is expected, and whether the loop includes a domain round
- Decide beforehand which team area you want to be considered for, and give one concrete reason drawn from your own work
Technical Phone Screen
Candidates report a coding question shared in a collaborative editor that checks core CS fundamentals, with live coding in C++ or Python on data structures, graph algorithms or data-handling exercises. Candidates do not tie specific questions to this screen, but the reported coding questions show the open-ended style to expect. One reported coding question, filling a grid with contiguous token groups via multi-source BFS, leaves key details for you to pin down. Before you code, settle the input, the constraints and what a correct output looks like. Then write complete, runnable code and walk through the edge cases without being asked.
What it evaluates
- Whether you clarify an ambiguous prompt before coding: input size, what counts as valid output, and what randomness or ordering is required
- Whether the code is complete and correct in the language you chose, including correct use of that language's standard library
- Whether you state time and space complexity and test boundary cases such as an empty grid, a single cell or a disconnected graph
- Whether you explain your reasoning as you go, so a hint can steer you when you get stuck
How to prepare
- Drill BFS, multi-source BFS, DFS and Dijkstra on grids until you can write each from a blank editor without autocomplete
- Practise with bank problems of the same shape: Shortest Path in Cost Maze, Find Shortest Knight Path, Find the Best Meeting Point on a Grid, and Compute ETA between two map nodes
- For every problem you practise, write down three edge-case inputs before you run anything, and check them against your code by hand
Virtual Onsite Loop
Candidates describe the virtual onsite as four to five rounds covering coding, system design and behavioural interviews. Some reports also mention domain deep dives and a behavioural or technical-leadership conversation with a hiring manager. Candidates do not tie specific questions to individual onsite rounds, so treat the reported questions in each category as a practice set. Coding: a C++ history buffer with memory leaks, race conditions and syntax errors, string dynamic programming, and graph traversal over spatial data. Design: a fleet telematics pipeline with OTA updates, a distributed inference platform for bulk evaluation, annotation infrastructure, and a fleet-operations API backend. Behavioural: framing ambiguous problems, design disagreements, and balancing feature speed against code health. A project you describe in a behavioural conversation may be the same system you sketched in design, so the figures and trade-offs you give need to match.
What it evaluates
- Whether coding answers cover correctness, edge cases and code quality, not just an algorithm that works on the example
- Whether design answers name the failure modes of vehicle-to-cloud systems, such as intermittent connectivity, ordering, backpressure and replay, and choose between them explicitly
- Whether behavioural stories show a decision you made, what it cost, and what happened afterwards
- Whether the figures and trade-offs you give for a project stay the same when it comes up again in a later round
How to prepare
- Work one C++ debugging problem end to end: find each defect, classify it as a compile error, an ownership problem or a concurrency problem, then refactor. Fix Racy Token Cache in the bank is the same kind of problem
- Design two fleet systems aloud from the bank, Car Position Logging System and Real-Time Sensor Ingestion Pipeline, stating the write rate, the buffering on the vehicle and the delivery guarantee each time
- Write a one-page sheet for each project you will discuss, with its scale, team size, timeline and the key trade-off, and rehearse from it until the numbers come out the same every time
Domain Knowledge Round
Candidates report this round only for specific roles, and it tests domain knowledge relevant to the position. Candidates do not tie specific questions to this round, and reports say domain questions can also come up in technical screens. The reported questions in the domain and ML category show the range you may meet here or elsewhere in the loop: the trade-offs between classification and regression losses for trajectory prediction, geometric computation in C++ such as cross products and nearest-neighbour search over point clouds, an active learning pipeline from selecting hard cases to fine-tuning, and a vehicle's stopping distance given its speed and a friction coefficient. When a question sits outside your experience, say where your knowledge ends and reason from first principles rather than guessing confidently.
What it evaluates
- Whether you state your modelling assumptions and units before you calculate, for example constant deceleration, a flat road and whether reaction time counts
- Whether ML answers explain why a formulation fits the problem, for example how a plain regression loss averages two plausible futures into one implausible one
- Whether geometric code handles degenerate inputs such as collinear points, duplicate points and an empty point set
How to prepare
- Derive stopping distance under constant friction deceleration (v squared over 2 times mu times g), then add a reaction-time term and check the result with real numbers
- Implement a 2D cross product, an orientation test and a convex hull in C++; Compute Convex Hull Boundary in the bank covers the boundary-point case
- Sketch an active learning loop on one page: how hard cases are mined, who labels them, how label quality is checked, and how the retrained model is evaluated before it replaces the old one
Questions Waymo 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 Waymo works from.
Data structures and algorithms
Ask which resource the problem actually takes away, because it is not always time. If the input does not fit in memory, the set you were going to build is gone and the bound that matters is space. If the data arrives once and cannot be revisited, anything needing a second pass is out whatever its complexity, and a constraint banning modification of the input removes the in-place trick that would otherwise be free. Naming the scarce resource first cuts the candidates down to a handful, and it stops you optimising a dimension the problem was never short of.
- Given a buggy implementation of a history buffer in C++, identify memory leaks, race conditions, and syntax errors, then refactor it for production stability.
- Solve a string dynamic programming problem focused on pattern matching and sequence alignment.
System design and architecture
A design is a set of promises about what happens when something is slow, down, or delivered twice, and that is the part usually left unsaid. For each dependency, say what the system does when it is unavailable: serve stale data, reject the write, or accept it and settle later. For each relaxation of consistency, describe the anomaly a user would actually notice, in the product's own words, rather than naming a consistency model and moving on. The separator is volunteering the failure behaviour as part of the design instead of producing it only when pushed.
- Design an API server and web backend to monitor real-time vehicle supply and depot logistics for autonomous fleet operations.
- Architect a real-time telematics data pipeline to handle continuous sensor logging, vehicle state telemetry, and over-the-air (OTA) updates for a large autonomous fleet.
- Design a high-throughput, low-latency distributed inference platform capable of hosting both internal deep learning models and large language models (LLMs) for bulk evaluation.
Behavioural and engineering judgement
Every question in this category has a number in it somewhere, and engineers routinely tell the story without one. What the outage cost, what the delay cost, what the rewrite consumed in engineer-weeks against what it saved, and who absorbed it when it was not users: the on-call rotation, another team's quarter, the support queue. You will rarely have an exact figure, and an estimate with its method stated beats both a precise-sounding invention and silence. Say how you got to it and what it leaves out. That is the sentence that makes the follow-up about whether it was worth doing answerable at all.
- Describe how you approach framing an open-ended, ambiguous real-world scenario into a well-defined engineering problem.
- Tell me about a time you had a technical disagreement with a colleague regarding software design and how you resolved it.
Other topics
Nothing constrains the range here, so expect at least one question at the edge of what you know. Answer at that edge deliberately: give the adjacent thing you do know, say plainly where your knowledge stops, and reason forward from the part you can defend. Calibration is itself information about you, and it is cheap to check, since one follow-up asking why will empty out an answer with nothing under it. Everything you have claimed as experience is fair material, so the item you last touched three years ago is worth an hour of re-reading rather than a hopeful skip.
- Solve a physics-based vehicle kinematics problem: given a vehicle traveling at velocity $x$ on a surface with friction coefficient $y$, calculate the exact stopping distance and braking threshold.
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.
Model the movement ledger so a retried write is a no-op
inventory_movement is append-only: movement_id, item_id, node_id, lot_id, state, delta_qty (signed BIGINT), uom_code, reason_code, ref_type, ref_id, idempotency_key, occurred_at, recorded_at, reversed_by. inventory_position is keyed (item_id, node_id, lot_id, state) with qty, version, last_movement_id. Give the DDL constraints and the exact statements a pick confirmation runs so that a client which times out after the commit and retries changes nothing. Then say how you correct a pick recorded against the wrong lot two days ago, and why the position carries CHECK (qty >= 0) rather than clamping at zero.
How a strong candidate works it
- Put the guarantee in the schema, not the handler: UNIQUE (idempotency_key) on inventory_movement. A SELECT-then-INSERT cannot work, because two concurrent retries both read nothing before either commits and both then insert.
- Claim with INSERT ... ON CONFLICT (idempotency_key) DO NOTHING RETURNING movement_id, and branch on rowcount. Zero rows means the first attempt already committed, so the handler returns success without touching the position. This branch is the whole point: applying the position update on the conflict path is what makes the ledger and the shelf disagree by one pick forever.
- Keep the insert and the position update in one transaction: UPDATE inventory_position SET qty = qty + :delta, version = version + 1, last_movement_id = :id WHERE item_id = :i AND node_id = :n AND lot_id = :l AND state = :s AND version = :v, then test the rowcount and retry the transaction on zero.
- Use lot_id BIGINT NOT NULL DEFAULT 0 rather than NULL. In a default unique index NULLs are distinct from each other, so a NULL lot would let two position rows exist for the same untracked item and split the running sum in half.
- Correct the mis-keyed pick with two compensating movements, never an UPDATE or a deleted_at flag: one reversal restoring the wrong lot, one fresh pick against the right lot, both with reason_code = 'reversal' or 'pick' and ref pointing at the original movement_id. The original row keeps its values; reversed_by is a back-pointer written in the same transaction so a reader does not need a reverse scan to see that the row was undone.
- CHECK (qty >= 0) aborts the transaction and surfaces a real defect. GREATEST(qty + :delta, 0) writes a plausible number instead, so the missing receipt that caused the negative is never investigated and the first symptom is a picker at an empty location.
Expected result (20 min): One movement row per idempotency key, a position equal to the signed sum of its movements, and a corrected pick represented as three rows (original, reversal, re-pick) with no row ever updated except reversed_by and the position aggregate.
Check yourself
- SUM(delta_qty) per (item_id, node_id, lot_id, state) equals inventory_position.qty after the double-send and after the correction.
- Drop the UNIQUE constraint and confirm the double-send test now fails; if it still passes, the retry is not actually racing.
- Force a negative by replaying a pick against an empty position and confirm the transaction aborts rather than storing zero.
- Query the history of the mis-keyed pick and confirm the original quantity and its reason code are still readable.
Follow-ups
- Two retries of the same pick arrive concurrently on different application instances. Walk through both transactions statement by statement and say which one commits a position change.
- occurred_at from a handheld is five minutes ahead of recorded_at because the device clock drifted. Which of your queries break, and which column should each of them actually be using?
- How long do you keep idempotency_key unique for, given the table grows by tens of millions of rows a month?
Stop two allocators promising the same unit of stock
inventory_position is keyed (item_id, node_id, lot_id, state) and holds qty BIGINT, which is on-hand stock and is moved only by the movement ledger; available_qty BIGINT, maintained on the same row; and version. The declared invariant is available_qty = qty - SUM(allocation.qty) over that key in status held or committed, enforced by CHECK (available_qty >= 0) and CHECK (available_qty <= qty). allocation holds allocation_id, order_line_id, item_id, node_id, lot_id, qty, tier, status (held, committed, consumed, released, expired) and expires_at. Twelve allocator threads try to claim the last eight units of one item at one node inside the same 50 ms. Give the exact statements that let at most eight units be claimed, then say what changes if identical code runs under PostgreSQL REPEATABLE READ versus InnoDB REPEATABLE READ, and what a retried allocate for the same order line must do.
How a strong candidate works it
- Collapse the check and the write into one statement whose WHERE clause is the business rule: UPDATE inventory_position SET available_qty = available_qty - :q, version = version + 1 WHERE item_id = :i AND node_id = :n AND lot_id = :l AND state = 'on_hand' AND available_qty >= :q. qty is deliberately untouched: a hold is a promise, not a physical move, so only a movement may change on-hand and the ledger-equals-position invariant survives. Rowcount zero is a refusal, not an error, and the allocation row is inserted in the same transaction only when the rowcount is one.
- If the deployment carries no available_qty column and availability must be derived, the alternative is SELECT ... FOR UPDATE on the position row followed by the SUM over held and committed allocations inside the same transaction. That is correct but holds the row lock for two statements instead of one, which matters on a key taking hundreds of writes a second.
- Isolation is not a substitute for the atomic step, and it changes the error contract. Under PostgreSQL READ COMMITTED the losing UPDATE blocks, then re-evaluates its predicate against the newly committed row version and returns rowcount 0, which the handler already handles. Under PostgreSQL REPEATABLE READ the same statement instead raises SQLSTATE 40001 and the whole transaction must be retried, so a handler that only tests rowcount fails closed on an exception it never expected. InnoDB REPEATABLE READ does neither: a locking read or UPDATE performs a current read of the latest committed row, so the loser blocks and then sees rowcount 0, with a deadlock rather than a serialization failure as its exceptional case.
- Make the retry a no-op with a partial unique index: UNIQUE (order_line_id, node_id, lot_id) WHERE status IN ('held','committed'). Then a retried allocate conflicts instead of double-claiming, and the handler reads the existing allocation and returns it.
- Never source the availability number from a replica or a cache for this path. Replication lag is largest during the write burst that made the key hot, so the guarantee fails precisely when it is load-bearing.
- For the hottest item-node pairs, shed load without overselling: route allocations for that key through a single writer with a bounded queue and refuse with a conservative not-available when the queue is full. Splitting the position into N sub-rows cuts contention N-fold but strands the remainder across sub-rows and produces false out-of-stock when the last units are spread thin.
Follow-ups
- One order line needs two units from two different nodes, atomically. What do you do when those positions live in different shards and no distributed transaction is available?
- Soft-tier allocations expire. Show the sweeper statement, and say what stops it from releasing an allocation that is being committed at that instant.
- Under sustained contention your CAS retries. What is the retry budget, and what does the caller see when it is exhausted?
Trace a suspect lot forward to every shipped order line
lot_edge(parent_lot_id, child_lot_id) has 400 million rows with btree indexes on both (parent_lot_id, child_lot_id) and (child_lot_id, parent_lot_id). lot_shipment(lot_id, order_line_id) maps lots to shipped lines. Given one suspect received lot, return every downstream order_line_id. The reachable subgraph can reach 2 million lots and 12 levels; the full edge table does not fit in memory. Give the traversal, its complexity in terms of the reachable subgraph, and what you do if the data contains a cycle.
How a strong candidate works it
- Genealogy is a directed acyclic graph, not a tree: a production run consumes several lots and one lot splits across many shipments, so a node is reached by several paths and a tree traversal revisits it combinatorially. Breadth-first from the suspect lot over the
(parent_lot_id, ...)index, with a hash set of visited lot ids, is O(V' + E') where V' and E' count only the reachable subgraph — 2 million 8-byte ids is 16 MB of payload and a few tens of MB with open-addressing overhead, which is why the visited set is affordable and the edge table's 400 million rows never enter memory. - Batch each level into one indexed read. Issue
WHERE parent_lot_id = ANY($1)with a few thousand parents per call instead of a query per node: at 12 levels and 2 million nodes, per-node round trips dominate the wall clock entirely, and the recall's deadline is a real-world response time, not a query-plan aesthetic. The batch size is the tuning knob between round-trip count and per-statement planning cost. - Keep the visited set even though the schema says DAG. Rework and repack loops — material returned into an earlier lot — do occur, and an unguarded traversal on a cyclic edge set does not terminate; a set membership test is O(1) and turns a hang into a correct answer plus a data-quality alert. Also cap the traversal by level and by node count, and fail loudly on the cap rather than returning a partial set silently.
- Join to
lot_shipmentonce at the end over every visited lot id, not over the leaves and not per node. A lot is routinely both shipped and consumed: half a lot goes out on an order line while the other half is repacked into a child lot, so it has alot_shipmentrow and outgoing edges at the same time and is not a leaf. Restricting the join to nodes with no outgoing edges drops exactly those order lines, which is under-recall — the one failure mode this trace is not allowed to have. One bulk join against an index onlot_idover the whole visited set is a single pass; per-node lookups multiply the round-trip problem by the number of visited lots. - State the semantics honestly: reachability answers which shipped lines could contain material from the suspect lot, and without propagating edge quantities it is a superset of physical containment. For a recall that is the correct direction of error — over-recall, never under-recall — and saying so is the point, because a trace that returns quickly and incompletely will be believed. The upstream question, which received lots are in a suspect shipped unit, is the same traversal over the reverse index, which is why both indexes exist.
Expected result (30 min): Every lot downstream of the suspect appears exactly once in the visited set; the merge node is visited once, not twice; the partly-shipped interior lot contributes its order line even though it has children; the cyclic fixture terminates with the same reachable set plus a flagged edge; the query count is levels + 1, not proportional to node count.
Check yourself
- The reachable set is a superset of the set you computed by hand, and equal to it on the acyclic fixture.
- Restricting the final join to lots with no outgoing edges drops the partly-shipped interior lot's order line — the failing test that pins the over-recall rule to code.
- Running the same traversal over the reverse index from a leaf returns the suspect lot.
- Removing the visited set makes the diamond fixture return duplicates — a failing test that documents why the set is there.
Follow-ups
- A recursive CTE would express this in ten lines. What is its cost here if the index on the traversal column is missing, and how would you notice?
- You need the quantity of suspect material in each downstream line, not just reachability. What has to be on the edge, and what does that do to the traversal?
- The traversal must run while a production run is writing new edges. What does a consistent answer mean here, and what do you cut it at?
Paginate an append-only movement ledger under continuous writes
A partner's nightly reconciliation job pulls every inventory_movement row for its nodes since its last run, while the table is being appended to throughout. The columns available are movement_id BIGINT (sequence-assigned), node_id, occurred_at (device-reported) and recorded_at (insert time). Specify the cursor format, the ordering key, the page-size policy and the delivery guarantee you promise across job restarts. State explicitly what happens to a row inserted today whose occurred_at is eighteen hours in the past, and how the client resumes after a crash mid-page.
How a strong candidate works it
- Use a keyset cursor, not OFFSET: WHERE node_id = ANY($1) AND movement_id > $2 ORDER BY movement_id LIMIT n, served by an index on (node_id, movement_id). Each page costs O(log n + k); OFFSET re-walks the skipped prefix at O(offset + k), so page 900 of a nightly pull is the page that times out.
- Order by insert order, never by occurred_at. A handheld that reconnects after a partition inserts movements stamped hours earlier; a cursor advanced past that instant will never return them. occurred_at is a filter and a display field, recorded_at and movement_id are the feed order - conflating the two is how a reconciliation silently misses a day of picks.
- Name the sequence-gap hazard and close it. The sequence hands out movement_id at INSERT, but transactions do not commit in id order, so a reader that takes max(id) from a page can step over a row that commits afterwards with a lower id. Either hold the cursor behind a watermark older than the longest writer transaction (bounded by statement_timeout plus idle_in_transaction_session_timeout) or read a commit-ordered source such as logical decoding or a transactional outbox, and say which you chose.
- Make the cursor opaque - encode the ordering key plus a schema tag - so you can change the key later without a coordinated client release. Return next_cursor and has_more; do not return a total count, which is a second full scan and is stale before it is serialised.
- State the guarantee honestly: at-least-once across restarts, because a client that crashes after processing a page and before persisting the cursor will re-request it. Delivery is idempotent on the partner side through movement_id, and exactly-once over a network is not on offer.
Expected result (20 min): A keyset cursor over movement_id backed by an index on (node_id, movement_id), a stated lag or commit-ordered source that makes a skipped row impossible, an explicit at-least-once guarantee, and occurred_at excluded from the ordering key.
Check yourself
- A full pass taken while inserts continue returns every committed row at least once and skips none.
- A row inserted with occurred_at eighteen hours old still appears in a later page.
- p95 page latency is flat in page number rather than growing with it.
Follow-ups
- The partner asks for 10,000-row pages and still times out. What do you change before raising the limit?
- They ask for a changes-since-timestamp parameter instead of a cursor. What breaks, and what do you offer instead?
- They need to re-read a three-day window to settle a dispute. How does that coexist with the cursor contract?
Orchestrator memory climbs a gigabyte a day at flat throughput
The fulfilment orchestrator's resident memory rises about 900 MB a day and old-generation occupancy after each full collection rises monotonically; a restart resets both. Transition throughput and latency are unchanged, and saga state itself lives in the database. The process keeps an in-heap timer wheel with one timeout entry per forward step of each order line, and lines legitimately sit in one state for days. Diagnose in order, prove the hypothesis with arithmetic before changing code, and give the fix.
How a strong candidate works it
- Distinguish a leak from a large working set first, because the remedies are opposite. Plot occupancy immediately after each full collection, not resident memory: a monotone rise across days is retention, while a rise that plateaus is a cache doing its job. Live sagas legitimately number in the millions here, so the working set is genuinely large and the question is only whether it is bounded.
- Diff two heap dumps taken two hours apart by retained size per dominator rather than guessing at the suspect. The growth will show as timer entries that transitively pin whole saga objects; the reference path from the GC root through the wheel's bucket array to the entry to the saga is what names the owner, and without that path you are guessing.
- Prove the hypothesis numerically before touching code. 900 MB a day divided by roughly 1.5 KB retained per entry is about 600,000 entries a day, near seven a second. Compare that to entries created per second minus entries removed per second. If the rates do not match within an order of magnitude, the dominator is something else and the timer wheel is a bystander.
- Name the defect precisely: the entry is registered when a step starts and is only released when its timeout fires, so a step that completes early leaves an orphan that survives until its deadline, and a step whose line is cancelled or re-sourced leaves one that may never be cleaned at all. Age cannot be used to identify orphans, because a line sitting in one state for days is normal.
- Fix by cancelling on transition, keyed by (order_line_id, step), so the entry is removed by the same code path that advances the saga. Then move the timer out of the heap entirely: persist due_at on the saga row and let a sweeper poll WHERE due_at < now() AND status IN (open states) behind an index on due_at partial to those states. That bounds memory by the poll batch instead of by open sagas, and fixes the separate bug that an in-heap wheel silently loses every pending timeout on restart or deploy.
- Add a bound so the next version of this is loud. Cap the in-flight timer structure, emit a metric on entries created versus removed, and alert on the difference rather than on memory, which only moves after the damage is done.
Follow-ups
- The sweeper now polls millions of open sagas. What does the index look like and how do you keep the poll from scanning the whole table?
- Two orchestrator instances poll the same due sagas. How do you stop both from firing the same timeout?
- How would you have detected the lost-timeouts-on-restart bug, given that it produced no error and no memory symptom?
Turn an implicit fail-open availability answer into a decision
When the inventory service is unreachable, the availability client returns the last value it cached, because a broad except block around the call does that by accident. Nobody chose it. During a 40-minute outage the storefront kept promising stock for items that had sold out, and 900 lines short-shipped. Describe a time you converted an implicit behaviour into an explicit decision: how you surfaced it, the two directions the error can go and what each costs, what you actually chose for the checkout path versus a browse page, and where the choice is now written down.
How a strong candidate works it
- Surface the behaviour as a fact before arguing about it: the except block is a policy, it was never chosen, and it currently applies identically to a browse page and to the promise made at checkout. Framing it as an unowned decision rather than as someone's bug is what gets it looked at.
- Price both error directions with the operations they trigger, since neither is free. Promising stock you do not have produces a short-ship, a customer contact, a re-source or refund and, on the floor, a picker sent to an empty location; refusing stock you do have loses the order outright. Both are real, the second is cheaper to reverse, and that asymmetry is the argument.
- Split the decision by path instead of choosing one policy for the system. A browse page can serve a stale value with a staleness label and be right most of the time; a checkout promise should degrade to a conservative answer, which is not zero but the last known position reduced by an in-flight allowance sized from the recent allocation rate for that key. Applying one policy to both paths is the actual defect.
- Bound the staleness, because a cached value with no age is the same bug wearing a different hat. Serve the cached number while it is under a stated age, then switch to the conservative answer, then refuse; the thresholds need owners and should appear on a dashboard as the share of reads answered from each tier.
- Write it down where the next person meets it: a short decision record giving the behaviour, the two costs, the chosen policy per path and the date, plus a test that asserts the checkout client cannot serve an unlabelled stale value. A decision that lives only in a thread gets reverted by the next broad except block.
- Report what the change actually cost. If the conservative answer suppressed some orders that would have been fillable, say how many, because a degradation policy that was never measured is indistinguishable from one that was never enforced.
Follow-ups
- How do you size the in-flight allowance, and what happens to it during a promotion on that item?
- The outage is 4 hours rather than 40 minutes. Does your policy still hold, and what changes at which threshold?
- Someone adds a new consumer of the availability client next quarter. What stops them inheriting the wrong policy by default?
Where candidates lose points
Fixing only the syntax errors in the C++ history-buffer debugging question
The reported prompt asks for memory leaks, race conditions and syntax errors, so a clean compile is only the first of three passes. Work in order. Fix what stops compilation. Then check ownership: every new needs a delete on every path, so replace raw allocations with RAII members, a std::vector or std::unique_ptr. Then check concurrency: find each piece of shared state, such as the head index, the size and the backing storage, and state which lock protects it or why it is atomic. Write down the buffer's invariant (head, size and capacity, and what full versus empty means) and test wraparound, reading from an empty buffer and writing to a full one.
Designing the fleet telematics pipeline as if every vehicle is always online
Vehicles upload over cellular links that come and go, so a design that assumes a live connection breaks as soon as a vehicle loses signal. Start with durable buffering on the vehicle and a rule for what uploads first when bandwidth is short, for example safety events before bulk sensor logs. Key uploads by vehicle and sequence number so retries are idempotent, and state whether delivery is at-least-once. Treat OTA updates as a separate downstream path with staged rollout and rollback. A design that says only 'Kafka to a data lake' leaves buffering, priority, idempotency and rollout unanswered.
Giving the stopping-distance formula without stating the model behind it
The reported kinematics question asks for stopping distance and a braking threshold given speed and a friction coefficient. Before you calculate, state the model: constant deceleration of mu times g, a flat road, and whether reaction or system latency is included. Derive d = v^2 / (2 mu g), add v times t for latency if it counts, and plug in real numbers to check the answer is plausible. Ask what 'braking threshold' means in this prompt instead of guessing. Then say what the model ignores, such as slope, a wet surface, and braking force that ramps up rather than applying instantly.
Starting to code an open-ended grid or graph prompt before defining what a correct answer is
Candidates report open-ended coding prompts, such as filling a grid with contiguous token groups using multi-source BFS, that leave key details unstated. Before you code, ask about grid dimensions, whether every cell must be filled, what 'random' has to guarantee, and what happens when there are more token types than cells. Say how you will check contiguity. Then write complete code and test a 1x1 grid, a grid with a single token type and an unreachable region. If you have spent the time explaining the approach and left the code unfinished, you have not answered the question.
Answering the trajectory-prediction loss question with terminology instead of a trade-off
A regression loss such as L2 on future positions pulls the prediction toward the mean. When a road agent could plausibly turn left or go straight, the mean is a path between the two that nobody would take. Classification over a set of candidate trajectories or anchors can represent both modes and assign each a probability, but it depends on a good anchor set and usually needs a regression step to refine the chosen anchor. State that trade-off directly, then say how you would evaluate each option, for example error on the best of k predictions versus error on the single top prediction.
Weakness triage, driven by a day-one self-assessment
Day one measures instead of guessing, under a fixed rubric, and the remaining hours are allocated in proportion to the gaps before any studying begins. The allocation is deliberately not renegotiated midweek, because the area that feels worst on day three is usually the one that is moving.
Day 1 — Scope the loop and take a coding baseline
- Write the questions for your recruiter call: which area the loop targets (onboard, ML or infrastructure), which coding language is expected, and whether a domain knowledge round is included
- Solve one graph problem from a blank editor in the language you will interview in, such as Shortest Path in Cost Maze, saying each step aloud and recording yourself
- Score the attempt on three things: did you clarify before coding, did it run correctly on the first try, and did you name the edge cases unprompted
Deliverable: A written list of recruiter questions and a scored baseline attempt, with the weakest of the three things circled.
Day 2 — Graph and grid traversal for the phone screen
- Write BFS, multi-source BFS, DFS and Dijkstra from memory, then solve Find Shortest Knight Path and Find the Best Meeting Point on a Grid
- Attempt the reported token-grid prompt: fill a grid with contiguous token groups using multi-source BFS, writing out your clarifying questions and contiguity check first
- Work through the worked coding exercise on level-batched BFS with a visited set, and reproduce its cycle and duplicate-visit tests on your own grid solution
Deliverable: Four traversal templates written from memory, plus a token-grid solution with its edge-case tests listed.
Day 3 — String dynamic programming and computational geometry
- Solve two string dynamic programming problems in the pattern-matching and alignment family (edit distance, then wildcard or regex matching), writing the recurrence and base cases before any code
- Implement a 2D cross product, an orientation test and Compute Convex Hull Boundary in C++, including collinear and duplicate points
- Write a brute-force nearest-neighbour search over a small point set, then explain aloud when a k-d tree or grid bucketing would replace it
Deliverable: Two dynamic programming recurrences with their base cases, and a convex hull implementation that passes the collinear-point case.
Day 4 — C++ debugging and code quality
- Write a deliberately buggy circular history buffer in C++ with a leak, an unguarded shared index and a compile error, swap it with a study partner, and fix theirs in three passes: compile, ownership, concurrency
- Solve Fix Racy Token Cache and state which operation needed to become atomic and why
- Read the debugging drill on the orchestrator memory leak and practise its method on your buffer: first confirm memory keeps growing, then find what holds the references, then check the growth rate with arithmetic before changing any code
Deliverable: A refactored buffer with its invariant written down and tests for wraparound, empty-read and full-write.
Day 5 — Fleet system design
- Design the reported telematics pipeline aloud, covering on-vehicle buffering, upload priority, idempotent ingestion, and OTA as a separate path with staged rollout
- Design Car Position Logging System and Design Reliable Command Delivery Between Autonomous Vehicles and the Cloud, stating the delivery guarantee in each
- Work through the worked design exercise on keyset pagination over an append-only log, and apply its ordering rule to a consumer replaying vehicle logs that arrive late
Deliverable: Three one-page design sketches, each naming its main failure mode and how the system recovers.
Day 6 — Domain questions: kinematics, ML and inference
- Derive stopping distance with and without reaction time, state every assumption, and check the result with real speeds and friction values
- Explain classification versus regression losses for trajectory prediction in two minutes, including the mode-averaging problem
- Sketch the reported distributed inference platform for bulk evaluation and an active learning loop, noting where batch throughput and interactive latency pull in opposite directions
Deliverable: A one-page domain sheet: the kinematics derivation, the loss trade-off, and the two pipeline sketches.
Day 7 — Behavioural stories and a full mock
- Prepare stories for the reported prompts: a complex project you led, framing an ambiguous problem, a design disagreement, and balancing feature speed against code health
- Write a one-page sheet per project with its scale, team size, timeline and key trade-off, so the numbers match wherever the project comes up
- Run a mock onsite with one coding, one design and one behavioural question, with the interviewer changing a constraint in the middle of each
Deliverable: Four behavioural stories each with a decision and an outcome, and notes from the mock listing anything you would still get wrong.
Frequently asked questions
Is this an official Waymo interview guide?
No. It is PracHub's own research and practice material for the Software Engineer role at Waymo. The rounds and questions reflect what candidates have reported, not a process Waymo has published, and they change over time. Confirm the current format and scope with your recruiter.
How hard are the Waymo Software Engineer coding interviews?
Candidate reports put them between LeetCode medium and hard. Reports stress more than getting the algorithm right: handling edge cases, writing clean code, using your language correctly (especially C++), and producing complete, working code within the time given. Practise until you can take a graph or dynamic programming problem from a blank editor to tested code without help.
Do I need robotics or autonomous vehicle experience?
According to candidate reports, it is not strictly required for general Software Engineer roles. Knowing perception, planning or kinematics helps, and candidates with strong CS fundamentals, distributed systems or machine learning backgrounds are reported to get through. If you lack domain experience, cover the basics in the reported domain questions (stopping distance, cross products, trajectory-prediction losses) so a domain question does not catch you cold.
Which programming languages can I use?
Candidates report that general coding rounds usually allow your choice of language, such as C++, Python or Java. For specialised roles such as onboard infrastructure or telematics, interviewers may require C++ to test memory management and systems knowledge. Ask your recruiter which applies to your loop, and if the answer is C++, practise RAII, smart pointers and standard containers.
How does team matching work?
Candidate reports describe a team matching phase after the virtual onsite. Recruiters connect you with hiring managers from teams such as Planner, Perception or ML Platform to talk through each team's remit and projects, and the offer process continues once both sides agree on a match. If you clear the onsite but do not match right away, stay in touch with your recruiter.
What does the domain knowledge round cover?
Candidates report it only for specific roles, focused on domain knowledge relevant to the position. Candidate reports do not tie specific questions to this round. Separately, reported questions in the domain and ML category include classification versus regression losses for trajectory prediction, geometric computation in C++ such as cross products and nearest-neighbour search, active learning pipelines, and vehicle stopping distance from speed and friction, and candidates report that domain questions can also come up in technical screens. Prepare them whether or not your loop includes this round, and ask your recruiter which area it covers.
How should I prepare for the C++ debugging question?
The reported prompt gives you a buggy C++ history buffer and asks you to find memory leaks, race conditions and syntax errors, then refactor it. Go through it in three passes: compile errors, then ownership (replace raw new and delete with RAII), then shared state under concurrency. Write down the buffer's invariant and test wraparound, reading when empty and writing when full.
Keep practising
- Waymo questions on PracHub — 65 Software Engineer questions in the bank.
- Software Engineer question bank — cross-company practice.
- Interview preparation framework
- All interview guides