Interview guides · Qualcomm
Qualcomm Software Engineer Interview Guide
Qualcomm Software Engineer interview guide: reported C, bit manipulation, OS and computer architecture questions, round-by-round prep and a 7-day plan.
Interviewing at Qualcomm
- Practice bank: 6+ questions
- Rounds: 1
- Typical prep: 1–2 weeks
- Interview reports: 2
Interview rounds
| Round | Questions |
|---|---|
| Technical Screen | 6 |
How hard is the Qualcomm Software Engineer interview?
| Difficulty | Share | Questions |
|---|---|---|
| Easy | 0% | 0 |
| Medium | 100% | 6 |
| Hard | 0% | 0 |
What Qualcomm actually tests for
| Topic | Share | Questions |
|---|---|---|
| Coding & Algorithms | 50% | 3 |
| Software Engineering Fundamentals | 33% | 2 |
| System Design | 17% | 1 |
The 60-second brief
Candidate reports describe the Qualcomm Software Engineer role as software written close to the silicon: device drivers, firmware, RTOS tasks, Linux and Android kernel code, and acceleration libraries for mobile processors, NPUs, GPUs, cellular modems and system-on-chip platforms. Teams named in those reports include Modem, GPU, Linux Kernel, Multimedia, AI/NPU and Driver groups. The work is described as controlling how memory is mapped, how threads are scheduled across cores and how power is managed, and as done alongside silicon design, verification and hardware validation teams.
Qualcomm candidates report 5 rounds · ≈ 4-6 weeks. The stages below are what candidates describe, not a published process.
What a Software Engineer does at Qualcomm
The responsibilities in those reports include writing bare-metal code, Linux and Android kernel drivers, firmware for embedded processors and hardware acceleration libraries, and turning hardware specs and block diagrams into working C/C++ driver code. Bring-up work is described in both pre-silicon emulation and early post-silicon platforms. Performance and stability work covers latency, power profiles, deadlocks, memory leaks and memory corruption, with kernel tracing tools, trace analyzers and lab instruments.
For preparation, this means the interview material leans toward the machine rather than toward abstract puzzles. The reported questions ask why a struct is the size it is, what volatile stops a compiler from doing, what a mutex costs compared with a spinlock, how a virtual address becomes a physical one, and what happens between an interrupt firing and its handler running. The coding questions reported are linked lists, bit operations, array two-pointer work and a thread-safe ring buffer. A correct answer is only half the job. The other half is saying what your code does to memory, to the cache and to other threads.
The reports also say the emphasis changes by team. Specialized roles such as ASIC verification, multimedia drivers or ML infrastructure can replace generic data-structure problems with domain rounds on UVM/SystemVerilog, camera or computer-vision pipelines, or DSP algorithms. Ask your recruiter early which team you are interviewing for and which of these apply.
The Qualcomm interview process
Qualcomm candidates report 5 rounds · ≈ 4-6 weeks. The stages below are what candidates describe, not a published process.
Recruiter Outreach
Candidates describe this as a first recruiter contact about the opening and your interest in it. Use it to learn which team the role sits in and what the later rounds will focus on. Candidate reports say both depend heavily on the team (Modem, GPU, Linux Kernel, Multimedia, AI/NPU, Driver). The answers decide whether you spend the week on generic data structures or on a domain round.
What it evaluates
- Whether your background (C/C++, kernel, firmware, embedded, drivers) matches the team the opening sits in
- Whether you can say in a sentence or two why low-level systems work is what you want to do
- Whether your constraints (start date, location, work authorisation) are clear before a loop gets scheduled
How to prepare
- Ask which team the role is on, which language the technical rounds expect, and whether a specialized domain round (verification, multimedia, DSP, ML infrastructure) is part of the loop
- Prepare a two-sentence summary of your most relevant low-level project: the hardware or OS layer it touched and the problem it solved
- Write your constraints down as one-line facts before the call so you state them rather than negotiate them on the spot
Initial Screening Phase
Candidates describe this stage only as a preliminary check of basic qualifications and fit. Candidate reports also say initial technical screens are held virtually, so set up a quiet space and a plain editor you are comfortable writing C in. Treat this stage as the point where your resume starts to be tested: candidate reports say resume projects get probed in depth during the process, so anything you name here should be something you can explain down to the register or the lock.
What it evaluates
- Whether your basic qualifications line up with the opening: the languages, operating systems and hardware layers you can speak to from real work
- Whether each resume project has a clear shape: the constraint, what you changed, how you measured the result
- Whether you can separate your own work from the team's on a shared project
How to prepare
- Before any screen, write one-paragraph answers to the reported fundamentals questions (endianness detection,
volatileandregister, dangling pointers, process versus thread) and say each aloud until it stays short and exact - For every project on your resume, list the three deepest follow-up questions someone could ask and make sure you can answer each one
- Attach a measured result to each project, or state plainly that it was never measured
Technical Deep Dives
Candidates describe a series of technical interviews on deep domain knowledge. The reports stress follow-up questions that test how far your understanding goes past the first answer. Candidate reports give one example of such a follow-up: what happens if a spinlock is held inside an interrupt service routine. A memorised definition gets you through the first question. The follow-ups need the mechanism.
What it evaluates
- Whether an OS or C answer holds up through two or three follow-ups: what the hardware, compiler or scheduler actually does
- Whether you can connect a concept to its failure mode, for example priority inversion to a stalled high-priority task, or cache thrashing to a loop's access pattern
- Whether you reason about memory layout, allocation and concurrency without being prompted
How to prepare
- For each reported OS topic (mutex, semaphore, spinlock, priority inversion, virtual memory, IPC), write the answer and then the next two follow-up questions with their answers
- Work through a spinlock shared between process context and an ISR: why process context must disable local interrupts while holding it, and why a sleeping lock cannot be taken in an ISR
- Trace a virtual-to-physical translation step by step: TLB lookup, page-table walk on a miss, page fault if the entry is not present
Full Loop Technical Interviews
Candidates describe these as back-to-back technical interviews that need sustained energy. Candidate reports say many technical interviews have you write C on a whiteboard or in a plain editor without autocomplete, so hand-written pointer code, bit masks and loop bounds have to be right without a compiler to catch you. Use the reported coding questions (linked-list reversal, bit set/clear/flip, moving zeros while keeping order, a thread-safe ring buffer) as practice material for that kind of session, and prepare for the length of the day as well as the content.
What it evaluates
- Whether hand-written C compiles in your head: correct pointer syntax, correct operator precedence, no off-by-one in array bounds
- Whether you state edge cases (empty input, single element, full and empty buffer, bit index out of range) before being asked
- Whether your explanation stays sharp in the later sessions of a back-to-back day
How to prepare
- Write the reported coding questions in C in a plain editor with no autocomplete, then compile them and count every error you would have shipped
- For the ring buffer, decide how you tell full from empty (leave one slot unused, or keep a count) and which synchronisation you use (a mutex plus condition variables for several producers, atomics only for the single-producer single-consumer case)
- Run at least one back-to-back mock of several technical sessions to find where your accuracy drops
Specialized Technical Rounds
According to candidate reports, specialized roles such as ASIC verification, multimedia drivers or ML infrastructure can swap generic data-structure problems for domain interviews. Topics named include UVM/SystemVerilog, computer vision pipelines and DSP algorithms. Related bank topics include debugging a UVM verification environment, raw camera data processing, and AI/ML compiler concepts. If your recruiter confirms a domain round, prepare for it as a separate subject, not as an extension of the coding practice.
What it evaluates
- Depth in the specific domain the team works in, rather than general algorithm fluency
- Whether you can debug a system in that domain step by step: isolate the failing stage, form a hypothesis, confirm it
- Whether you can explain the trade-offs particular to that domain (coverage closure, pipeline throughput, fixed-point precision) in plain terms
How to prepare
- Confirm with the recruiter which domain applies, and drop the others from your plan
- For verification roles, review UVM components versus UVM objects and describe how you would debug a failing test or close a coverage gap
- For multimedia or ML roles, walk through one pipeline you know end to end (raw sensor data to processed output, or model graph to compiled kernel) and name where it bottlenecks
Questions Qualcomm candidates report
Data structures and algorithms
One structure rarely covers the whole operation set, and the interesting questions in this category are the ones where it does not. When you need constant-time lookup and constant-time reordering at once, or the minimum plus deletion of an arbitrary element, the answer is two structures that index each other: a map from key to the node holding it, beside the list or heap that maintains the order. What separates a strong answer is the invariant between them, stated explicitly. Every operation has to leave both halves consistent, and the bug people ship is a removal from one that leaves a stale entry in the other.
- Solve array/string manipulation problems using two-pointer techniques or sliding windows (e.g., Two Sum, reversing arrays, checking palindromes).
- Write a function to reverse a singly linked list in C/C++ in-place.
- Write a program to set, clear, or flip specific bits (e.g., flip the 3rd bit) in an integer using bitwise operations.
- Implement a thread-safe circular buffer (ring buffer) or FIFO queue with concurrent push/pop capability.
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.
- Explain the fundamental differences between bus protocols like AXI, AHB, and standard interfaces like I2C vs. SPI.
- What happens internally when software writes to a memory-mapped register in an embedded system?
Languages, concurrency and fundamentals
Before choosing threads, processes or async, classify the work by which resource is saturated. CPU-bound work needs real parallelism, so it needs cores, and a runtime that serialises bytecode execution will not provide any however many threads you start. I/O-bound work is waiting rather than computing, and concurrency alone fixes it: one thread or one event loop can hold thousands of open sockets. Weak answers reach for threads by default. Strong ones name the bottleneck first, then the model, then what the model costs, which is either shared memory and data races or isolated memory and serialisation on every hand-off.
- What is the difference between a Mutex and a Semaphore? When would you use a Spinlock over a Mutex?
- Explain structure padding and packing. How do alignment requirements impact total structure size across 16-bit, 32-bit, and 64-bit architectures?
Behavioural and engineering judgement
Ownership questions here usually point at something with no owner and no deadline: an alert people mute, a suite everyone reruns until it passes, a service whose on-call is a rota of volunteers. Nothing forces action, so what you did says how you spend discretionary time. The strongest answers are not the ones where you fixed everything. They are the ones where you sized the problem first, chose between fixing it, containing it and writing it down for whoever comes next, told someone who could have overruled you, and can still say why the other two options were worse.
- How do you resolve technical disagreements with hardware or verification teams during project development?
Practice exercises with worked solutions
The prompts above tell you what gets asked.
Replace offset paging on the resource feed with keyset
resource holds resource_id, tenant_id, owner_user_id, title, body_ref, version, status ('draft','active','archived','deleted'), created_at, updated_at, deleted_at, with an index on (tenant_id, status, updated_at DESC, resource_id DESC). The listing endpoint returns active resources for one tenant, newest update first, 50 per page, today with LIMIT 50 OFFSET n. Tenants reach page 400 and rows are created while they read. Write the keyset query, define what the cursor carries and how it is encoded, and say which part of the index each predicate uses. Assume PostgreSQL 16.
How a strong candidate works it
- Name the two failures separately. OFFSET 20000 makes the server produce and discard 20,000 rows, so page cost grows with depth rather than with page size. Independently, any write that changes how many rows sort above the offset moves the window between two fetches, and the direction decides which anomaly you get: an insert lands at the head of updated_at DESC and pushes already-returned rows down past the boundary, so they are returned a second time; a delete above the offset, or a row whose updated_at is bumped above the cursor, pulls rows up and one is never returned at all. Nothing in the response reveals either.
- Write the seek: WHERE tenant_id = $1 AND status = 'active' AND (updated_at, resource_id) < ($2, $3) ORDER BY updated_at DESC, resource_id DESC LIMIT 50. The row-value comparison is one index range rather than a disjunction, and both columns are NOT NULL, which is what makes that comparison well defined.
- Map each predicate onto the index: tenant_id and status are equality on the leading columns, (updated_at, resource_id) is the range, and the ORDER BY matches the index order so no Sort node appears and the scan stops after 50 rows. The DESC in the definition only matters for mixed directions — a plain ascending btree on the same columns is read backwards for this query.
- Put both sort columns in the cursor and nothing the client can tamper with into another tenant: base64 of (updated_at, resource_id), validated server-side, with tenant_id taken from the principal.
- State the residual honestly. Keyset is stable against concurrent inserts and deletes, but not against a row whose updated_at changes mid-scroll — that row moves in the ordering and can be seen twice. If the feed must be a snapshot, order by an immutable key or bound the page set with updated_at <= the cursor's start value.
- Keep a total out of the page path. A tenant-wide COUNT(*) is the scan keyset just removed; fetch LIMIT 51 and return has_more instead.
Expected result (25 min): Keyset reads about 50 index entries per page and its latency is flat from page 1 to page 10,000; under both writer profiles the collected ids contain no duplicate and omit no row that existed for the whole scroll. OFFSET at page 400 reads 20,050 rows to return 50. Under the insert-only writer it repeats rows and skips none — an insert can only push a row down past the boundary — and only the second profile, whose deletes and updated_at bumps move rows up past the cursor, also makes it skip. Without the tie-break, the seek is updated_at < $2, which drops every still-unread row sharing the boundary timestamp.
Check yourself
- EXPLAIN shows an Index Scan with no Sort node — a Sort means the ORDER BY is not satisfied by the index.
- Buffers read per page stays constant as page depth grows.
- Ids collected across the scroll are unique, and complete for rows untouched during the run.
Follow-ups
- The client asks for 'jump to page 400'. What do you offer instead, and what does the honest version cost?
- Sort order becomes user-selectable across four columns. How many indexes is that, and which would you refuse to add?
- What does the cursor do when the row it points at has since been deleted?
Keep soft-deleted accounts from blocking re-registration
app_user holds user_id, tenant_id, email CITEXT, password_hash (NULL for SSO principals), email_verified_at, auth_version, status ('invited','active','suspended','deactivated'), created_at, updated_at, deleted_at. Two live accounts for one address inside a tenant must be impossible, but an address freed by a soft delete must be reusable, and the same tenant may delete and re-register it repeatedly. Write the uniqueness DDL for PostgreSQL 16, then the equivalent for MySQL 8 where partial indexes do not exist, and say what each permits once three deleted rows already hold that address.
How a strong candidate works it
- Start from what is actually unique: not (tenant_id, email), but (tenant_id, email) among live rows. PostgreSQL says that directly — CREATE UNIQUE INDEX app_user_live_email ON app_user (tenant_id, email) WHERE deleted_at IS NULL. A full constraint over the same two columns burns the address permanently the first time someone deletes an account.
- Keep case-insensitivity in the type or the index, never in the application: CITEXT as given, or UNIQUE (tenant_id, lower(email)) as an expression index where the extension is unavailable. A case-sensitive unique column is exactly how two accounts for one human appear.
- For MySQL 8 the predicate has to move inside the key: add a discriminator column that is a constant 0 while the row is live and is set to user_id on delete, with UNIQUE (tenant_id, email, deleted_marker). Live rows share the constant and still collide; deleted rows differ from each other and stop colliding.
- State the NULL variant and its dependency: leaving the marker NULL for deleted rows also works, because a unique index treats NULLs as distinct — true in MySQL, and true in PostgreSQL only under the default NULLS DISTINCT, which PostgreSQL 15 lets you reverse. Check the polarity against the three existing deleted rows: constant-on-live is what preserves the collision you want, and reversing it silently admits duplicate live accounts.
- Say what a soft delete must do besides setting deleted_at: increment auth_version so existing tokens stop validating, leave resource.owner_user_id and resource_revision.actor_user_id intact, and accept that the address is retained — erasure is a different requirement answered by scrubbing the column, not by a DELETE that would break those references.
Follow-ups
- A deleted account re-registers with the same address the next day. Do the old resource rows follow the new user_id, and how does the API keep the two principals apart?
- How do you honour an erasure request while resource_revision.actor_user_id still references this table?
- What changes if a user may hold membership in two tenants?
Merge partitioned event streams into one ordered feed with bounded lateness
The read-model service consumes 64 log partitions carrying about 4,000 events per second in total. Each partition is ordered within itself, but partitions drift by up to 30 seconds, and the activity feed must present a tenant's events in occurred_at order. Produce the merge. State its complexity, the buffer it requires in events and in bytes, what happens when one partition is idle, and what you do with an event that arrives after you have already emitted its position. Payloads average 1 KB.
How a strong candidate works it
- Merge with a min-heap over the 64 partition heads keyed on (occurred_at, event_id): O(log P) per event and O(n log P) overall. The tie-break on event_id is what makes the output deterministic when two partitions carry the same millisecond, which matters because the feed is paginated and a non-deterministic order reorders pages under the reader.
- Emitting the heap head is only correct once every partition has produced everything up to that timestamp, so the emit condition is a watermark: the minimum across partitions of the highest occurred_at seen, less the allowed lateness. Events are held until the watermark passes them, which is what turns individually ordered streams into a jointly ordered one.
- Size the buffer from the lateness rather than guessing: 4,000 events per second times 30 seconds is 120,000 buffered events, and at 1 KB each about 120 MB of heap. That number is the real price of the ordering guarantee and belongs in front of whoever asked for it.
- Handle the idle partition explicitly, because it fails the feed rather than corrupting it: a partition with no traffic never advances its own maximum, so the watermark freezes and output stops entirely. Either every partition emits a periodic idle marker carrying the broker's current time, or the watermark falls back to wall clock for a partition silent beyond a threshold.
- Choose the late-event policy from what the projection is keyed on. The projection upserts on (aggregate_id, aggregate_version) and discards a version it has already applied, so a late event is safe to apply out of order and correctness never depended on the merge at all. Apply it, recompute the affected feed page, and count lateness so the 30-second budget can be re-derived from data rather than folklore.
- Say what the merge does not buy: ordering is guaranteed within one aggregate by the log's partitioning, and no watermark makes the cross-aggregate order authoritative. Two events from different aggregates in the same millisecond have no true order, so the feed's order is a presentation choice that must be stable rather than correct.
Expected result (35 min): A 64-way min-heap merge at O(n log P) with a deterministic (occurred_at, event_id) comparator, a watermark of the per-partition minimum less 30 seconds gating emission, a stated buffer of 120,000 events and roughly 120 MB, idle markers so a silent partition cannot freeze the watermark, and a late-event policy justified by the projection's idempotency on (aggregate_id, aggregate_version).
Check yourself
- Feeding the partitions in an adversarial interleaving produces the same output sequence as feeding them in timestamp order.
- Silencing one partition stalls output without the idle marker and does not stall it with the marker.
- Applying a late event twice leaves the projection byte-identical, which is what permits the out-of-order path to exist.
Follow-ups
- The lateness budget is raised to five minutes. What is the new buffer, and what besides memory changes?
- The consumer restarts. Where does it resume from, and what does the feed look like for the first 30 seconds?
- One partition is ten minutes behind because its producer is slow. Do you stall the feed or emit without it?
Specify the outbound webhook contract a customer endpoint can verify
Egress delivery sends resource.created, resource.updated and resource.archived to roughly 40k customer endpoints at about 1.5k deliveries/second, with a per-destination concurrency cap of 4 and a 10 second connect-plus-read timeout, at-least-once from the relay. The receiver is code you do not control, often behind a proxy that may re-encode JSON. Specify the payload envelope, how the receiver verifies authenticity and rejects replays, what ordering it may assume, how it deduplicates, which status codes it should return and what each means to us, and the retry, circuit and replay behaviour.
How a strong candidate works it
- Send the fact, not a pointer: event_id, event_type, aggregate_type, aggregate_id, aggregate_version, occurred_at and the payload copied from outbox_event. A receiver told only that resource 42 changed has to call back, and the call back returns a newer state than the event describes - that is precisely how a consumer applies changes out of order while believing it is following them.
- Sign the exact bytes on the wire: HMAC-SHA256 over the timestamp, a separator, and the raw body, in a header, compared in constant time before the body is parsed. Instruct receivers to verify the raw bytes, never a re-serialised copy, since a proxy that reorders keys or changes whitespace invalidates a signature computed over re-encoded JSON. Reject a timestamp outside a tolerance - five minutes is typical - and keep a short seen-set of event_ids, because a capture replayed inside the tolerance still verifies.
- Support key rotation by sending several signature values in one header during the overlap window and publishing that any one that verifies is sufficient. Without that rule a key rotation is a synchronised outage across every destination at once.
- State the guarantee as it actually is: at-least-once and unordered, including within a single aggregate_id. The outbox is ordered per aggregate at the source, but the relay holds up to 4 requests in flight per destination and backs off each retry independently, so a version 6 delivery that needs one retry lands after a version 7 that succeeded on its first attempt. Publishing per-aggregate ordering we do not enforce is worse than publishing none, because a receiver that trusts it overwrites new state with old. Receivers deduplicate on event_id and apply conditionally - UPDATE ... WHERE aggregate_version < :incoming, or the same comparison under a row lock on the aggregate - which discards stale redeliveries and stays correct when two deliveries for one aggregate are processed at the same time. A duplicate and an out-of-order delivery then have the same outcome: no-op.
- Define the response contract in the receiver's terms. 2xx means durably accepted, and accepting means writing it down - a receiver that does its processing inline inside our 10 second timeout will be retried while it is still working. 4xx is permanent and we stop. 429, 5xx and timeouts are retried with capped exponential backoff and full jitter within the per-destination concurrency cap, and a circuit opens after a run of consecutive failures and half-opens on a schedule, so a permanently dead endpoint costs one probe per interval instead of a growing queue.
- Give them a recovery path that does not involve us holding a queue for everyone: a visible parked state, and a replay endpoint keyed by event_id or time range, so a receiver that was down for two hours asks for the gap on its own schedule.
Expected result (30 min): A self-contained signed envelope, verification over raw bytes with a replay window and a seen-set, an at-least-once guarantee that states delivery is unordered even within one aggregate, a version-conditional apply, a status table that distinguishes permanent from transient, and a customer-driven replay path.
Check yourself
- Delivering the same event twice leaves the receiver's state identical to delivering it once.
- Delivering version 7 and then version 6 for one aggregate leaves version 7 applied.
- A destination that always times out costs one probe per circuit interval rather than a retry queue.
Follow-ups
- A customer insists an event never arrived. What do you check, in order, and which table settles the argument?
- A receiver cannot store a version and asks us to deliver their events in order. What would the relay have to give up to offer that, and what does one failing event then do to the rest of their stream?
Listing latency scales with page size, not with filters
The tenant listing endpoint reads resource filtered by tenant_id and status, ordered by updated_at DESC, and returns each row plus the owner's display name from app_user and the actor of that resource's latest resource_revision. p99 is 55 ms at 10 rows per page and 1.4 s at 200. Database telemetry shows 401 statements per request, each under 1 ms, and nothing in the slow-query log. Diagnose the cause and give the fix, stating the statement count per request and the p99 you expect afterwards.
How a strong candidate works it
- Read the counters before forming a theory. 401 statements for 200 rows is one driver query plus two per row, and sub-millisecond execution with an empty slow-query log rules out a bad plan. The time is round trips, which is why it is invisible in every per-query metric and scales with rows returned rather than with filter selectivity.
- Name the two per-row statements from their normalised text: a single-row app_user lookup by user_id, and a resource_revision lookup by resource_id ordered by version DESC LIMIT 1. Confirm by dropping those two response fields and watching the statement count fall to one. That locates the calls in the serialisation layer, not the repository.
- Check that the arithmetic accounts for the whole gap. Measure one round trip to the replica in isolation; 400 trips at roughly 3 ms of network plus 0.2 ms of execution is about 1.3 s on top of a 55 ms baseline, which matches. If the multiplication had fallen short, the N+1 would only be part of the story and you would keep looking.
- Batch both lookups. Collect owner_user_ids and resource_ids from the driver query, then issue WHERE tenant_id = $1 AND user_id = ANY($2) for the users, and PostgreSQL's SELECT DISTINCT ON (resource_id) ... WHERE resource_id = ANY($2) ORDER BY resource_id, version DESC for the latest revision, which the UNIQUE (resource_id, version) index serves directly. On an engine without DISTINCT ON, use a lateral join or a row_number window. Three statements per request at any page size.
- Keep the tenant predicate in the batched query. The per-row version was implicitly scoped because its ids came from tenant-scoped rows; a batched user_id = ANY(...) with no tenant_id is an unscoped read that behaves correctly only as long as the id list is trustworthy.
- Re-measure at 10, 50 and 200 rows and confirm the statement count is constant. Latency should now track bytes returned.
Follow-ups
- The page size is capped at 200 today. What breaks first if it is raised to 2,000, and is it still this bug?
- How do you stop the next N+1 from reaching production, given that no individual query is slow and the endpoint's tests pass?
- The latest-revision actor is only used to render an avatar. Make the case for denormalising it onto resource, and name the write anomaly that introduces.
Reverse your own decision and price the reversal
Describe a technical decision you made and later reversed. Pick one that cost something: a service you split and merged back, a cache you added and removed, an index you created that pushed the planner onto a worse plan, a projection you rebuilt from scratch. State what you believed when you decided, the measurement that changed your mind, how long the wrong version ran in production, and what the reversal cost in migrations, dual writes, and a deprecation window for callers you did not own.
How a strong candidate works it
- State the original rationale without irony, in the version you would still defend given what was known then. If it is not defensible, the story is about carelessness rather than judgement, and a different example serves you better.
- Give the measurement that moved with a before and after: the p99 that did not improve, the cache hit rate that sat at 40%, the plan that flipped to a sequential scan once the table passed a size you can name.
- Cost the reversal in steps, not adjectives: expand-and-contract deploys, the dual-write window, the callers who had to be notified, the rows already written in the wrong shape that had to be backfilled or abandoned.
- Distinguish reversal from rewrite by naming what you kept. Most good reversals preserve the schema or the interface and undo one decision inside it, which is also why they were affordable.
- Finish on the process change: the smallest experiment that would have produced the same measurement in a day, and why you did not run it the first time.
Follow-ups
- What in that decision was irreversible, and did you know it was irreversible when you made it?
- How did you tell the people who had already built on top of the original decision?
- What do you now measure before committing to a change of this size?
Estimate work you have never done and defend the range
You are asked to estimate a change you have never attempted: add a column to a 100-million-row table, populate it, move reads across, and drop the old shape. Give a range with the assumptions that generate it, including batch size, the signal your backfill throttles on, and wall-clock hours, and name the three unknowns that would move the number most. Then describe a real estimate you gave under comparable ignorance: how you expressed its uncertainty, what you committed to, and how wrong you turned out to be.
How a strong candidate works it
- Decompose into independently deployable steps before estimating anything: add the column nullable, write both shapes, backfill in batches, verify, move reads, stop writing the old shape, drop it. That is four deploys spread over days, and the calendar estimate is dominated by them rather than by the loop's runtime.
- Do the arithmetic aloud for the part that has arithmetic in it: batch size times number of batches times per-batch duration, at a write rate the primary can absorb alongside roughly 1.2k writes per second of production traffic. The loop is throttled by replication lag and lock waits, not by how fast it can issue statements.
- Price the schema step by its lock rather than its statement duration. In PostgreSQL an ALTER TABLE taking ACCESS EXCLUSIVE waits for every open transaction on that table while later queries queue behind it, so a millisecond change issued during a thirty-second analytics query stalls that table for thirty seconds. Adding a nullable column with a non-volatile default avoids a rewrite from version 11; a new index wants CREATE INDEX CONCURRENTLY, which cannot run inside a transaction block and leaves an invalid index behind if it fails.
- Express the answer as a range whose endpoints each trace to a stated assumption, then name the cheapest experiment that collapses it, which is almost always running one real batch against the real table and multiplying.
- Commit to a checkpoint rather than a completion date: the day you report a measured number from that first batch. That is a promise you can keep under uncertainty, and it is what the asker actually needs in order to plan.
Follow-ups
- How do you verify the backfill genuinely finished, given rows written by production traffic while it ran?
- Where does the backfill resume from after a worker is killed mid-batch, and what makes that resume point trustworthy?
- Your first batch comes back ten times slower than assumed. What do you tell the person waiting on the estimate, and when?
Where candidates lose points
Giving a textbook definition of a mutex, semaphore or spinlock and then stalling on the scenario follow-up
Candidate reports describe scenario follow-ups rather than definition checks, for example what happens if a spinlock is held inside an interrupt service routine. Prepare each primitive through its failure modes. A mutex may sleep, so it cannot be taken in interrupt context. A spinlock busy-waits, so it only suits short critical sections. If the same data is touched from an ISR, process context has to take the lock with local interrupts disabled, or the ISR can spin forever on that CPU. Priority inversion needs a low-priority holder, a high-priority waiter and a medium-priority task that preempts the holder.
Writing C that only works with a compiler and autocomplete to fix it
Candidate reports say many technical interviews use whiteboards or plain editors with no autocomplete. Practise the reported coding questions by hand and check them afterwards. Common slips: x & mask == 0 parses as x & (mask == 0), so parenthesise the mask. 1 << 31 on a signed int is undefined in C, so use 1u << n. Right-shifting a negative signed value is implementation-defined, so use unsigned types for register values. In linked-list reversal, save next before you overwrite the pointer.
Producing working code but not being able to say what it does in memory
Candidate reports say candidates who struggle often have working code but cannot explain memory layout, stack versus heap allocation, pointers or concurrency safety. After every solution, add a sentence on where each object lives, what gets copied, and what happens if two threads call the code at once. For struct questions, work the layout on paper. On x86-64, {char; int; double; short} is 24 bytes and shrinks to 16 when ordered largest-first. On 32-bit ABIs the result depends on whether double aligns to 4 or 8 bytes, so say which ABI you assume.
Listing resume projects you cannot defend at register, driver or lock level
Candidate reports say interviewers dig deep into resume projects: architecture choices, hardware tools used, bugs solved. Keep only projects you can explain three levels down. For each, prepare the hardest bug with its step-by-step isolation, one design decision with the alternative you rejected, and one measured result. Anything you cannot go deep on should become a single line, or come off the resume.
Treating volatile as a concurrency tool or skipping it on memory-mapped registers
Know exactly what volatile does. It stops the compiler from caching, merging or removing accesses, which memory-mapped registers and ISR-shared flags need. It does not make an operation atomic and it does not order memory between CPUs, so thread synchronisation still needs atomics, locks or barriers. When asked what happens on a register write, add that the store can be buffered or posted on the bus, which is why drivers read the register back or use a barrier before depending on the write having landed.
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 — C memory layout and language details
- Answer the reported C questions in writing: endianness detection in C, structure padding and packing,
volatileandregister, the compile pipeline from source to binary, and dangling pointers - Work struct sizes on paper for a mixed
char/int/double/shortstruct under x86-64 and a 32-bit ABI, then reorder the members to minimise size and check withsizeof - Write
memmoveby hand so it handles overlapping regions, and explain whymemcpydoes not have to
Deliverable: One page of C answers you can deliver from memory, plus a struct-layout worksheet checked against a compiler.
Day 2 — Bit manipulation and endianness coding
- Write set, clear, flip and test for bit n using unsigned masks, then write a byte-order swap for a 32-bit word with shifts and masks
- Count set bits two ways: a loop over all bits, then Kernighan's
x &= x - 1at O(k) for k set bits, and explain why the second one terminates - Solve the two-unique-numbers XOR problem and explain why isolating the lowest set bit splits the two numbers into different groups
Deliverable: A tested file of bit routines written first in a plain editor, with every hand-written mistake noted.
Day 3 — Pointer-based coding: linked lists and arrays
- Reverse a singly linked list in place in C with three pointers, and trace it on empty, single-node and two-node lists before running it
- Move all zeros to the end of an array while keeping the order of the other elements, in one pass with a write index
- Solve Two Sum with a hash map and a palindrome check with two pointers, stating the complexity of each before coding
Deliverable: Hand-written C solutions with edge-case traces written before compiling, and a count of the errors the compiler found.
Day 4 — Operating systems and concurrency
- Write answers with follow-ups for process versus thread and context switching, mutex versus semaphore versus spinlock, priority inversion and inheritance, virtual memory translation, and IPC through shared memory
- Implement a thread-safe ring buffer with a mutex and two condition variables, and state how full and empty are told apart
- Solve producer-consumer with POSIX mutexes and condition variables, and explain why the wait sits inside a
whileloop and not anif
Deliverable: A working ring buffer that passes a multi-producer stress test, plus an OS answer sheet with two follow-ups per topic.
Day 5 — Computer architecture and the hardware-software interface
- Explain cache hits and misses, write-back versus write-through, coherence (MESI) and thrashing, then restructure a column-major loop over a row-major array and explain the locality gain
- Walk through a memory-mapped register write from the store instruction to the device, including why the access must be
volatileand when a read-back or barrier is needed - Explain the sequence from interrupt assertion to ISR execution, and compare AXI and AHB with I2C and SPI at the level of what each is for
Deliverable: A one-page diagram set: cache hierarchy, MMIO write path and interrupt flow, each explained aloud without notes.
Day 6 — Debugging stories, resume depth and specialized topics
- Prepare the behavioral stories: the hardest bug from bring-up or emulation with step-by-step isolation, an optimisation for memory footprint or latency, and a technical disagreement with a hardware or verification team
- For each resume project, write the three deepest follow-up questions and answer them
- If the recruiter confirmed a specialized round, spend the remaining time on that domain (UVM, camera pipelines, DSP or ML compilers). Otherwise, walk through debugging driver-caused memory corruption step by step: the symptom, how you narrow it to the driver, the tools you would use, and how you confirm the fix
Deliverable: Three STAR stories with measured results, plus a follow-up sheet for every project on your resume.
Day 7 — Back-to-back mock loop in a plain editor
- Run several technical sessions back to back with a partner: one C and memory, one bit or linked-list coding, one OS or concurrency, one architecture, all written without autocomplete
- Have the interviewer push every answer with at least two scenario follow-ups, such as a spinlock inside an ISR or an interrupt arriving mid-critical-section
- Note where accuracy dropped across the sessions and re-drill that topic briefly before the real loop
Deliverable: Mock scorecards for each session and a short list of the topics to review the night before.