cswork / libraryOpen practice workspace

Interview guides · ALDI

ALDI Software Engineer Interview Questions & Guide 2026

Timed coding practice for ALDI software engineer candidates: full attempts under a clock, then scored against a rubric.

The 60-second brief

As a Software Engineer at ALDI, you will contribute to the technical backbone of one of the world’s most efficient retail operations. This role is pivotal in maintaining the high-performance systems that support ALDI’s global supply chain, inventory management, and customer-facing digital platforms. You are not just writing code; you are building solutions that optimize complex, large-scale retail processes that demand both reliability and precision.

Treat capacity estimation as a conversion skill rather than a table to memorise: turn a user count and an action rate into requests per second and bytes per day, then name the component that number breaks first. The figure only matters if it changes the design.

Focus areas

  • Make checkout idempotent from cart to capture
  • Design order state machines with explicit compensating transactions
  • Keep flash-sale writes off one contended row

Overview

The work environment at ALDI is characterized by structure, efficiency, and a focus on practical engineering outcomes. You will work within teams that value technical competence and the ability to solve real-world problems over abstract algorithmic puzzles. Because ALDI operates with a lean and highly disciplined business model, your contributions as a Software Engineer have a direct, tangible impact on the company’s ability to deliver value to millions of customers.

The ALDI interview process

ALDI candidates report 4 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.

Initial Screening

The person on this call usually cannot evaluate your code and does not need to. They write a short paragraph, and that paragraph is what a hiring manager skims when deciding who to put on your loop. So the test is not whether your work was hard, it is whether a non-engineer can repeat it correctly. Name systems by what they did rather than by their internal codename, give each project a shape (what was breaking, what you changed, what happened after), and keep the whole walkthrough near ninety seconds. Depth that cannot survive a paraphrase reads as vagueness.

What to demonstrate

  • Whether a non-engineer can restate your projects without distorting them, since their paraphrase is what travels to the hiring manager, not your sentences
  • Whether each project has a shape rather than a stack list: the failure or constraint, the change you made, the result and how it was measured
  • Whether you can say what was yours inside a team project without either inflating it or disappearing into the plural

How to prepare

  • Rewrite each headline project as two sentences with no internal system names and no acronyms outside your company, then say them to someone outside engineering and have them repeat them back. Fix whatever came back wrong
  • Attach one measured number to each project: the baseline, the change, and the window it was measured over. Where nothing was ever measured, say that plainly rather than reaching for a plausible percentage
  • Time the background walkthrough against a clock. If it runs past two minutes, compress the earliest role to a single clause and spend the recovered time on the most recent one

Technical Assessments

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

Behavioral Interviews

Many 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

Final Assessment

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 a payment timeout as a failure and retrying with a fresh idempotency key

An HTTP timeout carries no information about whether the provider processed the request; the call may have succeeded and the response been lost in either direction. Retrying with a new key creates a second authorization or a second capture, and the customer sees two pending amounts on their statement — which is also the version of this bug that reaches support rather than a dashboard. The correct shape is to persist the intent with its key before the call, retry with the same key, and if the operation is one the provider does not make idempotent, resolve it by querying for your own reference before issuing anything else. The structural requirement is that the outcome type has three values, not two: a boolean return type makes the bug unavoidable no matter how careful the caller is.

Representing money as floating point, or rounding tax at a different grain than the total is computed at

Binary floating point cannot represent most decimal fractions exactly, so sums over thousands of lines drift and two services computing the same total from the same inputs disagree in the last place. Rounding grain is the subtler half: rounding tax per line and summing gives a different total than computing tax on the order subtotal and rounding once, and the difference is a cent or two per order — small enough to pass every test and large enough to make the payment provider's amount, the invoice and the finance ledger disagree at month end. Store minor units as integers, and treat the rounding rule as a specification that is applied at exactly one point, propagated everywhere else, and pinned by a CHECK constraint on the order total; which rule is required is jurisdiction-specific, so the engineering obligation is consistency and explicitness rather than a single universal choice.

Assuming fixed-width integer arithmetic cannot overflow

In languages with fixed-width integers, including C, C++, Java, Go and Rust, computing a midpoint as (lo + hi) / 2 overflows once the sum passes the type's maximum, so write lo + (hi - lo) / 2 instead. Say which language you are in: arbitrary-precision integers, as in Python or Ruby, remove this specific hazard and none of the others.

Listing technologies instead of trade-offs

Name the property the design needs first, such as ordered range scans, multi-entity transactions, cheap appends, or a predictable p99, then pick something that provides it and say what it gives up in exchange. Almost any component is defensible once you state the requirement it satisfies and the one it sacrifices.

Questions & practice

Can you describe a scenario where you had to solve a particularly difficult technical problem at work?

Medium · data structures and algorithms

Approach

  1. Name the brute-force solution and its complexity before improving on it.
  2. State the target complexity and say which constraint rules the naive version out.
  3. Walk one small example through your approach before writing the whole thing.

Follow-up

  • What is the worst case, and how likely is it on real data?
  • How does this change if the input no longer fits in memory?

How do you manage complex technical requirements when working under strict time pressure?

Medium · data structures and algorithms

Approach

  1. Choose the data structure from the access pattern, not from familiarity.
  2. Restate the input: its shape, its size, and what is guaranteed about it.
  3. Walk one small example through your approach before writing the whole thing.

Follow-up

  • Which test case would catch an off-by-one here?
  • How does this change if the input no longer fits in memory?

Find peak concurrently held units per variant with a sweep

Medium · sweep line · intervals · reservations

You have up to 50 million historical reservation records as (variant_id, node_id, units, start_utc, end_utc), where end_utc is the instant the hold stopped counting — its expiry, release or commit — and the interval is half-open. For each (variant_id, node_id) return the maximum units held simultaneously and the earliest instant that maximum was reached, then flag every key whose peak exceeded a supplied cap. Records are unsorted. State your complexity and your tie rule at equal timestamps.

Approach

  1. Expand each record into two endpoint events, (start_utc, +units) and (end_utc, -units), group by (variant_id, node_id), and sort each group by (timestamp, delta_sign) with negative deltas first. That ordering is the entire correctness argument for half-open intervals: a hold ending exactly when another begins must not appear to overlap, and putting starts first invents a peak that never existed.
  2. Sweep each group with a running sum, tracking the maximum and the timestamp at which it was first attained. The peak holds over [t, next_event_time), so report the window rather than the instant — an alert naming a moment is far harder to corroborate against the ledger than one naming an interval.
  3. Complexity: O(n log n) dominated by the sort, O(n) space, or O(n log g) when sorting within groups of size g. Grouping is not a micro-optimisation: a single global sort interleaves 50 million events from two million keys, and the running sum is meaningless unless it resets at every group boundary anyway.
  4. Offer the bucket alternative with its condition attached. If timestamps are second-granularity over a bounded window, a difference array over seconds makes each group O(n_g + W) for a window of W seconds. That wins for a hot variant with millions of holds and loses badly across a long tail of variants with three holds each, so choose per group by comparing n_g log n_g against n_g + W.
  5. Compare each peak against the cap on_hand - damaged + oversell_allowance as it stood at that time, not as it stands now. Judging last Tuesday's peak against today's on-hand produces false alarms on restocked variants and misses real breaches on depleted ones.

Worked solution (25 min)

  1. Hand-build six intervals on one variant, two of them sharing an endpoint, and compute the expected peak on paper before writing code.
  2. Implement the sweep with the tie rule explicit, and assert the hand-computed answer.
  3. Flip the tie rule deliberately and confirm the answer moves by exactly the units of the touching interval; keep that as the regression test.
  4. Scale to 5 million events across 200,000 keys, then time the sort and the sweep separately to confirm the sort dominates.

Expected result: The hand-built group's peak matches the paper figure under the ends-before-starts rule, and is higher by exactly the touching interval's units under the wrong rule. The reported window is half-open and terminates at the next event timestamp.

Verify it yourself

  • The running sum returns to zero at the end of every group; a non-zero residual means an unmatched endpoint, usually a NULL end_utc that was silently dropped.
  • The running sum is never negative at any point in the sweep.
  • Doubling every units value doubles every peak and leaves every peak timestamp unchanged.

Follow-up

  • end_utc is NULL for holds still live at the end of the window. What do you substitute, and what does that choice do to the peak?
  • The same reservation appears twice because the export is at-least-once. Where do you dedupe, and on what key?
  • You now need the peak per hour rather than per window. What changes, and what stays the same?

Fold duplicate, out-of-order line events into one current state

Medium · window functions · event tables · deduplication

order_line_event is append-only: event_id UUID primary key, order_line_id, event_type in reserved, released_to_node, picked, shipped, delivered, cancelled, occurred_at_utc from the producer's clock, received_at_utc from yours, and producer_seq. Delivery is at-least-once with no ordering across producers, so the storefront's cancel and the warehouse's picked event can arrive in either order and either can arrive twice. Write one query returning, per order_line_id, the current state, when it was entered, and seconds spent in the previous state. Say which ordering column you trust and why.

Approach

  1. Close the duplicate at ingestion rather than in the query: the primary key on the producer's event_id plus INSERT ... ON CONFLICT DO NOTHING makes a redelivery a no-op. A query that de-duplicates after the fact is compensating forever for an ingestion bug you can fix once.
  2. Pick the winning row with ROW_NUMBER() OVER (PARTITION BY order_line_id ORDER BY ... ) and filter rn = 1, and make the ORDER BY a total order by appending producer_seq and event_id. Those tiebreaks are not decoration: with ties on the timestamp, the same data returns different answers between runs, and that is the class of bug nobody reproduces.
  3. Say which clock you trust. occurred_at_utc is the producer's, and the storefront and the warehouse do not share one, so sorting by it is only valid within a single producer, where producer_seq is the correct tiebreak. received_at_utc is your clock and is the only total order across producers, at the cost of being arrival order rather than event order.
  4. Because of that, do not decide state by timestamp alone. Map event_type to its position on the linear path with a CASE, take the highest rank per line, and a late picked event arriving after shipped cannot regress the line. Handle cancelled as a branch rather than a rank: it is only legal while no shipped event exists, so compute bool_or(event_type = 'shipped') per line and let a cancel that loses that race be discarded — at which point the compensation is a return, not a cancellation.
  5. Compute dwell in the same pass with LAG(occurred_at_utc) OVER (PARTITION BY order_line_id ORDER BY rank, occurred_at_utc, producer_seq, event_id) and read it on the winning row. If you add a running aggregate later, write the frame explicitly: an ORDER BY with no frame clause defaults to RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, which includes every peer row tied with the current one and is rarely what was meant.
  6. Index (order_line_id, occurred_at_utc DESC) for the per-line lookup. For the whole-table refresh the query reads everything regardless, so the win comes from restricting to lines touched since the last watermark, not from the index.

Worked solution (35 min)

  1. Insert one line with reserved, released_to_node, picked and shipped in causal order, then insert a duplicate picked with the same event_id and a second picked whose occurred_at_utc is later than shipped.
  2. Run the naive latest-by-occurred_at version and record the state it reports.
  3. Run the rank-based version and confirm the line stays shipped.
  4. Add a cancelled event to a second line that has no shipped event, and to a third line that does, then compare the two results.
  5. Confirm the duplicate insert affected zero rows because of ON CONFLICT DO NOTHING on the primary key.

Expected result: The naive query reports the first line as picked, a regression. The rank-based query reports shipped, with entered_at equal to the shipped event's occurred_at_utc and seconds_in_previous measured from the picked event. The second line reports cancelled. The third stays shipped and its cancel is discarded, which is the exact point where the compensation becomes a return authorization.

Verify it yourself

  • The duplicate insert reports zero rows affected and the query output is byte-identical before and after it.
  • A line whose only event is cancelled still appears in the output — if your CASE returns NULL for it and you filtered NULLs out, it vanished silently.
  • Swap two events' received_at_utc without touching occurred_at_utc and confirm the result does not move, which proves you sorted on the column you claimed to.
  • seconds_in_previous is NULL, not zero, on a line with exactly one event.

Follow-up

  • The warehouse says picked at 10:02, the storefront says cancelled at 10:01, and the cancel arrived first. What state is the line in, and what has to happen physically?
  • Turn this into an incremental job running every minute. What is your watermark column, and how late can an event be before the job misses it entirely?
  • How would you detect an impossible history — delivered with no shipped — without scanning the whole table nightly?

Keep the search read model correct when events arrive out of order

Hard · denormalisation · read models · event ordering

Search serves 10,000 queries per second over two million variants with filters, facets and sorting. The origin tables are product_variant, a price table, and one inventory_position row per variant per node. Design the denormalised row the index is built from, justify it from that read pattern rather than from convenience, and write the upsert a consumer runs. Catalog, pricing and inventory publish independently, each with its own monotonic per-row counter, and delivery is at-least-once. State exactly what your upsert does with an event older than the row it hits.

Approach

  1. Justify the denormalisation with arithmetic rather than with the word performance. Every query filters, facets and sorts, so a live join would hit three sources, and availability alone is a GROUP BY over one inventory_position row per node per variant. At 10^4 queries per second that join is not affordable per request; at 10^2 it would be, and saying so is the difference between a judgement and a habit. One precomputed row per variant is what the read pattern forces.
  2. Pin the grain and state its cost. One row per variant means a product with forty colourways floods the first page; collapsing to product grain means the displayed price is an aggregate — the cheapest variant satisfying the active filters, which is a different number from the cheapest variant overall. Pick one and make the facet counts agree with it, because counts computed before the filter and results produced after it is a correctness defect, not a ranking preference.
  3. Reject a single version column. Three producers with independent sequences cannot share one counter: gating a price update with catalog's version either discards good prices or admits stale ones, depending on which producer happens to be ahead. Carry catalog_version, price_version and inventory_version, each gating only the columns its producer owns.
  4. Make a stale event a no-op rather than an error: INSERT ... ON CONFLICT (variant_id) DO UPDATE SET ... WHERE variant_search_row.catalog_version < EXCLUDED.catalog_version. Then state the consequence the consumer must handle, because it is the part teams miss: a conflict suppressed by that WHERE affects zero rows and RETURNING yields nothing, and a consumer that reads zero rows as failure will retry that event forever.
  5. Decide availability semantics before storing them. in_stock_any_node is a hint for filtering and ranking and never a promise; the binding decision stays the conditional write against inventory_position. That makes seconds of staleness acceptable here, and makes the worst outcome a customer reaching a variant that turns out to be gone — a refusal at reserve time rather than an oversell.
  6. Set freshness budgets by consequence rather than uniformly. Price and availability must propagate within seconds because they are user-visible and materially wrong when late; synonyms, boosts and relevance weights ride the next full rebuild, which doubles as the recovery path for a consumer that mis-applied events for an hour.

Follow-up

  • A consumer lags an hour and then replays from its offset. What does your version guard do, and what is still wrong after the replay finishes?
  • Out-of-stock variants: disappear, sink, or render with a restock date? Give the answer and say what each choice does to facet counts.
  • A repricing job emits 200 events per second for one variant for ten minutes. What do you change in the consumer so the rest of the index does not fall behind?

What is your specific experience in this field?

Medium

Approach

  1. Say what you would check first and why it is the highest-information step.
  2. Work from the requirement backwards to the design.
  3. 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?

Can you walk us through your technical background and how it prepares you for this position?

Medium

Approach

  1. Work from the requirement backwards to the design.
  2. State your assumptions explicitly before working the problem.
  3. Clarify what is being asked and what a complete answer contains.

Follow-up

  • What assumption would you test first?
  • How would you know your answer was wrong?

Reconcile payment attempts whose outcome is unknown

Medium · queues · backpressure · idempotency · rate limits

Your payment service leaves payment_transaction rows in state 'pending' or 'unknown' whenever a provider call times out; the partial index (state, requested_at_utc) WHERE state IN ('pending','unknown') exists for this sweep. The provider permits 50 status lookups per second and answers in 100 to 1000ms. Design the reconciliation worker: how it claims rows, how it avoids racing a request handler still in flight, how it behaves when the provider rate-limits, and why two copies running at once must be safe. Say what happens to a row still unknown after an hour.

Approach

  1. Claim work with SELECT ... FOR UPDATE SKIP LOCKED LIMIT k over that partial index, ordered by requested_at_utc. SKIP LOCKED is what makes two workers safe without coordination: neither blocks on the other's rows, and a single slow row cannot head-of-line block the sweep. Keep the claiming transaction short and commit the resolution in its own transaction, so a lookup taking a second does not hold locks for a second.
  2. Resolve by lookup, never by re-issue. Query the provider for your own idempotency key or psp_reference and write the answer. The outcome type has three values, so a lookup that itself times out leaves the row unknown and increments an attempt counter; back off exponentially with jitter, because a fixed interval synchronises every worker onto the provider's next blip.
  3. Handle the race with the original handler by time and by compare-and-set, not by locking across processes. Only act on rows older than a grace period that exceeds the client timeout, and write with UPDATE ... WHERE state = 'unknown'; zero affected rows means the handler resolved it first and the worker drops the row silently rather than treating it as an error.
  4. Size concurrency from the rate limit rather than from thread count. By Little's Law, concurrency equals arrival rate times service time, so at 50 lookups/s and a 1s worst case the limiter is 50 in-flight requests, and anything above that just queues at the provider and turns into timeouts. On a 429, halve the limiter and honour the retry-after header; a bounded queue with an explicit shed policy beats an unbounded one that converts overload into latency.
  5. Define the escalation and the invariant it protects. After the horizon, an unresolved row moves to a manual queue and the order is held: no further capture and no refund against that payment until the row is definite, because a refund issued while an earlier attempt is unresolved is exactly how total refunded exceeds total captured.

Worked solution (30 min)

  1. Write the claim query against the partial index and confirm it is an index scan, not a sequential scan over payment_transaction.
  2. Implement resolution as a provider lookup plus a compare-and-set write, with an explicit no-op branch for zero affected rows.
  3. Compute the concurrency limit from the rate limit and worst-case latency, and state the number.
  4. Run two worker copies against the same backlog and confirm each row is looked up once and written once.
  5. Force a 429 from a stub provider and confirm the limiter halves and the backlog drains rather than erroring out.

Expected result: A worker that claims disjoint row sets under SKIP LOCKED, resolves each unknown by lookup rather than re-issue, holds in-flight requests at 50, degrades on 429 by shrinking concurrency rather than dropping work, and escalates rows past the horizon to a manual queue while blocking further money movement on the affected order.

Verify it yourself

  • Two concurrent workers produce the same final state set as one worker, with no row looked up twice in the same pass.
  • No code path in the worker issues an authorize, capture or refund; it only queries and records.
  • The concurrency limit is derived from the rate limit and service time, with the arithmetic shown.
  • A row resolved by the original handler mid-sweep results in zero affected rows and no second write.

Follow-up

  • The provider's lookup returns 'not found' for a key you know you sent. What are the possible causes, and what do you write?
  • A worker resolves a row to 'succeeded' after the order was already cancelled and the authorization voided. What happens next?
  • How far back does the sweeper look, and what stops it rescanning a year of settled rows every minute?

Oversell that appears only at peak and never in staging

Hard · concurrency · lost update · invariants

The warehouse reports units sold that are not on the shelf, on three variants, always within two minutes of a drop, never reproducible in staging. on_hand_units never goes negative and no CHECK constraint violation appears in the logs. A reconciliation query shows reserved_units below the summed units of active reservations on exactly those (variant_id, node_id) rows. Last quarter the reservation insert and the reserved_units update were split into two transactions to cut checkout latency. Find the defect, explain the silence, and give the fix.

Approach

  1. Establish the direction of the drift first, because the two directions have opposite causes and only one of them oversells. reserved_units below the true sum means available-to-promise is overstated and units are sold twice; above it means availability is understated and customers see false scarcity.
  2. Explain the silence from the constraint's shape, because the absence of errors is evidence and is being read as reassurance. CHECK (reserved_units <= on_hand_units - damaged_units + oversell_allowance_units) bounds the counter from above only. The overselling direction is unconstrained by construction, so no violation can ever be logged for it.
  3. Establish the mechanism from the change that was made. The reservation row commits in transaction one; the counter update runs in transaction two. Any loss in between — a crash, a dropped task, a retry that exhausted its attempts — leaves the counter permanently low and every subsequent ATP derivation too high. Peak is when timeouts and retries actually happen, which is precisely why staging never reproduces it.
  4. Rule out the neighbouring hypotheses with one check each rather than reasoning about them. First, read-then-write: at READ COMMITTED a single UPDATE ... WHERE available >= n is safe because the statement re-evaluates its predicate against the updated row version after waiting on the lock, while a SELECT followed by a separate UPDATE is a lost update — so look at which shape the code has. Second, the sweeper: check for rows in state 'expired' whose order_line has shipped, and for a missing state = 'held' clause in the sweeper predicate. Third, retry handling: check whether a serialization failure is being swallowed as success.
  5. Fix by putting the reservation row and the counter in one transaction with a conditional UPDATE judged by affected-row count, so a lost race returns zero rows instead of diverging silently. The latency cost is one additional statement inside an already-open transaction, single-digit milliseconds, which is not what the split was buying.
  6. Stop letting the counter be the decision. The binding check is the conditional write against inventory_position; the counter and the availability cache are hints derived from it. Keep the reconciliation job but make it alert with the discrepancy rather than silently repairing, because a silent repair destroys the only evidence that a write path is still losing updates.

Follow-up

  • Write the reconciliation query, then decide what it should do at three in the morning when it finds a discrepancy of four units on a live variant.
  • Which additional CHECK or partial index would have made the overselling direction fail loudly instead of silently?
  • The split was made for latency. Measure what merging actually costs, and say what you would cut instead if that cost were unacceptable.

Suggested practice checklist

Day one measures instead of guessing, under a fixed rubric, and the remaining hours are allocated in proportion to the gaps before any studying begins. The allocation is deliberately not renegotiated midweek, because the area that feels worst on day three is usually the one that is moving.

Day 1 — Diagnostic, scored before you study anything

  • Sit a 110-minute diagnostic in four blocks: forty-five minutes on two coding problems, twenty-five on one design prompt taken to interface and data model, twenty of short-answer fundamentals, and twenty delivering two behavioural answers aloud.
  • Score each block from 0 to 3 on a fixed rubric where 3 is correct and fluent, 2 is correct but slow or prompted, 1 is partially correct and 0 is stuck, grading the artifact rather than how the attempt felt.
  • Allocate days two to five in proportion to 3 minus each block's score, write the allocation down, and commit to leaving it alone.

Deliverable: A scored rubric and a fixed hour allocation for the rest of the week.

Day 2 — Largest gap: find the boundary rather than the subject

  • Split the weakest area into named sub-skills and rate each separately. For coding those are restating the problem, choosing the structure, stating the invariant, turning the invariant into loop bounds, handling empty and single-element input, and accounting for complexity out loud.
  • Attempt three items positioned just above where the rating drops off, and for each write the first move you failed to make.
  • Re-attempt one of them from blank four hours later with nothing open.

Deliverable: A sub-skill map with the two blocking sub-skills circled.

Day 3 — Drill the blocking sub-skill by repeating the shape

  • Do eight short repetitions of the same shape rather than eight different problems, so what gets practised is the pattern and not the puzzle.
  • State the rule you now hold in one sentence, then test it against a case built to break it, a sliding window over an array containing negative values, or a cache-aside read path whose invalidation message is dropped.
  • Have someone else read your one-sentence rule and find the precondition you left out.

Deliverable: One rule statement with its preconditions attached and one counterexample that would have caught the incomplete version.

Day 4 — Second gap, plus maintenance on the strongest area

  • Run the same sub-skill decomposition on the second-largest gap in half the time.
  • Spend twenty-five timed minutes on the block you scored highest, choosing the hardest item you can still finish rather than a warm-up.
  • Write whether each area fails you on recall, on setup, or on execution, and set the fix accordingly: repetition for recall, a written checklist for setup, timed work for execution.

Deliverable: A second sub-skill map plus a one-line failure diagnosis for each area.

Day 5 — The gap that is not a skill

  • Record one technical and one behavioural answer, then count two things in the playback: seconds before your first clarifying question, and sentences you began without knowing where they would end.
  • Practise saying that you do not know, followed by how you would find out, without letting it soften into a guess, and practise stating a complexity or an estimate before being asked for it.
  • Redeliver one answer under a hard ninety-second cap, which forces structure ahead of detail.

Deliverable: Two recordings with a counted reduction in time-to-first-question.

Day 6 — Retest under day-one conditions

  • Sit the same 110-minute structure with new prompts of comparable difficulty and score it on the identical rubric.
  • For any block that did not move, change the method rather than adding hours: a block stuck at 1 usually means the practice was too varied, not too short.
  • Write down which single block you would still lose the offer on.

Deliverable: A second scored rubric placed beside the first, with one named remaining risk.

Day 7 — Full loop under interview conditions

  • Run a sixty-minute mock over the two blocks that moved least, with an interviewer briefed to interrupt and change direction mid-answer.
  • Write the recovery script for going blank: restate the question, state your assumption, name the first thing you would check.
  • Say every rule from the week aloud without reading it, and cut any you cannot state in a single sentence, since a rule you have to reconstruct mid-answer will not survive an interruption.

Deliverable: A one-page card holding the recovery script and only the rules you could state from memory.

The estimate and the day you knew

A slipped date is only a bad story if you sat on it. What matters is what you believed when you gave the number, the signal that told you it was wrong, how many days passed before you said so, and what you cut rather than asking for more time. Scope you defended counts as much as scope you dropped.

How do you handle professional communication in formal settings?

Medium · behavioural and engineering judgement

Approach

  1. Name the disagreement and how you resolved it with evidence.
  2. Give the blast radius: what could have broken, and what you measured.
  3. Close with what you would do differently, concretely.

Follow-up

  • How did you know your change caused the improvement?
  • What would you do differently if you ran that again?

What do you know about ALDI as a company and why do you want to work here?

Medium · behavioural and engineering judgement

Approach

  1. Close with what you would do differently, concretely.
  2. State the situation in two sentences and spend the rest on the reasoning.
  3. Name the disagreement and how you resolved it with evidence.

Follow-up

  • What would you do differently if you ran that again?
  • What did you decide not to do, and why?

Turn an implicit out-of-stock policy into a written decision

Medium · ownership · read models · search · decision records

Search drops out-of-stock variants, category browse keeps and sinks them, and the product page renders an expected date. Nobody decided this; three teams did three things. Facet counts and result counts now disagree on the same page. You have no authority over any of the three teams. Describe how you convert an implicit behaviour into an explicit, owned decision: what you measure first, what you put in writing, how you handle the fact that the correct answer costs revenue, and describe a time you did this for real, including whether it stuck.

Approach

  1. Split the bug from the policy in the first sentence, because they have different owners and different urgency. Facet counts disagreeing with result counts is a defect whatever policy you choose — the counts are computed over the index while the results are filtered afterwards — and it is fixable without anyone agreeing on anything. What happens to an out-of-stock variant is a policy question and needs a decision, not a fix.
  2. Gather evidence that costs you a day and costs the reader a minute: a three-row table of current behaviour with the code path behind each row, plus the measured size of the count divergence on real queries. Three teams discover they disagree far faster from a table than from a meeting.
  3. Make the decision small enough that someone will take it. Propose a specific default, name the person you think should own it, set a date, and say that silence is agreement. An open question with no proposal and no deadline stays open for a year.
  4. Be honest about the revenue coupling instead of hiding it, because the person who has to sign owns that number: hiding out-of-stock items protects the browse experience and loses the demand signal, and sinking them with a restock date keeps it but only works when expected_receipt_at_utc is trustworthy, which is a data-quality precondition rather than a preference. If it is not trustworthy, say that the date option is unavailable today and what it would take to make it available.
  5. Write the decision where code can point at it, and pin it with a test that fails when a service drifts back. A decision that lives only in a chat thread is re-litigated by the next joiner within two quarters.
  6. Report whether it stuck, including a reversal. A written decision later overturned by evidence is a better outcome than three implicit behaviours, and saying so is the point of the story.

Follow-up

  • The owner you proposed declines. What do you do next, and what do you not do?
  • Six months on, one service has drifted back. How do you find out, and what makes it not happen again?
  • Measurement shows the correct policy costs a measurable amount of revenue. Does that change the decision, and who gets to decide?