As a Software Engineer at AURORA, you sit at the forefront of the autonomous vehicle revolution, helping deliver the self-driving technology known as the Aurora Driver. This high-impact role spans a wide array of mission-critical systems, from onboard low-latency C++ autonomy software and real-time motion planning to high-throughput data processing pipelines, web-based visualization platforms, and Hardware-in-the-Loop (HIL) simulation engines. Software engineers here build the foundation that allows 80,000-pound autonomous Class 8 trucks and passenger vehicles to safely navigate complex real-world road environments.
The technical scope at AURORA is vast and deeply integrated. Depending on your specialization, you might engineer high-frequency drivers that interface directly with LiDAR, Radar, and camera sensors; build distributed cloud frameworks that ingest and process petabytes of multimodal vehicle logs; or develop responsive full-stack tools in React and TypeScript that allow operators and safety managers to visualize vehicle telemetry in real time. Everyday engineering challenges demand an uncompromising focus on deterministic execution, low-latency performance, and strict safety compliance.
Working at AURORA means tackling foundational, unsolved problems in robotics, machine learning, and systems engineering. The software you write directly influences physical vehicle behavior, making software quality and fault management paramount. Whether you are optimizing continuous learning data curation pipelines or building virtual "flight simulators" for autonomous trucks, your contributions directly impact the safety, reliability, and commercial deployment of self-driving transportation.
Recruiter Phone Call
reportedThe person on this call usually cannot evaluate your code and does not need to. They write a short paragraph, and that paragraph is what a hiring manager skims when deciding who to put on your loop. So the test is not whether your work was hard, it is whether a non-engineer can repeat it correctly. Name systems by what they did rather than by their internal codename, give each project a shape (what was breaking, what you changed, what happened after), and keep the whole walkthrough near ninety seconds. Depth that cannot survive a paraphrase reads as vagueness.
What to demonstrate
- Whether a non-engineer can restate your projects without distorting them, since their paraphrase is what travels to the hiring manager, not your sentences
- Whether each project has a shape rather than a stack list: the failure or constraint, the change you made, the result and how it was measured
- Whether you can say what was yours inside a team project without either inflating it or disappearing into the plural
How to prepare
- Rewrite each headline project as two sentences with no internal system names and no acronyms outside your company, then say them to someone outside engineering and have them repeat them back. Fix whatever came back wrong
- Attach one measured number to each project: the baseline, the change, and the window it was measured over. Where nothing was ever measured, say that plainly rather than reaching for a plausible percentage
- Time the background walkthrough against a clock. If it runs past two minutes, compress the earliest role to a single clause and spend the recovered time on the most recent one
Technical Screening
reportedThe same problem is scored by two different mechanisms depending on the format, and preparing for one does not cover the other. With a person watching, partial progress is visible and a hint is a correction you can absorb; silence is the expensive failure, because nobody can read a half-written function. With an automated grader there is no partial credit for what you were about to do, nobody to ask, and the worked examples in the prompt are the entire specification. Read them as a contract, down to whether an empty result should be an empty list or no output at all.
What to demonstrate
- In a live session, whether your commentary tracks what your hands are doing, and whether a hint redirects you or gets defended against
- In an automated one, whether you cover the cases the examples do not show, since the hidden cases are where the score moves
- Whether you manage the clock on purpose: abandoning an approach that is not converging while there is still time to write something simpler that finishes
How to prepare
- Have someone hand you a problem and feed you one deliberately wrong hint. Practise testing it against a concrete case instead of accepting or rejecting it on authority.
- Do one timed run a week in a plain browser editor with autocomplete, linting and your own snippets switched off, which is closer to what these environments give you
- For the automated format, write the harness before the solution: a main that feeds the worked examples plus an empty and a single-element case and prints expected against actual, so a wrong submission is caught by you first
Virtual Onsite Evaluation
reportedNobody in the room with you decides this. Interviewers typically write their rounds up separately, often before seeing anyone else's, and the outcome is settled later from those write-ups. A split panel gets resolved by whichever note carries specific evidence, so what you want out of each room is one concrete thing that person could write down: a bug you caught yourself, a trade-off you named, a decision you owned. The rest is arithmetic. The project you describe in a behavioural conversation is often the same system you sketched an hour earlier, and the two accounts have to agree.
What to demonstrate
- Whether the scale, team size and timeline you attach to a project hold steady when that project resurfaces in a different round
- Whether each interviewer leaves with a specific thing to cite rather than a general impression of competence
- Whether a trade-off you defended in one round survives a challenge in another, instead of being quietly swapped for the answer the new interviewer seemed to want
- Whether a question you have already answered earlier in the day gets the same answer at the same depth, without visible impatience
How to prepare
- Write a one-page sheet per project fixing the figures you will quote — request volume, data size, team size, elapsed time, what broke — and say them aloud from the sheet until they come out identical every time
- For each round on the schedule, decide in advance the one sentence you want in that person's notes, then check in a mock that you said it outright instead of leaving it to be inferred
- Have someone ask you the same project question twice, an hour apart, and diff the two answers for numbers that moved or a trade-off that reversed
PracHub editorial advice for the preparation topics above.
Retrying an irreversible external call without an owned idempotency key
Timeouts and connection resets are ambiguous by construction - the partner may have committed before the response was lost - so a generic retry policy around an HTTP client is, in this domain, a machine for buying two labels and dispatching two trucks. Relying on the partner's deduplication is not a substitute, because their window is usually short, their key is often derived from fields you may legitimately change on retry, and many older integrations have no such concept at all. The workable pattern is to generate the key yourself, persist it with an explicit unknown status before the call, and resolve ambiguity by querying the partner for that key rather than re-issuing; the sweeper that does this is the component that has to be correct, not the call site. Teams get this right for payments and then forget that a carrier tender, a warehouse work release and an EDI shipping notice have the same shape.
Holding a database transaction open across a physical or third-party operation
It is natural to open a transaction, lock the position, call the rating or tendering API, and commit on the response, and it works perfectly until the partner's p99 goes from 200 milliseconds to thirty seconds. At that point every request holding a lock on a hot item-node pair queues behind it, the connection pool fills with transactions that are waiting on the network rather than on the database, and an unrelated service sharing the pool fails at the same moment. In this domain the effect is amplified because demand concentrates on a few hot rows during a promotion or a seasonal peak, exactly when partner latency is also degraded. The structural fix is to keep transactions short and local - commit the state change together with an outbox row, let a relay perform the external call, and reconcile asynchronously - accepting at-least-once delivery and making the effect idempotent rather than trying to stretch a transaction over something the database cannot roll back.
Quoting amortised or average cost as if it were a worst-case guarantee
Appending to a dynamic array is amortised O(1), but the append that triggers a resize copies every element, and hash lookup is constant only while the hash spreads the actual keys. Say which guarantee you are offering when the caller cares about the latency of one call rather than the total over many.
Issuing one query per row of a result set
Fetch related rows in a single batched query keyed by the ids you already hold, or join them into the original query. A per-row round trip multiplies network latency by the row count, and it looks perfectly fine against the ten rows in your development database.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
How would you debug a subtle multi-threading deadlock in a real-time L…
How would you debug a subtle multi-threading deadlock in a real-time Linux embedded environment?
Approach
- Choose the data structure from the access pattern, not from familiarity.
- Walk one small example through your approach before writing the whole thing.
- Name the brute-force solution and its complexity before improving on it.
Follow-up
- How does this change if the input no longer fits in memory?
- Which test case would catch an off-by-one here?
Design an asynchronous mechanism using JavaScript Promises to manage r…
Design an asynchronous mechanism using JavaScript Promises to manage rate-limited network requests in a web application.
Approach
- Restate the input: its shape, its size, and what is guaranteed about it.
- State the target complexity and say which constraint rules the naive version out.
- Walk one small example through your approach before writing the whole thing.
Follow-up
- How does this change if the input no longer fits in memory?
- What is the worst case, and how likely is it on real data?
Given an array of coordinates, find the optimal trajectory path while …
Given an array of coordinates, find the optimal trajectory path while avoiding dynamic obstacle bounding boxes.
Approach
- Restate the input: its shape, its size, and what is guaranteed about it.
- State the target complexity and say which constraint rules the naive version out.
- Choose the data structure from the access pattern, not from familiarity.
Follow-up
- How does this change if the input no longer fits in memory?
- Which test case would catch an off-by-one here?
Split an order across the fewest nodes that can fill it
A fulfilment order has L lines, L up to 25. After filtering by capability, service time and stock, N candidate nodes remain; N is usually under 14 but can reach 200. Each node covers a known subset of the lines in full. Return a minimum-size set of nodes covering every line, tie-broken by total distance. Give an exact algorithm for the small case with its exact cost in operations and bytes, say precisely why enumerating node subsets stops working, and give what you ship when N is 200.
Approach
- Name it: this is minimum-cardinality set cover, which is NP-hard, so the useful move is choosing which parameter you are exponential in rather than hunting for a polynomial exact algorithm. Enumerating subsets of nodes is 2^N and correct — at N = 14 that is 16,384 subsets and finishes instantly, which is exactly why it survives review and then fails in production at N = 200, where 2^200 is about 1.6e60.
- Be exponential in L instead, because L is the parameter you bound. Index the DP by the set of lines still uncovered:
dp[rem]is the cheapest way to cover the lines inrem, withdp[0] = (0, 0)and the value a(node_count, total_distance)pair compared lexicographically. Letbbe the lowest set bit ofrem; any cover must contain some node that stocks lineb, so the transition ranges only over those nodes:dp[rem] = min over v with b in cover(v) of dp[rem & ~cover(v)] + (1, dist(v)). Worst case O(2^L * N) — at L = 25 and N = 14 that is 33,554,432 states times 14, about 4.7e8 transitions — and the lowest-bit restriction cuts the real branching factor to the nodes stocking one specific line. - Quote the memory for what you actually store, not for a count array alone. Over 2^25 masks you need three dense arrays:
uint8node count (33.5 MB),uint32total distance (134.2 MB, integer metres so the tie-break compares exactly and the array stays fixed-width), anduint8chosen node (33.5 MB, since N <= 200 fits a byte) — about 201 MB, roughly six times the 33.5 MB that a bare count array would suggest. The same layout at L = 20 is 6.3 MB, which is why this is comfortable up to about 20 lines and needs a deliberate decision at 25. - Reconstruction is free in this formulation and needs no parent-mask array: because every transition eliminates the lowest uncovered line, the predecessor of
remunder the stored choicevis exactlyrem & ~cover(v), so walkremfrom the full mask down to 0 emittingchoice[rem]. If the reachable state count is far below 2^L — coverage sets are usually highly correlated — a top-down memoized recursion over a hash map trades the 201 MB for roughly 40-60 bytes per visited state; measure the visited count first, because a hash map that ends up touching most masks is strictly worse than the dense arrays. Add a branch-and-bound cut ofceil(popcount(rem) / max_v popcount(cover(v) & rem)), which prunes long before the theoretical bound bites. - When exact is out of reach, ship greedy with a stated guarantee: repeatedly take the node covering the most currently-uncovered lines, using bitset popcounts, O(N * ceil(L/64)) word operations per pick and at most min(N, L) picks. Greedy is within H(L) <= ln L + 1 of optimal (Chvatal), and no polynomial algorithm achieves (1 - o(1)) ln L unless P = NP (Dinur and Steurer, 2014) — so effort belongs in the pre-filter and the tie-break, not in a cleverer heuristic. At L = 25 the bound is H(25), about 3.82x worst case, while the realistic gap on correlated coverage sets is zero or one node.
- Close on the objective, because fewest nodes is not cheapest. Two shipments from distant nodes routinely cost more than three from near ones, and a split also costs a second box, a second carrier pickup and a worse customer experience. If cost is the real objective it is weighted set cover — greedy by cost divided by newly-covered lines, same logarithmic bound — and if a node can cover a line only partially, the line mask is no longer sufficient state, since you must track remaining quantity per line; that is the point at which you stop hand-rolling and either restrict splits or hand an MILP to a solver with a time budget.
Worked solution 40 min
- Write
cover(v)as an L-bit mask per node and confirm that the union of all masks equals the full mask, otherwise the order is unfillable and the answer is a backorder, not a cover. - Implement the DP over uncovered masks with the lowest-uncovered-line transition, the
(node_count, total_distance)value and thechoicearray, then measure resident memory and wall time at L = 20 and L = 25 against the quoted 6.3 MB and 201 MB. - Implement greedy separately and run both on 10,000 random instances with L = 12, N = 10, comparing sizes.
- Construct the textbook instance where greedy is strictly worse than optimal — nested sets of geometrically decreasing size plus two disjoint halves — and confirm your greedy reproduces the gap.
- Add the distance tie-break to both and confirm the exact path returns the cheapest minimum-size cover, not merely a minimum-size one.
Follow-up
- The pre-filter is what keeps N small. What is in it, and what happens to your exact path the day someone loosens it?
- One node can cover a line only partially. Show where the bitmask formulation breaks and what state replaces it.
- You have 40 milliseconds inside a checkout call. Which of these runs there, and what runs asynchronously afterwards?
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.
Approach
- 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.
Worked solution 20 min
- Write the two CREATE TABLE statements including UNIQUE (idempotency_key), the composite primary key on the position, CHECK (qty >= 0) and lot_id NOT NULL DEFAULT 0.
- Write the handler as one transaction: the ON CONFLICT DO NOTHING RETURNING insert, the rowcount branch, then the versioned conditional UPDATE with its own rowcount test.
- Send the same pick twice with one idempotency key and assert one movement row and a position that moved by delta once, not twice.
- Apply the lot correction as two new movements and show that summing delta_qty per lot now matches the shelf while both the wrong and the right lot retain a legible history.
Follow-up
- 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?
Decide whether available-to-promise is derived or stored
Available to promise at one node equals inventory_position.qty in state on_hand minus the sum of allocation.qty in status held or committed for that key. The read sits inside checkout: 4,000 reads per second at peak, p99 budget 20 ms. inventory_position holds 40 million rows; during a promotion a single hot item-node pair carries about 2,000 open allocations. Decide whether to compute this per read or to maintain available_qty on the position row. Give both paths, the failure mode of each, and what the browse read may do that the allocate read may not.
Approach
- Cost the derived read in tuples, not in plans. One index probe into the position is trivial; the aggregate over allocation touches one tuple per open claim on that key. Even if only a tenth of peak lands on the hot pair, 400 reads per second times 2,000 rows is 800,000 index tuples per second for one item at one node, and it gets worse exactly as the key gets hotter. A partial index on status IN ('held','committed') does not help, because the row count is the cost, not the selectivity.
- Maintaining available_qty on the position row is nearly free on the contention axis, which is the point most candidates miss. The allocator already takes a row lock or a compare-and-set on that same position row, so the extra column costs no lock that was not already held, and the read collapses to a single primary-key probe well inside 20 ms.
- It is not free on the correctness axis. available_qty becomes a second derived value that can drift, so it joins qty under the same reconciliation job with its own re-sum against the allocation table, and a drift alert on that comparison is the operational price of the read speed.
- Split the two reads rather than arguing about one. The browse read may serve from a replica or a short-TTL cache and must err low, showing less than is there. The allocate read must be on the primary and inside the same transaction as the conditional update, because replication lag peaks during the write burst that made the key hot. Conflating them is what makes a replica read look safe in a benchmark.
- Pick the TTL against the drain rate, with a number. If a promotion burns 2,000 units in a minute, a 30-second cache is a thousand units stale, so the displayed figure should be a banded signal such as in stock or low stock rather than an exact count that the allocate step will then contradict.
- Do not denormalise availability across nodes into a single network number. A yes-somewhere answer cannot be allocated against, so sourcing will disagree with the page, and the customer sees a promise retracted after checkout rather than an honest out-of-stock before it.
Follow-up
- Roll this out on a live system. How do both paths run side by side, what do you compare, and what result gates the cut-over?
- The reconciliation job finds available_qty drifted by 3 on one key. What do you do, and what must you not do?
- Lot-tracked items multiply the position rows per item-node. Does your answer change, and where does the read now spend its time?
Design a high-availability user authentication and route protection sy…
Design a high-availability user authentication and route protection system for internal fleet operations tools using MongoDB and modern backend APIs.
Approach
- Name the read and write paths separately; they rarely have the same bottleneck.
- State the consistency you need, and where you are willing to be stale.
- Choose a partition key and say what query it makes expensive.
Follow-up
- What breaks first when traffic grows ten times?
- How does this behave when that dependency is down for an hour?
Architect a Hardware-in-the-Loop (HIL) testing platform capable of run…
Architect a Hardware-in-the-Loop (HIL) testing platform capable of running continuous simulation suites for an autonomous fleet.
Approach
- Name the failure you are designing for, then the recovery path.
- Choose a partition key and say what query it makes expensive.
- Name the read and write paths separately; they rarely have the same bottleneck.
Follow-up
- What breaks first when traffic grows ten times?
- What would you drop to keep the system up under load?
Design a 3D geospatial mapping orchestration engine that manages versi…
Design a 3D geospatial mapping orchestration engine that manages versioning, validation, and live updates across an autonomous fleet.
Approach
- Choose a partition key and say what query it makes expensive.
- State the consistency you need, and where you are willing to be stale.
- Name the read and write paths separately; they rarely have the same bottleneck.
Follow-up
- What breaks first when traffic grows ten times?
- What would you drop to keep the system up under load?
How do hardware-in-the-loop environments simulate sensor failure injec…
How do hardware-in-the-loop environments simulate sensor failure injections without crashing the physical compute unit?
Approach
- Say who the caller is and what they do when the call fails halfway.
- Define the identity of a request so a retry cannot double-apply it.
- Design the error taxonomy before the success shape; callers branch on it.
Follow-up
- How does a client discover it is on an old version of this contract?
- What does a partial failure look like to the caller?
Explain how you would interface with a high-bandwidth camera or LiDAR …
Explain how you would interface with a high-bandwidth camera or LiDAR sensor over an onboard high-speed Ethernet backbone.
Approach
- State how the contract changes without breaking existing clients.
- Design the error taxonomy before the success shape; callers branch on it.
- Separate accepted, pending, failed and confirmed; they are different facts.
Follow-up
- How does a client discover it is on an old version of this contract?
- What does a partial failure look like to the caller?
Available-to-promise reads for a cart across candidate nodes
The checkout page asks for availability of up to 20 lines against up to 30 candidate nodes, 3,000 times per second at peak, inside a 25 ms p99 budget. Availability at a node is that node's on_hand position minus the outstanding held and committed allocations against it. Design the read path: what is cached, keyed how, invalidated by what, and what the service returns when the inventory primary is unreachable. State which direction the error must go and why.
Approach
- Size the fan-out before choosing anything: 20 lines by 30 nodes is 600 (item, node) pairs per call and 1.8 million pair reads per second. Per-pair round trips are ruled out by tail amplification alone. If each lookup exceeds 20 ms one time in a hundred, the chance at least one of 600 does is 1 - 0.99^600, about 99.8%, so the call's p99 becomes the slowest of 600. One batched multi-get, or one query with a composite IN list, per call.
- Fix the cache entry: key (item_id, node_id), value {on_hand_qty, outstanding_alloc_qty, position_version}. Availability is computed at read time from those two numbers, so a movement and an allocation status change invalidate the same key rather than two different ones.
- Invalidate by version, not by TTL alone. The Availability Service publishes (key, version) on every committed write; the cache applies it as a compare-and-set that discards a version lower than the one held. That makes out-of-order invalidations safe, and a lost invalidation message costs staleness bounded by a 1-2 second backstop TTL instead of permanent divergence.
- State the contract out loud: this path is advisory. The authoritative check is the conditional allocation write against the position row. Cache staleness therefore sets the rate of promise-then-retry, not the rate of overselling, and that is the whole reason a cache is permissible in front of inventory at all.
- Degrade deliberately. With the primary unreachable, serve last-known values minus a safety buffer and collapse the response to a coarse bucket (in stock / low / out) so no surface can quote an exact count from stale data. Err toward under-promising: the cost is a lost sale, against a short ship, a re-source and a second shipment's freight on the other side.
Worked solution 25 min
- Compute the per-call fan-out and the resulting pair-read rate, then decide batched versus per-pair from the tail-amplification arithmetic rather than from taste.
- Write the cache entry layout and key, and name the exactly two events that must invalidate it: any movement on that position, and any allocation entering or leaving held/committed.
- Write the invalidation message shape and the compare-and-set rule that makes an out-of-order invalidation harmless.
- Write the degraded response shape, the safety-buffer rule, and one sentence stating the direction of error and its cost on each side.
Follow-up
- A promoted item invalidates its 30 node keys every 50 ms. What stops the invalidation stream from evicting entries faster than they can be refilled, and what does a read do on a miss for that key?
- The candidate node set spans two regions. Does the read cross the region boundary, and what does that do to the 25 ms budget?
- How do you measure whether the safety buffer is set correctly, rather than guessing it?
Allocation p99 hits nine seconds while database CPU stays flat
At 14:10 allocation p99 went from 30 ms to 9 s and an unrelated read service sharing the connection pool began failing. Database CPU and mean statement time are unchanged. pg_stat_activity shows 80 of 100 connections in state 'idle in transaction' with a mean transaction age of 18 s, and lock waits concentrated on about 30 inventory_position rows. In the same minute a carrier's rate-quote p99 went from 180 ms to 22 s. Give the ordered diagnosis, the immediate mitigation, and the structural fix.
Approach
- Ask whether the database is working or waiting, because the two look the same from the client. Flat CPU and flat mean statement time with most connections in 'idle in transaction' says the database is idle inside open transactions: the application has begun a transaction and gone off to do something else. That state name is the whole diagnosis and it points at application structure, not at the database.
- Tie the holders to the dependency rather than assuming the link. Compare the distribution of now() minus xact_start against the carrier's latency histogram in the same window; when the two track each other, the transaction is wrapping the external call. The 18 s mean transaction age against a 22 s partner p99 is that match.
- Locate the contention: join pg_locks to pg_stat_activity, group by the locked tuple, and confirm the waits concentrate on a few dozen hot (item_id, node_id) keys. Demand concentrates on those rows precisely during a promotion, which is also when partner latency degrades, so the two curves multiply rather than add.
- Mitigate without a deploy where possible. Set a call timeout on the partner shorter than the lock budget, set idle_in_transaction_session_timeout and lock_timeout so a stall sheds instead of queueing, and give the carrier gateway its own connection pool so one degraded partner cannot drain the pool an unrelated service depends on. A circuit breaker on that partner turns a 22 s wait into an immediate, handled failure.
- Fix the structure: take the external call out of the transaction. Write the allocation state change and an outbox row in one short local transaction, let a relay make the rating or tender call asynchronously with a persisted idempotency key, and advance the saga on the result. Lock hold time then equals local work, single-digit milliseconds, and is independent of any partner.
- Accept and handle what the outbox costs you. Publication becomes at-least-once, so the consumer and the external effect must both be idempotent, and the caller now sees an asynchronous outcome it must be designed for. That is the trade: bounded lock hold time in exchange for eventual consistency and duplicate delivery you have to absorb.
Follow-up
- Your mitigation adds a lock_timeout. What does the allocation path do when it fires, and what does the customer see?
- Even with short transactions, 30 rows absorb most writes on promotion day. How do you keep those keys from serialising everything?
- How do you test this before the next promotion without a degraded partner to hand?
Four days sample coding, design, fundamentals and the practical rounds at deliberately shallow depth, which is enough to surface the topics you did not know were in scope. That map, rather than a guess made on day one, decides where the last three days go.
Prepare, practise & reflect
One practical outcome each day. Spend longer where you need it.
0 / 7 done01Coding, one pass at shallow depth
- Solve one problem from each of six families, an array with two pointers, hash counting, binary search, a tree traversal, a graph traversal and one dynamic program, under a hard twenty-minute cap with no extensions, marking each finished, late, or stalled.
- For every stall, write the exact move you could not make rather than the subject, so the note reads could not turn the recurrence into a loop rather than bad at dynamic programming.
- Fix nothing today. The value of the pass is the unfixed record.
Deliverable: Six timed attempts marked finished, late or stalled, each stall carrying a named blocking move.
Practice prompt ↗Practice prompt ↗Practice prompt ↗Worked solution ↗02Design, one pass at shallow depth
- Spend twenty minutes each on three different shapes, a read-heavy feed, a write-heavy ingest path, and something needing a transaction across two entities, stopping each at requirements, interface and data model.
- After each, write the first question you could not answer, which is usually a number you could not estimate or a failure mode you had no vocabulary for.
- Mark which of the three you would be most relieved not to be asked, and treat that as data rather than as a preference.
Deliverable: Three shallow designs, each with the first unanswerable question written at the bottom.
Practice prompt ↗Practice prompt ↗Practice prompt ↗03Fundamentals and the practical rounds
- Answer eight short questions in writing at four minutes each, covering the material that fills the gaps between the big rounds: what happens between a URL and a rendered page, what an index costs on write, when a process is preferable to a thread, and what conditions a deadlock requires.
- Do one thirty-minute practical task of the kind a take-home compresses: read an unfamiliar two-hundred-line file and write what it does, what you would change, and the one thing you remain unsure of.
- Score every answer fluent, correct but slow, or absent, and keep the absent ones visible.
Deliverable: Eight scored short answers and one written reading of unfamiliar code.
Practice prompt ↗Practice prompt ↗04The rounds that are about you, and the map
- Deliver three behavioural answers aloud against a timer, a conflict, a failure you owned, and a decision made without enough information, marking any that ran past three minutes or contained no number.
- Assemble the map: every marked item from days one to three on a single page, sorted by how likely it is to appear in your loop rather than by how uncomfortable it felt.
- Choose exactly two areas for the remaining three days and write down what you are deliberately abandoning.
Deliverable: A one-page scored map of the whole surface area with two areas chosen and the rest explicitly abandoned.
Practice prompt ↗Practice prompt ↗Worked solution ↗05First chosen area, to the depth you skipped
- Work the higher-ranked area in four focused blocks, choosing items one level above where you stalled rather than repeating what already works.
- After each block write the rule you extracted in one sentence with its precondition attached, since a rule carrying no precondition is exactly what fails under a variation.
- Re-attempt the day-one or day-two item that exposed this area and compare against the original timing.
Deliverable: Four worked blocks, a timed re-attempt against the original, and three one-sentence rules with preconditions.
Practice prompt ↗Practice prompt ↗06Second chosen area, where the gap is coverage rather than speed
- Treat the second area differently from the first. Day five drilled something you could already half-do; this one is usually a topic you had simply never met, so build one worked reference example end to end and keep it, rather than attempting six problems badly.
- Write down the vocabulary you were missing on day two or three, five terms at most, each with the one sentence that makes it usable in an answer rather than the textbook definition.
- Redo the shallow attempt that exposed this area and note whether you now fail later in the problem, because moving the failure point is the realistic gain from a single day and is worth more than a score that did not change.
Deliverable: One worked reference example for the newly covered area, a five-term vocabulary list, and a note on where the failure point moved.
Practice prompt ↗Practice prompt ↗07Reassemble the loop
- Sit two rounds back to back with no gap, ordering them so the area you chose second comes last, because the map was built from rested, isolated attempts and the loop will reach your weaker area when you are already spent.
- Write where the second round suffered from the first, which is normally the point at which structure collapses into narration.
- Reduce the week to one page holding only the rules you can state without reading them.
Deliverable: Mock notes on cross-round carryover plus a one-page card of rules you can recite from memory.
Practice prompt ↗Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
Bring the two or three numbers the story rests on and know how they were collected. A p99 whose timer starts inside your handler excludes the time a request spent queued, so it can sit flat while users wait longer. Give the window, the percentile and what the measurement left out, or drop the number.
Describe a situation where you discovered a significant bug or flaw in…
Describe a situation where you discovered a significant bug or flaw in a production system late in the development cycle. How did you handle it?
Approach
- Pick a story where you made the decision, not one where you watched it.
- Close with what you would do differently, concretely.
- Name the disagreement and how you resolved it with evidence.
Follow-up
- What did you decide not to do, and why?
- How did you know your change caused the improvement?
Argue against serving available-to-promise from a cache
The availability read sits inside checkout with a p99 budget in the low tens of milliseconds, and it is missing it. A senior engineer proposes serving available-to-promise from a two-second in-memory cache, falling back to a read replica, and leaving the allocation write untouched. You believe this is either harmless or an overselling bug depending on one detail nobody has stated. Describe a design you argued against: the distinction you drew, the evidence you brought, the alternative you offered, and what was actually decided. Be specific about the failure you predicted and whether it happened.
Approach
- Draw the distinction the room is missing before arguing anything: an advisory availability read that decorates a page is a different object from the check that gates an allocation write. Caching the first is ordinary; caching the second removes the atomicity that no-over-allocation depends on, because the check and the write must be one step.
- Make the argument falsifiable with the schema rather than with principle. If the allocation still commits through UPDATE inventory_position SET qty = qty - :n, version = version + 1 WHERE (item_id, node_id, lot_id, state) = (...) AND version = :v AND qty >= :n, with the affected-row count tested, then a stale cache produces failed attempts and a worse conversion rate, not oversold stock. If the write trusts the cached number, the cache is the bug. Ask which one is on the branch.
- Quantify the exposure instead of asserting it. On a hot item-node pair taking 40 allocation attempts per second with a two-second staleness window, roughly 80 attempts can be decided against an unchanged number; multiply by the fraction of pairs whose available quantity is below that to get the oversell rate, and note that both factors peak on the same day. Replication lag has the same shape and is worse, because lag grows precisely under the write burst that makes availability tight.
- Offer a cheaper path to the latency goal so the argument is not simply obstruction: serve the browse and search paths from the cache with an explicit staleness label, keep the authoritative check on the primary row, and attack the p99 where it actually is by measuring whether the cost is the query, the connection pool wait, or fan-out across nodes for a multi-node promise.
- Say where you were willing to lose. If the measurement showed the check was already conditional and the cache only chose candidates, the correct move is to concede quickly and say so, because an engineer who argues the same way regardless of the evidence stops being listened to.
- Report the outcome honestly, including the version that shipped and whether your predicted failure appeared. A strong answer names the metric that would have proved you wrong and whether anyone put it in place.
Follow-up
- The team keeps the primary check but wants the cache to prefilter which nodes are even considered. What can now go wrong that could not before?
- How would you bound overselling on one very hot item-node pair without adding latency to every other pair?
- What would you measure for a week to decide whether the cache is safe, and what result would change your mind?
Reverse an allocation design after peak contention
You chose optimistic concurrency for allocation: read the position, then UPDATE inventory_position SET qty = qty - :n, version = version + 1 WHERE version = :v, retrying on an affected-row count of zero. It was correct and fast in load tests with spread keys. At peak, a few hundred hot item-node pairs absorbed most write traffic, retries amplified, and allocation p99 went past the checkout budget. Describe a decision you reversed under production evidence: what you originally reasoned, the measurement that forced the change, what you replaced it with, and what you would have measured before committing.
Approach
- Say why the original choice was reasonable, because a reversal story is only useful if the first decision was defensible. Compare-and-set avoids holding a lock across the read, has no deadlock surface, and is uncontended on the long tail of keys, which is most keys most of the time.
- Name the mechanism of the failure rather than calling it contention. On PostgreSQL under READ COMMITTED, a conflicting UPDATE does not fail fast: it blocks on the row lock until the other transaction commits, then re-evaluates its predicate against the new row version and reports zero rows affected. Each loser therefore pays a full lock wait before learning it must retry, so with k writers queued on one pair the work is quadratic in k across the burst, and the retry loop adds round trips rather than avoiding waits.
- Bring the measurement that settled it, not the anecdote: attempts per successful allocation on the hottest pairs, the distribution of write traffic across item-node keys, and the p99 contribution of lock wait time separated from query time. Load tests with spread keys cannot show any of this, which is the real lesson and the thing you would run differently.
- State the replacement and its cost. Serialising each hot key behind a single writer with a bounded queue converts an unbounded retry storm into a bounded wait plus explicit shedding, at the cost of a new component, a routing decision and a failure mode when the writer for a key is unavailable. SELECT ... FOR UPDATE is the smaller change and trades the retry loop for an in-database queue that still consumes a connection per waiter.
- Describe the migration, since reversing a write path in production is where these stories become concrete: route only the measured hot keys first, keep both paths live behind a per-key decision, and verify with the same attempts-per-success metric before widening.
- Close on what you would have measured before committing, and be specific: key skew from production traffic, not from a synthetic generator, is the input the original decision was missing.
Follow-up
- Under REPEATABLE READ on PostgreSQL that same conflict raises a serialization failure instead. What changes in your retry code and your error budget?
- One key becomes so hot that even the single writer saturates. How do you shed load without overselling?
- An allocation must span two positions atomically. What breaks in your replacement design if those rows live on different shards?
- 01
Describe a situation where you discovered a significant bug or flaw in a production system late in the development cycle. How did you handle it?
- 02
The availability read sits inside checkout with a p99 budget in the low tens of milliseconds, and it is missing it. A senior engineer proposes serving available-to-promise from a two-second in-memory cache, falling back to a read replica, and leaving the allocation write untouched. You believe this is either harmless or an overselling bug depending on one detail nobody has stated. Describe a design you argued against: the distinction you drew, the evidence you brought, the alternative you offered, and what was actually decided. Be specific about the failure you predicted and whether it happened.
- 03
You chose optimistic concurrency for allocation: read the position, then UPDATE inventory_position SET qty = qty - :n, version = version + 1 WHERE version = :v, retrying on an affected-row count of zero. It was correct and fast in load tests with spread keys. At peak, a few hundred hot item-node pairs absorbed most write traffic, retries amplified, and allocation p99 went past the checkout budget. Describe a decision you reversed under production evidence: what you originally reasoned, the measurement that forced the change, what you replaced it with, and what you would have measured before committing.
Is this an official AURORA interview guide?
No. It is PracHub's own research and practice material for the Software Engineer role at AURORA. Rounds and questions reflect what candidates have reported, not a process AURORA has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗How difficult are technical interviews at AURORA compared to standard tech companies?
Interview rigor at AURORA is high, comparable to top-tier technology companies, but with a stronger emphasis on real-world system interactions, low-level execution efficiency, and domain-specific problem solving. Live coding sessions focus heavily on clean implementation and edge-case handling rather than abstract theoretical tricks.
PracHub interview research ↗Can I split the virtual onsite interview across multiple days?
Yes, the 4-round virtual onsite interview at AURORA can typically be split across two days upon request to accommodate scheduling and manage candidate fatigue. Coordinate directly with your recruiting coordinator once you reach the onsite stage.
PracHub interview research ↗Which programming languages should I focus on during coding rounds?
Language choice depends on the specific role track. For autonomy, vehicle platforms, and simulation roles, coding interviews are primarily conducted in C++. For data engineering, ML pipelines, and cloud roles, Python or Go is standard. For frontend/visualization roles, TypeScript and React are expected.
PracHub interview research ↗What is the typical timeline from the initial phone screen to an offer?
The entire interview process generally takes between 2 to 4 weeks. Recruiting teams at AURORA maintain fast communication, often providing feedback within a few business days following technical screens and onsite panels.
PracHub interview research ↗Sources & methodology 3 sources ↗
Official role evidence, timestamped platform data and clearly labeled preparation advice.
- 01PracHub interview research ↗
PracHub editorial research into this company and role, maintained with this guide. Candidate-reported, not an employer publication.
platform · Accessed 2026-09-24 - 02PracHub Software Engineer practice ↗
Cross-company practice questions for this role.
platform · Accessed 2026-09-24 - 03PracHub interview preparation framework ↗
The framework the preparation plan follows.
platform · Accessed 2026-09-24