Interview guides · Land O'Lakes
Land O'Lakes Software Engineer Interview Questions & Guide 2026
Take-home style practice for Land O'Lakes software engineer candidates: a small service, a fixed budget, a written tradeoff.
The 60-second brief
A Software Engineer at Land O'Lakes sits at the intersection of traditional agricultural expertise and cutting-edge digital transformation. As a member of a cooperative with a massive footprint, your work directly influences the efficiency of supply chains, the precision of agricultural data, and the digital tools that empower farmers and members across the country. This role is not just about writing code; it is about building scalable, reliable systems that support a complex, real-world business environment.
Ask how many rounds there are and what each one is before you plan your weeks, because the composition is what your hours should follow. A loop with three coding rounds and one short design conversation deserves a different split from the reverse, and whoever schedules it will usually say plainly which it is.
Focus areas
- Reserve inventory without overselling under concurrent allocation
- Make every external side effect safe to retry
- Fold late, out-of-order scan events by event time
Overview
You will encounter a unique blend of challenges, ranging from optimizing data-heavy applications to solving operational problems at physical manufacturing sites. The environment is collaborative and pragmatic, favoring engineers who can bridge the gap between technical implementation and business utility. Whether you are working on internal tools or customer-facing platforms, your contributions are critical to maintaining the operational excellence that defines Land O'Lakes.
The culture at Land O'Lakes is frequently described as friendly and professional. Expect your interviewers to value clear communication and a genuine interest in how your technical skills solve industry-specific problems.
The Land O'Lakes interview process
Land O'Lakes candidates report 5 rounds · ≈ 4-6 weeks. The stages below are what candidates describe, not a published process.
Initial Screening
The 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 Evaluation
The 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 Evaluation
Your first answer is not really what is scored. It buys the follow-up questions, and those decide the round. An interviewer with fifteen minutes takes one thread and pushes on it four or five times, so a story you can only tell at a single level of detail collapses under the third why. That is an argument for fewer stories known deeply rather than one prepared per prompt. Four or five pieces of work you can still explain down to the code you changed and the argument you had about it will cover nearly anything asked in this round.
What to demonstrate
- Whether a story holds as the questioning moves from what you did to why that instead of the alternative, and then to what you would change knowing what you know now
- Whether you can re-cut a project to answer the question actually asked rather than delivering a rehearsed block that answers an adjacent one
- Whether your level of detail is chosen rather than habitual: going down to the schema when the question is about the data model, staying out of it when the question is about the person who disagreed with you
How to prepare
- Pick four projects and write the chain out four levels deep for each: what you did, why that, why not the alternative, and what would have to be true for the alternative to have won. Where you cannot reach the fourth level, you have a placeholder rather than a story
- Have someone ask why three times in a row on a single thread with nothing else added, and mark the point where you start repeating a sentence you already said. That point is where the interviewer stops learning anything
- Build a one-page index instead of an answer bank: the common prompts in this round (disagreement, a failure that was yours, thin requirements, a deadline you missed, work you inherited) mapped to which of your four projects you would use for each, so the choosing is done now rather than while an interviewer waits
Collaborative Assessment
What this round decides is narrow: whether you can produce code that runs and is correct on inputs nobody showed you. An elegant solution that does not compile scores below a plain one that does, so write a correct brute force first, say out loud that you know its cost, and improve it with the working version still on screen. What separates strong answers is who finds the broken case. Trace your own code against an empty input, a single element, and duplicate keys before you say you are finished, because being told is far more expensive than noticing.
What to demonstrate
- Whether degenerate inputs get checked without being asked for: an empty collection, one element, every element equal, and the extreme value the input type allows
- Whether the complexity you state matches the code you actually wrote, including a sort or a copy sitting inside a loop
- Whether the finished answer is verified against the worked examples before you call it done, rather than assumed correct because the code reads correctly
How to prepare
- Take five problems you have already solved and, without running anything, write down what each returns for empty input, a single element, and all-duplicates. Then run them and count how many you predicted wrong.
- Drill the brute force as its own skill: on ten problems, write only the obviously-correct slow version and time how long it takes to get it passing. If that is more than a few minutes, that is what to practise, not the optimal version.
- Add a fixed last step before you submit anything, reading only the loop bounds and the initial value of each accumulator, which is where most off-by-one errors live
Final Decision
Coding rounds mostly set a floor. They decide whether you clear the bar, not where you land on the ladder. Level tends to come out of the design discussion and the ownership stories, so the question worth auditing beforehand is whether the scope you describe matches the scope of the job. Work that stops at your own service, or a story whose hard part was writing the code rather than getting several people to agree on an interface, reads a level below where you think you are interviewing, and that gap is usually resolved downwards.
What to demonstrate
- Whether the largest thing you describe owning ran end to end — the decision, the migration path, the rollout, and what you did when it went wrong — or stopped at the change you merged
- Whether design answers include what you would not build, what you would defer, and what you would measure before committing, rather than only what the boxes are
- Whether a disagreement in a story was settled with something checkable — a benchmark, a prototype, a written proposal — instead of by seniority or by waiting it out
- Whether you can say which calls you made alone and which you escalated, and why the line sat where it did
How to prepare
- Write your largest piece of owned work as a timeline of decisions — who decided what, when, and what you did when the plan broke — then delete every sentence whose subject is "we" and see how much survives
- Take one system you know well and drill the migration answer: how old and new paths run side by side under live traffic, how you compare their outputs, what the rollback is once writes are going to both, and which step you would not automate
- Map each line of the ladder in the job posting to a specific thing you have done, find the line you cannot support, and prepare the closest evidence you have plus an honest account of the gap
Where candidates lose points
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.
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.
Comparing floating-point values for equality, or holding money in them
Binary floating point cannot represent 0.1 exactly, so repeated addition drifts and an equality check fails on values that are mathematically equal. Store currency as integer minor units or a decimal type, and compare floats against a tolerance you chose for a stated reason.
Hardcoding to the sample inputs
Solve the stated problem rather than the two examples; special-casing a literal to make a sample pass is obvious immediately and reads as either a misunderstanding or an attempt to fake progress. If you genuinely cannot generalise yet, say which part is a stub and what would replace it.
Questions & practice
Re-sum a movement ledger and report positions that disagree
Easy · aggregation · reconciliation · append-only ledger
inventory_movement holds 3 billion immutable rows: item_id, node_id, lot_id (NULL when the item is lot-untracked), state, delta_qty (signed int64 in the item's smallest transacting unit), uom_code, and reversed_by. inventory_position holds one row per (item_id, node_id, lot_id, state) with qty, version and last_movement_id, using lot_id = 0 as the sentinel for untracked. Up to 80 million distinct keys. Report every key whose summed movements disagree with the stored qty, while writes continue. State your time and space bounds and how you bound the comparison.
Approach
- Fix the key first.
inventory_movement.lot_idis NULL for untracked items andinventory_position.lot_idis 0, so the group key is(item_id, node_id, COALESCE(lot_id, 0), state). Getting this wrong does not error — it silently produces two groups that each look like a variance, and the report becomes noise nobody reads. - Cut the ledger at a watermark, but do not take
W = max(movement_id)at the start of the scan. A sequence hands out an id before the inserting transaction commits, so at the instant you read that maximum there are ids below it still in flight and invisible to your snapshot. Summingmovement_id <= Wmisses them on this run, and advancingreconciled_through_movement_idtoWmakes the miss permanent: every later incremental run starts above those ids and they are never summed again. That is a hole in the ledger's own re-derivation, not a transient skew. - Take a watermark that is provably settled instead. Bound write transactions with a statement or transaction timeout so 'the longest write' is a number T, record
(observed_at, max_movement_id)samples periodically, and use asW_safethe largest sampled id whoseobserved_atis older than T — every id at or below it has committed or rolled back. Sum rows withmovement_id <= W_safe, compare only against position rows withlast_movement_id <= W_safe, treat anything newer as a write that raced you rather than a variance, and advancereconciled_through_movement_idonly toW_safeso the next run starts there instead of re-reading 3 billion rows. If the ledger carries a commit timestamp, cutting on that is the same guarantee without the sampling table. - Sum
delta_qtyas int64, and include reversal rows.reversed_byis a back-pointer for audit, not an exclusion filter: an original of +10 and its reversal of -10 must both be summed to reach 0. Excluding the original while keeping the reversal produces -10 and a false variance on every corrected key. - Guard the denomination rather than trusting it.
uom_codeis stored per row because pack factors change over time, so reject — do not sum — any row whoseuom_codeis not the item's smallest transacting unit, and report those keys separately. A mixed-denomination sum is arithmetically meaningless and looks exactly like a real variance. - Hash aggregation is O(n) time and O(distinct keys) space: 80 million entries at roughly 40-56 bytes each in a typical runtime is 3-5 GB, so quote the number. The bounded-memory alternative is an external sort-merge on the group key — O(n log n) comparisons, resident memory bounded by the merge fan-in rather than by key count, and it streams — or hash-partition by
hash(item_id) % Pand run P independent passes for 1/P of the peak.
Follow-up
- A key shows a variance of exactly one pick, repeatedly, at one node. What do you look at first, and what would distinguish a duplicate movement from a missed one?
- The job takes six hours and the variance report is stale by the time anyone reads it. How would you make it incremental without losing the guarantee that it re-derives from the ledger?
- Who writes the correcting movement, and what reason code does it carry?
Parse partner quantity and date segments without floating point
Easy · parsing · unit-of-measure · time zones
A partner sends a delimited flat file whose segments look like QTY+17:4.500:CS and DTM+232:20260317. Quantities carry up to three decimal places in a named unit; each item has an integer pack factor to eaches that is versioned by effective date. Produce observation_event rows with an integer quantity in eaches, the unit recorded, and occurred_at resolved, rejecting any line whose conversion is not exact. Files reach 4 GB. Single pass, no floating point. State your bounds and what you do with the zoneless date.
Approach
- Scan with indices, not a regex. Find the segment terminator, then split the payload on
:by scanning forward and slicing — O(bytes) with no backtracking and no per-line allocation beyond the output row. A regex with nested quantifiers over 4 GB is where this job silently becomes CPU-bound. - Parse the decimal as an integer mantissa plus a scale.
4.500becomes(mantissa = 4500, scale = 3): accumulate digits into an int64, count digits after the point, and never call the runtime's float parser. Reject a mantissa that would overflow int64 when multiplied by the pack factor, checking before the multiply rather than after. - Convert by integer arithmetic and demand exactness:
numerator = mantissa * factor; emitnumerator / 10^scaleonly whennumerator % 10^scale == 0, otherwise reject the line with a reason code. Worked:4.500 CSat 12 eaches per case gives 54000/1000 = 54, exact.0.125 PLat 36 eaches per pallet gives 4500/1000 = 4.5, which is half a sealed unit and must fail at the boundary, not round three services away. - Select the pack factor by the file's effective date, not by the current item master. Factors are properties of a point in time — a supplier moving a case from 12 to 10 must not reinterpret last quarter's file — so the lookup is a range query on the factor's validity interval.
20260317carries no zone and no time. Attach the IANA zone of the node the segment refers to, captured at ingestion, because it cannot be recovered downstream; then pick and document one convention for a date-only field (start of local day is the usual choice) and store the raw local string beside the resolved instant so the assumption stays visible. Complexity: O(total bytes) time, O(longest line) working space, one pass.
Follow-up
- The same file is re-sent in full tomorrow with three lines changed. What makes the second ingest a no-op for the unchanged lines?
- A line names a unit that is not in the ladder for that item. Reject the line, reject the file, or quarantine it — and who finds out?
- The partner starts sending an offset like
+0100on some dates. Does that change your storage decision for a future appointment as well as a past event?
Find the busiest ten-minute window per reader after deduplication
Medium · sliding window · event time · 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.
Expected result: The container seen three times contributes 1 to the distinct count; the resent duplicates contribute 0; the event at exactly t+600 falls in the next window; the reported start is an event timestamp, not a rounded clock boundary.
Verify it yourself
- A brute-force O(p^2) scan over all event-anchored windows agrees on a 1,000-event fixture.
- The
distinctcounter equals the multiset's key count at every step — assert it in the loop while testing. - Sorting by
received_atinstead ofoccurred_atchanges the answer on a fixture with a backlogged device, which is the test that proves event time is doing work.
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?
Decide whether available-to-promise is derived or stored
Hard · denormalisation · read path · cache staleness
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.
Worked solution (40 min)
- Load a position with 2,000 open allocations and measure p99 for the derived read at 400 requests per second against the same read from a maintained available_qty column.
- Add available_qty, backfill it per key under the same lock the allocator takes, and write the re-sum query that proves it equals qty minus held and committed.
- Run both read paths in shadow for a fixed window, logging every disagreement with its key and magnitude.
- Point the browse read at a replica with a 30-second TTL and the allocate read at the primary, then measure how often the browse figure is higher than the allocatable truth.
Expected result: The maintained column serves the read in a single primary-key probe inside the 20 ms budget at 4,000 reads per second, the derived path misses it on the hot key, and shadow comparison shows zero disagreements on the primary path while the replica path is stale in the low-availability direction only.
Verify it yourself
- p99 for the maintained read is flat as open allocations on the key grow from 10 to 2,000; the derived read's is not.
- The re-sum finds zero drift after a sustained allocate-and-release workload.
- Every observed browse-versus-primary disagreement is an under-report, never an over-report.
- Killing an allocator mid-transaction leaves available_qty and the allocation rows consistent after the expiry sweeper runs.
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?
Add a required unit column and uniqueness to a live ledger
Hard · online migration · lock timeouts · unique constraints
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?
How do you interpret the output of this specific code snippet?
Medium
Approach
- State your assumptions explicitly before working the problem.
- Say what you would check first and why it is the highest-information step.
- Clarify what is being asked and what a complete answer contains.
Follow-up
- How would you know your answer was wrong?
- What assumption would you test first?
How do you balance the need for rapid feature delivery with long-term system stability?
Medium
Approach
- Work from the requirement backwards to the design.
- Clarify what is being asked and what a complete answer contains.
- State your assumptions explicitly before working the problem.
Follow-up
- What assumption would you test first?
- How would you know your answer was wrong?
What steps would you take to diagnose a system outage in a production environment?
Medium
Approach
- Clarify what is being asked and what a complete answer contains.
- State your assumptions explicitly before working the problem.
- Say what you would check first and why it is the highest-information step.
Follow-up
- What assumption would you test first?
- How would you know your answer was wrong?
Evolve an availability contract without breaking floor devices
Hard · versioning · backward compatibility · units
GET /availability currently returns {item_id, node_id, qty}, where qty is denominated in whatever the item master says today - eaches for some items, cases for others. Three clients consume it: a checkout service you deploy, a partner integration on a twelve-month release cycle, and handheld firmware on the warehouse floor you cannot force-upgrade. You must expose the unit explicitly, split qty into on_hand, available and reserved, and add a new inventory state. Deliver the field plan, the version negotiation mechanism, the deprecation path, and how you prove nobody still reads the old field.
Approach
- Start from the rule that survives contact with un-upgradable clients: never change the meaning or the denomination of a live field. qty keeps precisely its current semantics forever, and the new truth ships as new fields - available_qty with an explicit uom_code, plus the on_hand and reserved split - so the handheld keeps reading exactly what it always read.
- Be precise about which additions are safe. New fields are safe only if readers ignore unknown ones, so state tolerant-reader expectations in the contract. Adding a value to an existing enum is not safe: a client switching exhaustively over inventory state breaks on the new state. Either declare the enum open in the current version with a mandated fallback branch, or carry the new state in a parallel field that old clients never see. Choose one and write it down.
- Pin versions per client credential rather than per deploy - an account-level default, overridable per request by a version header - so nobody is upgraded by your release. Name the cost honestly: you now serve N shapes from one code path, so cap N with a published support window and a rule that a frozen version gets no changes, not even helpful ones.
- Prove the dependency instead of assuming it. Count reads of the deprecated field per client credential, then send Sunset (RFC 8594) with a date and a deprecation link on a horizon longer than the slowest client's release cycle - a twelve-month partner cycle sets the floor, and the handheld fleet may not clear it at all, which is a planning input rather than a surprise.
- Guard it in CI with consumer-driven contract tests that fail on any field removal, type change or enum widening in a frozen version. A review checklist does not survive a rushed release; the test does.
- Close the loop on the unit trap explicitly: when a supplier changes a case from twelve to ten, the bare qty must not silently re-denominate. Carry the conversion factor's version alongside the number, and state that values already served keep the factor in force when they were computed.
Worked solution (40 min)
- Write the current and target payloads side by side and mark every field as unchanged, added or frozen - with nothing in a fourth category.
- For the new inventory state, write what each of the three clients does when it encounters an unrecognised value today, and pick open-enum or parallel-field accordingly.
- Specify the negotiation: the credential-level default, the override header, the error for an unknown or retired version, and the support window.
- Design the usage telemetry that tells you which credential still reads qty, and state the threshold at which you would send Sunset.
- Write the CI contract test that fails on a removal, a type change or an enum widening in a frozen version, and run it against the proposed change.
Expected result: An additive plan where qty is frozen rather than redefined, the unit travels on every new quantity field, enum evolution is settled explicitly, versions are pinned per credential, and the deprecation date is derived from measured usage plus the slowest client's release cycle.
Verify it yourself
- A replay of captured handheld requests against the new build produces byte-compatible values in every field that firmware reads.
- A client pinned to the old version never sees the new state value in any field it parses.
- The contract test fails when someone changes qty's type or adds an enum value to the frozen version.
Follow-up
- A client sends a version header you retired last quarter. What do you return, and what will their code do with it?
- You need one breaking change on one endpoint. How do you ship it without versioning the whole API?
- The Sunset date arrives and the handheld fleet still reads the deprecated field. What are your options, in order of cost?
Two committed allocations against a position holding one unit
Hard · concurrency · compare-and-set · isolation levels
Six oversells in a week, all on the twenty hottest item-node pairs, all inside promotion minutes. In one case inventory_position.qty was 1 and two allocation rows exist with qty 1 and status 'committed', created 4 ms apart; both transactions committed cleanly. The code reads availability, then issues UPDATE inventory_position SET qty = qty - :n, version = version + 1 WHERE item_id = :i AND node_id = :d AND lot_id = :l AND state = 'on_hand' AND version = :v, then inserts the allocation. A 200-thread load test across 50,000 SKUs never reproduces it. Find the defect and prove it.
Approach
- Reconstruct from the data before reading the code. Pull inventory_movement and allocation for that key ordered by identifier with the version values involved. Two commits that both proceeded from version v prove the compare-and-set did not gate anything; a single row correctly stepping v then v+1 with an oversell anyway would instead send you to the read path.
- Name the defect: the affected-row count of the conditional UPDATE is never tested. When version has moved the UPDATE matches nothing and affects zero rows, but control flow continues into the INSERT and the transaction commits, so the WHERE version = :v clause is decoration. A compare-and-set only protects you if the zero-row outcome changes what happens next.
- Explain why the load test is silent. With 200 threads spread over 50,000 SKUs, the chance that two allocators land on one row inside the UPDATE's execution window is negligible, while production traffic concentrates on twenty rows. Pin the same test to a single SKU with a position of 1 and N threads, assert that exactly one allocation survives, and it fails on the first run. That test is the proof, and it belongs in the suite.
- Rule out the decoy rather than ignoring it. If availability is read from a replica, lag peaks exactly when write volume does, so the read is often stale. But a stale read only mis-sizes the attempt: with a working compare-and-set the write still fails and retries, so the replica is a latency and retry-rate problem, not the oversell. Say that explicitly instead of fixing two things and learning nothing.
- Fix and then choose the mechanism deliberately. Test the rowcount; on zero rows re-read the position from the primary and retry with bounded attempts and jittered backoff, and never reuse the version captured before the failed attempt. Compare-and-set is lock-free but burns retries under contention on a single hot row; SELECT ... FOR UPDATE inside a short transaction queues writers predictably and is usually calmer for twenty hot keys; above that, funnel allocations for one item-node key through a single writer and accept the added latency.
- State what the isolation level does and does not do, since identical code is not equally safe everywhere. On PostgreSQL READ COMMITTED, which is the default and what this deployment runs, the second UPDATE blocks on the row lock, re-evaluates its predicate against the newly committed row, matches nothing and affects zero rows; control flow falls into the INSERT and the transaction commits, and that is the oversell. InnoDB's REPEATABLE READ ends the same way for this statement, because an UPDATE takes a current read rather than the snapshot read: it blocks, applies against the latest row, affects zero rows and does not abort. PostgreSQL REPEATABLE READ is the one that does not oversell, and not because the code is right: it raises SQLSTATE 40001 on a row a concurrent transaction updated after the snapshot, which aborts the whole transaction, so the unchecked INSERT can never commit. That is failing closed on an exception no handler anticipated, on one of the three configurations, at the cost of an error the caller has to catch and retry. Two of three commit the oversell, so the rowcount check is not optional, and the invariant belongs in a continuous monitor as well as in a test: assert per key that the sum of allocation.qty in status 'held' or 'committed' does not exceed the position quantity, and let the reconciliation job re-sum movements into positions and raise a variance when they disagree.
Follow-up
- An allocation must span two positions atomically. What changes in your mechanism, and what if the two live in different shards?
- Under the retry design, what stops a hot key from livelocking when fifty allocators all retry together?
- The six oversells are already promised to customers. What is the compensating path, and what does it write to inventory_movement?
Suggested practice checklist
Roughly ninety minutes on weeknights with one longer weekend block. The plan cuts scope rather than compressing everything, on the assumption that one thing finished per night beats four half-started.
Day 1 — Fix the scope and take a cold baseline
- Read the role description and write the three things the loop will almost certainly test, then write an explicit not-doing list and keep it visible all week.
- Take one twenty-five-minute coding problem and one fifteen-minute design prompt cold, and write the single sentence naming what blocked each, because those two sentences decide where the remaining evenings go.
- Set the week's rule: one thing finished every night, including the night you only have forty minutes.
Deliverable: A one-page scope with a not-doing list and two cold attempts, each carrying one sentence on what blocked it.
Day 2 — One pattern, written three times from blank
- Choose the single pattern most likely to appear in your loop and write it three times from an empty file rather than editing the previous attempt.
- On the third pass, write the invariant as a comment before the loop body and the complexity before the first line of code.
- Stop at ninety minutes even if the third version is imperfect, and write the one thing you would fix given another hour.
Deliverable: Three independent implementations of the same pattern plus a note on what changed between them.
Day 3 — One design, only to the depth you can defend
- Take one system shape and go only as far as requirements, interface and data model, refusing to draw a box you could not survive a follow-up about.
- Attach one number to each non-functional requirement, deriving it rather than asserting it, and write the assumption the number rests on.
- Write the one tradeoff you are choosing against and the observation that would make you reverse it.
Deliverable: One design at interface-and-schema depth with derived numbers and one written reversible tradeoff.
Day 4 — Only the fundamentals you will have to defend
- Write, in under two hundred words each, the answers to the two questions that follow almost any implementation: why this structure and not the obvious alternative, and what happens to this code at a hundred times the input.
- Write what an index actually costs: faster lookups on the indexed columns against a write that now maintains a second structure, plus the cases where the planner declines to use it anyway, low selectivity, or a predicate wrapping the column in a function.
- Delete any answer you cannot deliver aloud in under a minute, since an answer that needs reading is not an answer you have.
Deliverable: Three written answers, each under two hundred words and each timed aloud.
Day 5 — Your own work, timed
- Write a ninety-second and a four-minute version of your main project and time both aloud rather than reading them.
- Prepare the two follow-ups that always come: what you would do differently, and how you knew it worked.
- Put one number in the first sentence and be ready to say exactly where it came from and what it excludes.
Deliverable: Two timed narratives with one defensible number in the opening line.
Day 6 — The one full rehearsal, in the weekend block
- Run a sixty-minute mock covering a coding round and a design round in one sitting with no break, because sustained attention is the thing evenings have not trained.
- Immediately afterwards, and before hearing any feedback, write the three moments you lost the thread.
- Spend the rest of the block only on those three moments, and on nothing you merely feel shaky about.
Deliverable: Mock notes naming three failure moments with a specific fix written under each.
Day 7 — Taper
- Write the twenty-minute warm-up you will actually do on the morning: one problem you can already solve from a blank file, one design you can narrate, and nothing you have never seen.
- Re-read only your own notes from this week and open no new material.
- Write the logistics down: the editor or shared document you will be working in, whether execution and lookups are permitted, and the sentence you will use when you do not know something.
Deliverable: A one-page card holding the design structure, the project numbers, and the logistics.
Bounding a vague ticket before writing code
When the requirements were thin, the interesting part is how you fenced the problem off: the assumption you wrote down, who you got to confirm it, the narrow version you shipped first so the rest stayed cheap to change. Guessing and being right is luck. Guessing in writing, where someone could correct you, is method.
Tell me about a time you had to work with a difficult team member.
Medium · behavioural and engineering judgement
Approach
- Name the disagreement and how you resolved it with evidence.
- State the situation in two sentences and spend the rest on the reasoning.
- Give the blast radius: what could have broken, and what you measured.
Follow-up
- What did you decide not to do, and why?
- How did you know your change caused the improvement?
Describe a time you had to explain a complex technical issue to a non-technical stakeholder.
Medium · behavioural and engineering judgement
Approach
- Give the blast radius: what could have broken, and what you measured.
- Close with what you would do differently, concretely.
- Pick a story where you made the decision, not one where you watched it.
Follow-up
- What did you decide not to do, and why?
- How did you know your change caused the improvement?
Conte-me sobre um desafio profissional que você enfrentou e como lidou com ele (Tell me about a professional challenge you faced and how you handled it).
Medium · behavioural and engineering judgement
Approach
- Give the blast radius: what could have broken, and what you measured.
- 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 did you decide not to do, and why?
- What would you do differently if you ran that again?