As a Software Engineer at General Motors (GM), you stand at the intersection of automotive innovation and advanced software development. Software is no longer just a supporting component of vehicles—it is the central driver of modern mobility. In this role, you contribute directly to critical platforms ranging from advanced driver assistance systems (ADAS), autonomous vehicle telemetry pipelines, and vehicle electrification platforms, to enterprise web architectures, mobile vehicle applications, and manufacturing automation tools.
The software engineering organization at General Motors (GM) operates at immense scale. Your work directly impacts millions of vehicles on the road, supporting real-time embedded systems, vehicle-to-cloud communications, and critical safety frameworks. Whether you are engineering low-level microcontrollers in C++, building scalable microservices in Java and Kotlin, or crafting seamless user interfaces for connected vehicle applications, your code must meet rigorous standards for performance, safety, and reliability.
Working at requires a blend of traditional engineering discipline and modern software practices. You will collaborate across cross-functional teams comprising system architects, hardware engineers, product managers, and quality assurance specialists. Candidates who thrive in this environment demonstrate strong computer science fundamentals, clear communication, and a commitment to methodical problem-solving in complex system ecosystems.
Initial Screening Call
reportedThe title covers product work, platform work, infrastructure, mobile and frontend, and those are different jobs with different loops behind them. A screening call is the cheapest place to find out which one the seat is, and asking reads as experienced rather than fussy. The questions that separate them: what the team is on call for, what the last three projects were, and whether any round happens inside an existing repository instead of a blank file. Then say which of that you have done and which you have not. Claiming the whole posting is the fastest way to be found out one round later.
What to demonstrate
- Whether you can locate your experience inside one flavour of the role honestly instead of claiming the entire requirements list
- Whether you name what you have not done, which an experienced screener reads as a level signal and can plan the loop around
- Whether what you want next matches what the seat is: someone who wants greenfield work landing on a team that mostly operates an existing system is a hire that leaves within the year
How to prepare
- Mark every line of the posting as done, adjacent or new, and write one sentence for each adjacent line naming the closest thing you actually built
- Split your last two years into rough percentages across feature work, operating and debugging live systems, and design or review, so a question about scope gets numbers rather than adjectives
- Bring three questions that discriminate between seats: what the team is paged for, how much of the work is changing existing code versus standing up something new, and what shipped in the last quarter
Technical Interview
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
Behavioral Assessment
reportedMany of these questions are about something that went wrong, and the grading sits mostly in the hours after you knew. Who found out first, whether that was you or an alert or a user, how long it took you to say it out loud, and whether the people who needed the news got it while they could still act on it. Engineers under-tell this part because it feels like confessing. The pattern it is looking for is the opposite: the quiet fix, an incident absorbed without telling anyone, after which nothing changed and the same failure is still available.
What to demonstrate
- How the problem was found, and whether that route was one you had built or one that happened to you, since a user reporting it first means your instrumentation did not cover that failure
- Whether time-to-detect and time-to-tell are separate numbers in your account and whether you know both, because a fast fix that nobody heard about until the retro is a different answer from a slow one that was announced immediately
- Whether the resolution left something durable behind, a check that fires or a default that changed, rather than depending on people remembering to be careful
- Whether you can say what the failure cost without either inflating it or waving it away
How to prepare
- Reconstruct one incident you were part of as a timeline with clock times: first bad request, first signal, first person who knew, first message outside the team, mitigation, permanent fix. The gaps between those entries are what gets asked about
- Look up the configuration of the signal that caught it, including its evaluation window and threshold. An alert defined on a five-minute aggregate cannot fire until the condition holds across that window, which puts a floor under time-to-detect that has nothing to do with how severe the failure was. Be able to say what that floor was and whether anyone had chosen it deliberately
- Prepare one story where you escalated early and the severity turned out to be smaller than you thought, including what it cost the people you pulled in. Without it, every answer you give about raising alarms is unfalsifiable
PracHub editorial advice for the preparation topics above.
Floating-point quantities and implicit unit-of-measure conversions
Binary floating point cannot represent most decimal fractions exactly, so a chain of pallet-to-case-to-each conversions accumulates a residue that a later rounding turns into a unit that was created or destroyed, breaking conservation with no concurrency involved at all. The bug is slow and non-local: it surfaces as a cycle-count variance weeks later, at a node nobody changed, and it is untraceable because the arithmetic looked correct at every individual step. The fix is to hold quantities as integers in the smallest transacting unit, carry the UoM explicitly on every row that carries a number, store conversion factors as integers that are versioned in time, and reject a conversion that does not divide exactly instead of rounding it. The same discipline applies to money on the freight side, where an accessorial in floating-point dollars produces invoice reconciliation differences of a cent that cost more to investigate than the freight.
Treating the database as the truth about physical stock
Every system in this domain drifts from reality, through miscounts, unrecorded damage, mis-scans and theft, so a design with no adjustment path forces the drift somewhere worse. The usual shape is a CHECK constraint or application guard that clamps a position at zero: the negative that would have exposed a missing receipt is silently swallowed, and the first visible symptom is a picker standing at an empty location for an order the system promised. The correct posture is to let the invariant fail loudly - record the cycle-count adjustment as a first-class movement with its own reason code, surface the variance, and let planning see that this location is unreliable - because an adjustment that is indistinguishable from a normal pick destroys the only signal that would have found the root cause. A related version of the same mistake is deleting or updating a movement row to fix a mistake, which leaves the position correct and the history a lie.
Naming no test cases at all
State what you would test before being asked: empty input, a single element, all elements equal, the maximum permitted size, and the input that exercises the branch you just wrote. It costs thirty seconds and is much of what separates someone who has shipped code from someone who has only solved puzzles.
Designing for a scale nobody asked for
Ask for request rate, data size, read-to-write ratio and expected growth, then size the simplest option first; one relational instance on current hardware covers a large share of real workloads. Reaching for shards, queues and a cache tier before any number has been quoted reads as pattern-matching rather than judgement.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
Walk through a graph or tree structure to count connected components o…
Walk through a graph or tree structure to count connected components or navigate custom dependencies.
Approach
- Restate the input: its shape, its size, and what is guaranteed about it.
- Choose the data structure from the access pattern, not from familiarity.
- State the target complexity and say which constraint rules the naive version out.
Follow-up
- How does this change if the input no longer fits in memory?
- Which test case would catch an off-by-one here?
How would you find duplicate or matching elements across structured da…
How would you find duplicate or matching elements across structured data arrays efficiently?
Approach
- Walk one small example through your approach before writing the whole thing.
- Restate the input: its shape, its size, and what is guaranteed about it.
- State the target complexity and say which constraint rules the naive version out.
Follow-up
- What is the worst case, and how likely is it on real data?
- Which test case would catch an off-by-one here?
Find the busiest ten-minute window per reader after deduplication
One day of observation_event from fixed readers: source_id (reader), subject_id (container), occurred_at, received_at, dedupe_key. 300 million rows, partner and device replays resend whole windows, and some events arrive six hours after they occurred. For each reader, return the 10-minute event-time window containing the most distinct containers, with the window's start. Target linear work per reader after sorting. State the window semantics you chose, and what the job does with an event that lands after its window was already reported.
Approach
- Deduplicate before windowing, on
dedupe_key, and keep the two concerns separate. A replayed batch is a no-op only if the duplicate is removed first; deduplicating inside the window logic counts a resend as traffic and reports a congestion peak that never happened. Dedupe is a hash set per reader-day or a sort-unique on the key — the same sort you need anyway. - Partition by
source_idand sort each partition byoccurred_at, never byreceived_at. Arrival order is an artefact of device connectivity and partner batch cadence; a reader that went offline for six hours would otherwise show its entire backlog as one impossible burst. Sorting dominates: O(p log p) per reader of size p, O(n log n) overall. - Two pointers over the sorted partition with a hash multiset
container -> countand a separatedistinctinteger. Advance the right pointer, incrementing the count and bumpingdistinctonly on a 0-to-1 transition; advance the left pointer whileoccurred_at[left] <= occurred_at[right] - 600s, decrementing and loweringdistinctonly on a 1-to-0 transition, and erasing the key from the map so the map's size anddistinctnever diverge. Each element enters and leaves once: O(p) amortised after the sort, O(window occupancy) space. - Justify the candidate set rather than scanning every possible start. With half-open windows
[t, t+600), sliding a window right until its left edge coincides with an event can only add events and never removes one, so the maximum is attained at some event'soccurred_at; checking the p windows anchored at events is sufficient and exhaustive. State the half-open convention explicitly — an event exactly att+600belongs to the next window, and flip-flopping on that produces off-by-one disagreements between this job and whatever reads its output. - Handle lateness by choosing a policy, not by hoping. Either run over a closed event-time batch with a stated lateness allowance — the day plus six hours, so the six-hour tail is inside the batch and the day-late tail is not — or emit under a watermark and restate the affected windows when a late event lands. Both are defensible; what is not defensible is silently dropping the late event, because that is how the busiest window quietly becomes the one with the best connectivity.
Worked solution 30 min
- Write the window semantics as one sentence — half-open
[t, t+600), anchored at event timestamps — at the top of the function, and make the comparisons match it. - Implement dedupe, sort, then the two-pointer scan with the multiset and the explicit
distinctcounter. - Build a fixture for one reader: twelve events, one container seen three times inside a window, one duplicate
dedupe_key, and one event at exactlyt+600. - Hand-compute the expected maximum and window start, then run.
- Re-run with the fixture shuffled and with the duplicate resent five more times, confirming the answer is unchanged.
Follow-up
- Two events for one container arrive from two readers a second apart. Is that congestion, a misread, or one container passing two portals?
- You need this continuously rather than as a daily batch. What changes, and where does the memory go?
- A device's clock is three hours fast. What does that do to this result, and how would you detect it from
clock_offset_ms?
Add a required unit column and uniqueness to a live ledger
inventory_movement holds 4.2 billion rows on PostgreSQL 15 and takes about 2,000 inserts per second around the clock. Two changes are required with no downtime: add uom_code TEXT NOT NULL, whose truthful value must come from the conversion factors in force at each row's occurred_at rather than from today's item master; and make idempotency_key UNIQUE, although duplicates predating the retry fix already exist. Give the ordered steps, the lock each one takes, its expected duration and its rollback.
Approach
- ADD COLUMN with a non-volatile DEFAULT has been metadata-only since PostgreSQL 11, so no rewrite, but it still takes ACCESS EXCLUSIVE for an instant. That lock queues behind any long-running reader while blocking every statement behind it, so one 10-minute analytics scan turns a millisecond change into a 10-minute outage. Set lock_timeout to 2s and retry in a loop rather than waiting.
- Reach NOT NULL without a scan under an exclusive lock: ADD CONSTRAINT ... CHECK (uom_code IS NOT NULL) NOT VALID, which is instant, then VALIDATE CONSTRAINT, which takes only SHARE UPDATE EXCLUSIVE and runs concurrently with inserts, then SET NOT NULL, which PostgreSQL 12 and later proves from the validated check and so skips its own scan. Drop the redundant check afterwards.
- The truthful backfill is the long pole and must read the versioned conversion factor as of each row's occurred_at, not the current item master, or a supplier moving a case from 12 units to 10 retroactively reinterprets last quarter. Batch by primary-key range, a few thousand rows per transaction, with a sleep between batches and a resumable cursor in a control table. At 5,000 rows a second, 4.2 billion rows is roughly ten days of throttled work: that is a schedule, not a failure, and it must not block the deploy.
- The UNIQUE index cannot be built until the duplicates are gone. CREATE UNIQUE INDEX CONCURRENTLY aborts on the first violation and leaves an INVALID index behind that is ignored by queries but still carries update overhead on every insert, and it can only be removed with DROP INDEX CONCURRENTLY. So: find the duplicates, resolve each one as a compensating reversal rather than a DELETE, since every duplicate key is a double-applied movement and deleting it fixes the constraint while leaving the position wrong.
- Then CREATE UNIQUE INDEX CONCURRENTLY, which makes two passes over the table and waits out concurrent transactions, so budget hours here, and note it cannot run inside a transaction block: a migration framework that wraps every step in BEGIN will fail on this one. Promote it with ALTER TABLE ... ADD CONSTRAINT ... UNIQUE USING INDEX, which is a brief ACCESS EXCLUSIVE.
- Question whether the constraint should cover all history. At roughly 40 bytes per key plus overhead, a btree over 4.2 billion rows is in the hundreds of gigabytes, while a retry never arrives a year later. A partial unique index bounded by a fixed occurred_at literal, rotated forward on a schedule, gives the same protection for a fraction of the size, and the predicate must be a literal because now() is not immutable.
Follow-up
- Sequence the application deploy against these steps. When exactly may the code start writing uom_code, and when may it start requiring it?
- The backfill is on day six and the conversion-factor table is itself corrected. What happens to the rows already written?
- How do you roll back after SET NOT NULL is in place and half the traffic depends on the column?
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?
Explain how you would structure an caching strategy (such as an LRU Ca…
Explain how you would structure an caching strategy (such as an LRU Cache) combining hash maps and doubly linked lists for fast operations.
Approach
- Fix the scope first: who calls this, how often, and what they do when it fails.
- Name the read and write paths separately; they rarely have the same bottleneck.
- Name the failure you are designing for, then the recovery path.
Follow-up
- How does this behave when that dependency is down for an hour?
- What would you drop to keep the system up under load?
What strategies would you use to decouple microservices and handle hig…
What strategies would you use to decouple microservices and handle high-throughput streaming data in automotive telemetry platforms?
Approach
- Name the failure you are designing for, then the recovery path.
- Fix the scope first: who calls this, how often, and what they do when it fails.
- Choose a partition key and say what query it makes expensive.
Follow-up
- What would you drop to keep the system up under load?
- How does this behave when that dependency is down for an hour?
How would you design an end-to-end diagnostic monitoring system that t…
How would you design an end-to-end diagnostic monitoring system that tracks anomalies in real-time hardware feeds?
Approach
- Name the failure you are designing for, then the recovery path.
- 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?
- How does this behave when that dependency is down for an hour?
What is the time complexity of binary search on a sorted array versus …
What is the time complexity of binary search on a sorted array versus searching through an unsorted dataset?
Approach
- Clarify what is being asked and what a complete answer contains.
- Say what you would check first and why it is the highest-information step.
- Work from the requirement backwards to the design.
Follow-up
- How would you know your answer was wrong?
- What assumption would you test first?
Allocate stock on hot item-node pairs without overselling
Allocation peaks at 3,000 writes per second and 70 percent of it lands on about 50 (item, node) pairs during a promotion, roughly 42 per second against a single position row. The measured allocate transaction is 4 ms. Design the call so the availability check and the allocation insert cannot interleave: name the mechanism, state its throughput ceiling on one row, and say what the losing caller sees. Then say what changes when one order line must claim quantity at two nodes atomically.
Approach
- Put the check and the write in one atomic step, in one of two shapes. Pessimistic: SELECT the position FOR UPDATE, verify on_hand minus outstanding held and committed allocations covers the request, insert the allocation and bump version. Optimistic: UPDATE inventory_position ... WHERE key = ... AND version = ? AND qty - reserved >= ?, and test the affected-row count. A read followed by an unconditional insert is the defect, and no isolation level repairs it.
- Do the ceiling arithmetic. The row lock is held from the moment it is taken until commit, so a 4 ms transaction supports roughly 250 allocations per second on one row against the 42 demanded, about six times headroom. That headroom is the entire budget, which is why nothing remote may sit inside the transaction: a 200 ms carrier or rating call drops the same row to about 5 per second, eight times under demand, and the pair queues until the pool is exhausted.
- Be exact about isolation rather than waving at it. Under PostgreSQL REPEATABLE READ a second writer to the same row aborts with a serialization failure the application must catch and retry; under InnoDB REPEATABLE READ a locking read sees the latest committed row and blocks instead, so identical code is safe on one engine and not the other. READ COMMITTED with a locking read or a version-predicated UPDATE is the portable form.
- Define the loser's experience. A serialization failure or a zero-rowcount CAS is an expected outcome, not an error to log and swallow: retry with jittered backoff a bounded number of times, then return a typed insufficiency so sourcing picks another node. Uncapped retries against one hot row are the real outage, so cap attempts and shed.
- Shed without overselling by making the hot key single-writer: route allocations through a log partitioned on hash(item_id, node_id) so one process touches the row and contention becomes queueing instead of lock waiting. The costs are head-of-line blocking per key and duplicate delivery on rebalance, so the consumer still needs the partial unique index on allocation to make a replay fail closed rather than double-claim.
- For two nodes atomically: inside one database it is one transaction taking both position rows in a deterministic order, ascending by primary key, so two lines claiming the same pair cannot deadlock. Across shards no such transaction exists, so it becomes a saga - hold at A, hold at B, release A by a compensating status change on failure - and the pair is briefly held but uncommitted, which is a state the order line's machine must represent.
Worked solution 30 min
- Write both implementations against inventory_position and allocation: SELECT ... FOR UPDATE, and UPDATE ... WHERE version = ? with a rowcount test.
- Compute the single-row ceiling from the 4 ms hold, compare it to 42 per second, then recompute it with a 200 ms external call inside the transaction and state what breaks first.
- Write the concurrency test that fails without protection: N threads allocating against a position of 1, asserting exactly one allocation in status held and exactly one version increment.
- Extend to two nodes in one shard with the lock-ordering rule, then write the cross-shard saga with its compensating release and the line status that represents the half-held state.
Follow-up
- Two lines claim the same two nodes in opposite order and the engine kills one. What is the lock ordering rule, and what does the survivor's retry look like?
- Soft allocations expire. What does the sweeper do, and why is it a correctness component rather than housekeeping?
- Demand on one pair reaches 400 per second. Which of the two mechanisms do you move to, and what do you give up?
Wave release screen slows linearly with the line count
A wave-release screen lists open fulfilment_order_line rows for one node and, per line, its held allocation rows and the on_hand inventory_position quantity for that (item_id, node_id, lot_id, state). p95 is 40 ms for a 25-line wave and 3.4 s for an 800-line wave. The database reports 1,601 statements for the 800-line request, each under 1 ms, and no individual query is slow. Diagnose the cause, give the fix, and state the statement count and p95 you expect afterwards.
Approach
- Read the shape out of the numbers before forming a theory. 1,601 statements for 800 rows is 1 + 2N: one driver query plus two per line. Every statement being sub-millisecond rules out a bad plan, so the time is round trips, not work.
- Check that the arithmetic accounts for the whole regression. 1,600 round trips at roughly 2 ms each is about 3.2 s against a 40 ms baseline, which covers the observed 3.4 s. If it had only covered half, there would be a second defect and the N+1 fix alone would disappoint.
- Identify the two per-row statements by their normalised text: a single-row allocation lookup keyed by order_line_id and a single-row inventory_position lookup keyed by the four-column primary key. Confirm they are lazy relationship loads by removing those two fields from the response and watching the count fall to 1.
- Replace them with two batch statements. Collect the order_line_ids and item_ids from the driver query, then SELECT ... FROM allocation WHERE order_line_id = ANY($1) AND status IN ('held','committed') and SELECT ... FROM inventory_position WHERE node_id = $1 AND item_id = ANY($2) AND state = 'on_hand', and join them into maps in application memory. Three statements per request, independent of wave size.
- Verify the access paths rather than assuming them. The partial unique index on allocation (order_line_id, node_id, lot_id) WHERE status IN ('held','committed') leads with order_line_id, so it serves the first batch. inventory_position's primary key is (item_id, node_id, lot_id, state), so the second batch scans on the item_id, node_id prefix and filters on state; lot_id sits between them and is unconstrained, which is fine for a few hundred keys but is the reason this is a prefix scan rather than a point lookup.
Follow-up
- The screen adds a per-line column showing the last movement's reason_code. How do you fetch that for 800 lines without reintroducing the fan-out?
- A wave can now reach 12,000 lines. At what point does ANY($1) stop being the right tool, and what replaces it?
- Nothing here was a slow query. What monitor would have caught this before an operator did?
For a candidate senior enough that the loop turns on design and judgement rather than on whether the coding round gets finished. Five days build one system properly and then stress it; coding gets a single maintenance day, on the assumption that the risk at this level is an unexamined tradeoff rather than a missed algorithm.
Prepare, practise & reflect
One practical outcome each day. Spend longer where you need it.
0 / 7 done01Numbers before diagrams
- Build your own reference card of the figures you will re-derive all week: bytes for a realistic record, requests per second implied by a given daily active count, and the storage that a year at a given write rate produces. Derive each one rather than copying it, because the derivation is what survives a follow-up.
- Turn one product statement into capacity requirements. From ten million daily users at four writes and forty reads each, state the peak-to-average factor you are assuming and why, then produce peak write QPS, peak read QPS and a year of storage.
- Write the two numbers whose order of magnitude changes the design, the read-to-write ratio and the working-set size against memory per node, and state the threshold at which each one flips your answer.
Deliverable: A one-page numbers card and one worked capacity estimate with every assumption written down.
Practice prompt ↗Practice prompt ↗Worked solution ↗02One system, from requirements to schema
- Spend the first ten minutes producing only functional requirements, non-functional targets with numbers attached, a p99 latency, a durability expectation, a consistency requirement, and an explicit out-of-scope list.
- Define the interface before the boxes: the three or four endpoints, their parameters, what each returns, and which of them are idempotent.
- Write the data model, then write the single access pattern that justifies it, and state what the schema would have to become if the dominant access pattern were the other one.
Deliverable: One design carried to endpoint-and-schema depth, with non-functional targets expressed as numbers and a written out-of-scope list.
Practice prompt ↗Practice prompt ↗03The consistency you are actually buying
- Write out what a client sees under asynchronous replication when its write commits on the leader and its next read is served by a lagging follower, then write the two fixes, pinning that session's reads to the leader for a bounded window or carrying a version token the replica must reach, and the cost of each.
- Work the quorum arithmetic on paper for N of three with W and R of two, and separate what R + W > N does guarantee, that any read set intersects any write set, from what it does not: on its own it is not linearizability, and a sloppy quorum that accepts writes on nodes outside the preference list breaks even the intersection.
- Take two storage choices with different defaults, a single-leader relational store committing synchronously and a quorum-replicated store that converges eventually, and write the specific product behaviour that would be wrong under each, rather than a general statement about which is stronger.
Deliverable: A page separating what quorum overlap guarantees from what it does not, with one concrete product misbehaviour attached to each gap.
Practice prompt ↗Practice prompt ↗04Failure is the design
- For one write path, work through the case where the client times out after the server has already committed, then design the idempotency key: who generates it, how long it is retained, and what the duplicate request returns.
- Express the retry policy as parameters rather than as a word: maximum attempts, base delay, backoff factor, jitter, and which error classes are retried at all. Then state why retrying a non-idempotent write without a key is a correctness bug and not merely waste.
- Compute the fan-out effect on tail latency. If a request waits on ten backends and each independently exceeds its p99 one percent of the time, the chance at least one is slow is 1 - 0.99^10, about ten percent. Then write why independence is the optimistic assumption and what correlates them in practice.
- Name the backpressure mechanism for one queue or one dependency in the design, a bounded queue with shedding or a concurrency limit, and write what the caller is told when it engages.
Deliverable: One write path with an idempotency design, a parameterised retry policy, and a written tail-latency calculation with its assumption named.
Practice prompt ↗Practice prompt ↗Worked solution ↗05Scaling the hot path
- Choose cache-aside or write-through for one read path and write the staleness window each produces, then name the invalidation event and what the system does when that event is lost.
- Design against the stampede: either coalesce requests so only one recomputes a missing key, or refresh early with jittered expiry, and write why identical TTLs on keys populated in the same moment produce a synchronised expiry and a thundering herd.
- Shard one table by a key you choose, then answer the two questions that break the choice: which queries now require a scatter-gather, and what happens to the distribution when one tenant is a hundred times larger than the median.
- Write the cost of adding a node under plain modulo placement, where nearly every key moves, against consistent hashing, where roughly one key in n+1 moves, and state what virtual nodes are for.
Deliverable: A caching and sharding decision for one path, each with its failure mode and its rebalancing cost written beside it.
Practice prompt ↗Practice prompt ↗06Keep the coding hand in, at the bar that applies to you
- Solve one medium problem in thirty minutes, then spend twenty more making it production-shaped: named invariants, validation at the boundary, and errors that distinguish a caller mistake from an internal fault.
- Write the tests you would require of a colleague's version of that function: one for empty input, one for the boundary, and one for the case the implementation is most likely to get wrong.
- Read a piece of your own code from six months ago and write the change you would ask for, phrased as you would actually phrase it in review.
Deliverable: One problem hardened to review standard, with its test list and one written review comment.
Practice prompt ↗Practice prompt ↗07Defend it while being interrupted
- Run a forty-five-minute design mock with an interviewer briefed to change a requirement halfway, a tenfold traffic increase or a new strict consistency requirement, and to push on one number you estimated.
- Rehearse the two sentences a senior loop is listening for: naming the tradeoff you are choosing against and why, and saying what you would measure to learn that the choice was wrong.
- Prepare the design you regret: a real decision, the constraint that produced it, what it cost, and what you changed afterwards.
Deliverable: Mock notes recording how the design changed under the new requirement, plus a written account of one regretted decision.
Practice prompt ↗Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
A migration is a cost you chose to pay, not an achievement. The story is what the old system made expensive, what you measured before committing, what kept serving traffic during the cutover, and what you would have done if the numbers had come back flat. Without those, a rewrite reads as taste.
How do you handle edge cases and memory management when implementing c…
How do you handle edge cases and memory management when implementing custom data structures in C++ or Java?
Approach
- State the situation in two sentences and spend the rest on the reasoning.
- Name the disagreement and how you resolved it with evidence.
- Pick a story where you made the decision, not one where you watched it.
Follow-up
- What would you do differently if you ran that again?
- How did you know your change caused the improvement?
Describe a situation where a piece of software or code failed. What st…
Describe a situation where a piece of software or code failed. What steps did you take to debug the root cause and ensure long-term prevention?
Approach
- Pick a story where you made the decision, not one where you watched it.
- Give the blast radius: what could have broken, and what you measured.
- Close with what you would do differently, concretely.
Follow-up
- What would you do differently if you ran that again?
- What did you decide not to do, and why?
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
How do you handle edge cases and memory management when implementing custom data structures in C++ or Java?
- 02
Describe a situation where a piece of software or code failed. What steps did you take to debug the root cause and ensure long-term prevention?
- 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 General Motors (GM) interview guide?
No. It is PracHub's own research and practice material for the Software Engineer role at General Motors (GM). Rounds and questions reflect what candidates have reported, not a process General Motors (GM) has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗How difficult are the technical coding questions at General Motors (GM)?
Technical coding assessments generally range from easy to medium difficulty compared to standard industry benchmarks. The emphasis is placed heavily on clean code formatting, efficient logical execution, proper edge-case handling, and clear verbal communication during problem-solving.
PracHub interview research ↗How critical is the STAR method during behavioral interviews?
The STAR method is absolute key. General Motors (GM) interviewers explicitly evaluate answers based on Situation, Task, Action, and Result. Candidates who omit clear personal actions or quantifiable outcomes frequently receive lower behavioral evaluation ratings.
PracHub interview research ↗Does General Motors (GM) require domain experience in automotive systems?
Not necessarily. While domain knowledge in automotive protocols or embedded systems is beneficial for specialized vehicle software groups, enterprise IT and digital platforms hire heavily based on general computer science fundamentals, language proficiency, and software design capabilities.
PracHub interview research ↗How long does the hiring process typically take from application to offer?
The timeline varies by department, typically ranging between two to six weeks. Candidates usually complete an initial screening and online assessment, followed by virtual panel interviews, with offer decisions communicated shortly thereafter.
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-22 - 02PracHub Software Engineer practice ↗
Cross-company practice questions for this role.
platform · Accessed 2026-09-22 - 03PracHub interview preparation framework ↗
The framework the preparation plan follows.
platform · Accessed 2026-09-22