cswork / libraryOpen practice workspace

Interview guides · Siemens

Siemens Software Engineer Interview Guide

Siemens software engineer prep on databases: indexes, query plans, and the anomalies an isolation level still permits.

The 60-second brief

As a Software Engineer at Siemens, you do not just write code; you build the digital nervous system for the physical world. Siemens operates at the unique intersection of hardware and software, meaning your work directly impacts critical global infrastructure, industrial automation, smart grids, rail mobility, and healthcare systems. Whether you are optimizing factory automation pipelines, building cloud-native IoT platforms, or developing embedded systems for high-speed trains, your software must be exceptionally reliable, performant, and secure.

Find out what you will be typing into. A shared plain-text editor with no autocomplete, compiler or test runner changes what you have to hold in your head, and practising inside your own configured environment hides exactly that gap.

Focus areas

  • Model units, lots and state machines precisely
  • Reconcile a movement ledger against derived positions
  • Make every external side effect safe to retry

Overview

This role requires a mindset that balances cutting-edge software practices with the rigorous engineering disciplines needed for physical systems. You will work on complex, large-scale systems where software errors can have real-world physical consequences. Consequently, Siemens places a massive premium on clean code architecture, comprehensive testing, and systemic safety. It is an incredibly rewarding environment for engineers who want to see their code drive tangible, physical innovations across the globe.

The Siemens interview process

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

HR Screening Call

Half of this call is the part candidates treat as small talk: start date, notice period, work authorisation and its timing, location and time zone, on-call, and the number. Those are what kill offers late, after several engineers have each spent a day. Surfacing a hard constraint now costs you nothing and occasionally buys you something, since a loop compressed to fit a competing deadline can usually only be arranged if it is asked for early. The common failure is deflecting the compensation question twice, then discovering at offer stage that the band never reached your number.

What to demonstrate

  • Whether your hard constraints are compatible with the role before a loop gets booked: earliest start, notice period, what authorisation you hold and when it needs action, days on site, willingness to carry a pager
  • Whether you give a compensation range with something behind it, such as current total compensation or a competing timeline, rather than leaving the band untested
  • Whether your stated timeline is real, since a competing deadline raised now is something scheduling can sometimes work around and the same deadline raised at offer stage usually is not

How to prepare

  • Write each constraint down in one line before the call and state them as facts rather than negotiating them live under a question you were not expecting
  • Set your range from two or three current data points for that level and location, and name the structure you are quoting in, so the number is comparable to the one they are holding
  • If another process is running, say where it stands and by when, and ask directly whether this loop can be scheduled inside that window

Online Technical 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

Deep-Dive Technical Interviews

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

Managerial or Panel Round

Nobody in the room with you decides this. Interviewers typically write their rounds up separately, often before seeing anyone else's, and the outcome is settled later from those write-ups. A split panel gets resolved by whichever note carries specific evidence, so what you want out of each room is one concrete thing that person could write down: a bug you caught yourself, a trade-off you named, a decision you owned. The rest is arithmetic. The project you describe in a behavioural conversation is often the same system you sketched an hour earlier, and the two accounts have to agree.

What to demonstrate

  • Whether the scale, team size and timeline you attach to a project hold steady when that project resurfaces in a different round
  • Whether each interviewer leaves with a specific thing to cite rather than a general impression of competence
  • Whether a trade-off you defended in one round survives a challenge in another, instead of being quietly swapped for the answer the new interviewer seemed to want
  • Whether a question you have already answered earlier in the day gets the same answer at the same depth, without visible impatience

How to prepare

  • Write a one-page sheet per project fixing the figures you will quote — request volume, data size, team size, elapsed time, what broke — and say them aloud from the sheet until they come out identical every time
  • For each round on the schedule, decide in advance the one sentence you want in that person's notes, then check in a mock that you said it outright instead of leaving it to be inferred
  • Have someone ask you the same project question twice, an hour apart, and diff the two answers for numbers that moved or a trade-off that reversed

Behavioral HR Interview

What you say here is written down by each interviewer and compared afterwards, so the unit of evaluation is a claim someone else could check, not a well-told narrative. Two things make a story checkable: detail only a participant would hold, and a clean line around which part was yours. Vague ownership is the usual failure and it is usually accidental, because engineers say we about the team's work and we about their own, so the thing they personally built disappears into the plural. Name the part you wrote, and name who did the rest.

What to demonstrate

  • Whether your details are ones a participant would hold and an observer would not: the constraint that ruled out the obvious approach, the first attempt that failed, the person who objected and on what grounds
  • Whether ownership survives a direct question, since a follow-up to we decided is routinely who decided, and an answer that stays plural at that point is read as the work belonging to someone else
  • Whether the numbers you quote are ones you would say identically to a former colleague with the dashboard open

How to prepare

  • Go through each story replacing every we with either I or a named role (the on-call engineer, the reviewer, the other team) and check the story still holds together. Wherever it stops making sense you have found a part you cannot actually speak to
  • Open the artefacts for two of your stories, the pull request, the design doc, the incident notes, and read them for dates and figures you have been rounding in the retelling. Correct your version to match
  • For each story write the single sentence you would least want repeated to a former teammate, then either make it accurate or take it out

Where candidates lose points

Holding a database transaction open across a physical or third-party operation

It is natural to open a transaction, lock the position, call the rating or tendering API, and commit on the response, and it works perfectly until the partner's p99 goes from 200 milliseconds to thirty seconds. At that point every request holding a lock on a hot item-node pair queues behind it, the connection pool fills with transactions that are waiting on the network rather than on the database, and an unrelated service sharing the pool fails at the same moment. In this domain the effect is amplified because demand concentrates on a few hot rows during a promotion or a seasonal peak, exactly when partner latency is also degraded. The structural fix is to keep transactions short and local - commit the state change together with an outbox row, let a relay perform the external call, and reconcile asynchronously - accepting at-least-once delivery and making the effect idempotent rather than trying to stretch a transaction over something the database cannot roll back.

Retrying an irreversible external call without an owned idempotency key

Timeouts and connection resets are ambiguous by construction - the partner may have committed before the response was lost - so a generic retry policy around an HTTP client is, in this domain, a machine for buying two labels and dispatching two trucks. Relying on the partner's deduplication is not a substitute, because their window is usually short, their key is often derived from fields you may legitimately change on retry, and many older integrations have no such concept at all. The workable pattern is to generate the key yourself, persist it with an explicit unknown status before the call, and resolve ambiguity by querying the partner for that key rather than re-issuing; the sweeper that does this is the component that has to be correct, not the call site. Teams get this right for payments and then forget that a carrier tender, a warehouse work release and an EDI shipping notice have the same shape.

Quoting amortised or average cost as if it were a worst-case guarantee

Appending to a dynamic array is amortised O(1), but the append that triggers a resize copies every element, and hash lookup is constant only while the hash spreads the actual keys. Say which guarantee you are offering when the caller cares about the latency of one call rather than the total over many.

Arguing past a hint

When the interviewer asks what happens for a particular input or floats a different data structure, stop and take it seriously; it is almost always a correction rather than idle curiosity. Talking over it converts a recoverable wrong turn into a data point about how you handle review.

Questions & practice

How do you use bit manipulation to check if a given integer is a power of two?

Medium · data structures and algorithms

Approach

  1. Name the brute-force solution and its complexity before improving on it.
  2. Choose the data structure from the access pattern, not from familiarity.
  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?

How would you implement a queue using two stacks, and what is the amortized time complexity of the enqueue and dequeue operations?

Medium · data structures and algorithms

Approach

  1. Choose the data structure from the access pattern, not from familiarity.
  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

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

Write a function to detect and remove a cycle in a singly linked list.

Medium · data structures and algorithms

Approach

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

Follow-up

  • Which test case would catch an off-by-one here?
  • What is the worst case, and how likely is it on real data?

Solve a classic array-based problem: find the first repeating element in an array of integers with optimal time and space complexity.

Medium · data structures and algorithms

Approach

  1. Choose the data structure from the access pattern, not from familiarity.
  2. Walk one small example through your approach before writing the whole thing.
  3. Name the brute-force solution and its complexity before improving on it.

Follow-up

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

Explain the concepts of abstract classes and interfaces. When would you choose one over the other?

Medium · languages, concurrency and fundamentals

Approach

  1. Distinguish a value from a reference to it, and say which one you handed out.
  2. Reach for the cheapest primitive that closes the race, not the broadest lock.
  3. Identify the window where an invariant is briefly untrue.

Follow-up

  • How would you prove the race exists rather than suspect it?
  • What happens if two callers reach this at the same time?

What is a singleton design pattern, how do you implement it in a thread-safe manner, and what are its potential drawbacks?

Medium · languages, concurrency and fundamentals

Approach

  1. Distinguish a value from a reference to it, and say which one you handed out.
  2. Reach for the cheapest primitive that closes the race, not the broadest lock.
  3. Name what is shared across threads and what owns each piece of state.

Follow-up

  • Where could this allocate more than you expect?
  • What happens if two callers reach this at the same time?

Size dock doors from overlapping appointment windows

Medium · sweep line · intervals · time zones

For one node and one local calendar day you have up to 200,000 planned dock appointments from shipment_leg: leg_id, planned_arrive_at, planned_depart_at (both TIMESTAMPTZ), plus the node's IANA zone. A door is occupied over [arrive, depart). Return the minimum number of doors that lets every appointment start on time, and the maximal interval over which that peak is sustained. Target O(n log n). Say how you derive the day's boundaries and how you break ties between an arrival and a departure at the same instant.

Approach

  1. Name the result before computing it: with one interchangeable door type, the minimum door count equals the maximum number of simultaneously occupied intervals. That equality is not a heuristic — interval graphs are perfect, so their chromatic number equals their clique number, and greedy left-to-right assignment achieves it. Say this, because it is what licenses solving a scheduling question with a counter.
  2. Emit 2n endpoints, (t, +1) at each arrival and (t, -1) at each departure, sort by t, and at equal t order the -1 before the +1. Half-open occupancy means a trailer leaving at 10:00 frees the door for one arriving at 10:00; ordering arrivals first inflates the answer by exactly the number of back-to-back handoffs, which on a well-packed schedule is most of them.
  3. Scan once with a running counter, tracking the maximum and the endpoint index where it was first reached. The peak is attained on a half-open interval between two consecutive endpoints, [t_i, t_{i+1}), not at an instant — report it that way or the operations team cannot act on it. Extend the interval while the counter stays at the maximum.
  4. Complexity: O(n log n) dominated by the sort, O(n) space. If rows already arrive ordered by planned_arrive_at from an index, use the min-heap variant instead — push each departure, pop all departures at or before the current arrival, and the heap size is the current occupancy. Same time bound, O(peak) space rather than O(n), which matters when peak occupancy is 40 and n is 200,000.
  5. Derive the day's boundaries from the zone, not from arithmetic on instants. Convert local midnight and the next local midnight to instants in the node's IANA zone; on a transition day that span is 23 or 25 hours, so adding 86,400 seconds silently drops or duplicates an hour of appointments. Decide explicitly what a zero-length dwell means — under half-open semantics it occupies nothing — and state it.

Worked solution (25 min)

  1. Write the endpoint tuple with the tie-break baked into the sort key — for example (t, kind) where departure sorts as 0 and arrival as 1 — rather than handling ties in the scan body.
  2. Implement the scan, returning both the maximum and the [t_i, t_{i+1}) span at which it holds.
  3. Build a fixture of six appointments where three are strictly back-to-back at the same minute, and hand-compute the answer before running.
  4. Add an appointment spanning the local DST transition and derive the day window from the zone, asserting the appointment count inside the window.
  5. Re-run with the input shuffled to confirm the result depends only on the set of intervals.

Expected result: The back-to-back fixture needs one door, not three; the reported peak is a half-open interval between two endpoints; on the transition day the local window is 23 or 25 hours and no appointment is dropped or double-counted.

Verify it yourself

  • Flipping the tie-break to arrivals-first changes the answer on the back-to-back fixture — if it does not, the fixture is not actually testing the tie.
  • The maximum equals the largest number of intervals containing any single probe instant, checked by brute force on a small fixture.
  • The result is identical under any permutation of the input.

Follow-up

  • Doors are typed: some break pallets, some are parcel-only. Does max-overlap still equal the door count?
  • Appointments have a tolerance — a trailer may start up to 20 minutes late without penalty. How does that change the objective, and is it still solvable by a sweep?
  • Half the appointments are actuals and half are plans. Which do you sweep for tomorrow's staffing, and which for last week's utilisation report?

Write the reconciliation query that finds ledger and position variance

Easy · aggregation · outer joins · reconciliation

inventory_movement(movement_id, item_id, node_id, lot_id, state, delta_qty) is the ledger; inventory_position(item_id, node_id, lot_id, state, qty, version, last_movement_id, reconciled_through_movement_id) is its running sum, written in the same transaction as each movement. Write one query that reports every key whose stored qty disagrees with the sum of its movements, including keys present in only one of the two tables, bounded by a watermark movement_id you pass in. State what the job does with each variance, and how you avoid re-summing four billion rows every night.

Approach

  1. Aggregate once and join once: a CTE doing SELECT item_id, node_id, lot_id, state, SUM(delta_qty) AS ledger_qty FROM inventory_movement WHERE movement_id <= :w GROUP BY 1,2,3,4, then FULL OUTER JOIN inventory_position USING those four columns. One pass plus one hash aggregate, O(n) in movements scanned; a correlated subquery per position row instead costs one index scan per key and is orders of magnitude slower at 40 million keys.
  2. Use FULL OUTER JOIN, not LEFT. A key with movements and no position row is the signature of a lost position update, and a position row with no movements is the signature of a write that bypassed the ledger. Both disappear under a LEFT JOIN from either side.
  3. Compare with IS DISTINCT FROM and COALESCE the join keys out of both sides, so a missing row reads as a variance rather than as NULL = NULL evaluating to unknown and being filtered away.
  4. Run both sides in one snapshot: either a single statement, or a REPEATABLE READ transaction. Reading positions in one transaction and movements in another manufactures variances for every key written in between.
  5. Make it incremental with reconciled_through_movement_id: sum only movements in (reconciled_through, :w] and add that delta to the previously verified total. The catch is that sequence values are handed out before commit, so a movement whose id is below :w can become visible after the scan and would then never be re-summed. Set :w to a max(movement_id) observed a few minutes earlier, beyond the longest write transaction, and full-scan one partition per night on a rotation as a backstop.
  6. A variance is reported, not repaired. Rewriting qty to match the ledger hides the double-applied retry that caused it; only a physical count may produce a cycle_count_adjust movement, and the location should be flagged unreliable to planning until it is counted.

Follow-up

  • The job finds 14 variances, all on one node, all negative. What do you look at first, and what would make you suspect the reconciliation job itself rather than the data?
  • How do you run this without the nightly scan competing with the ATP read path for buffer cache and I/O?
  • What does the query return for a key whose only movements are a receipt and its exact reversal, and is that row worth reporting?

Fold late, out-of-order scan events into one status per leg

Medium · window functions · event time · monotonic fold

observation_event holds event_id, subject_type, subject_id, observed_status, occurred_at, received_at, dedupe_key (UNIQUE) and processing_status. A lookup table maps observed_status to status_rank SMALLINT along a progress lattice. Write one query returning the current status of every shipment leg that received an event in the last hour, such that a picked_up event arriving after a delivered event never changes the answer. Then explain exactly which part of your ordering makes a shuffled replay of the same batch produce identical output.

Approach

  1. Separate the two jobs. Deduplication is the UNIQUE on dedupe_key at ingest, because partner feeds resend whole windows; ordering is the fold. Either alone leaves a defect: dedupe without a lattice still lets a stale event win, and a lattice without dedupe double-counts a replayed window in anything that aggregates.
  2. Select the affected legs in one CTE by received_at >= now() - interval '1 hour', then fold over every event for those legs, not only the recent ones. Restricting the fold's input to the recent window is the failure: a lone late picked_up would be the only row considered and the leg would regress.
  3. Fold as a maximum over the lattice: SELECT DISTINCT ON (subject_id) subject_id, observed_status FROM ... ORDER BY subject_id, status_rank DESC, occurred_at DESC, event_id DESC. The equivalent form is ROW_NUMBER() OVER (PARTITION BY subject_id ORDER BY status_rank DESC, occurred_at DESC, event_id DESC) = 1.
  4. The determinism comes from the final event_id tiebreak. Rank and occurred_at alone leave two equal-rank events at the same instant to be resolved by heap order, which changes when rows are re-inserted, so the shuffled replay differs in exactly the cases nobody tests. With a total order over the tuple the fold is a max, which is commutative and associative and therefore order-insensitive.
  5. Do not advance a leg on occurred_at alone. The timestamp is device-reported and can be wrong, which is why the lattice rank leads the ORDER BY and why clock_offset_ms is worth storing: a future-dated event with a low rank then cannot promote itself past a delivered.
  6. Keep superseded rows and mark processing_status = 'superseded' rather than deleting them. The discarded event is usually the one that explains a delivery dispute, and deleting it makes the trace unanswerable.

Worked solution (30 min)

  1. Build a fixture for one leg with picked_up at T0, in_transit at T1, delivered at T2, then insert the picked_up row last so arrival order contradicts event order.
  2. Write the CTE selecting affected subject_ids by received_at, then the DISTINCT ON fold joined to the rank lookup.
  3. Run it and confirm delivered is returned, then shuffle the insert order of the same batch into a fresh table and re-run.
  4. Add a second delivered event at the identical occurred_at from a different source and confirm the event_id tiebreak still picks one deterministically.

Expected result: One row per leg carrying the highest-ranked status ever observed, identical across every insert ordering of the same batch, with the late picked_up present in the table and marked superseded rather than absent.

Verify it yourself

  • Replay the batch in five random orders and diff the result sets; they must be byte-identical.
  • Remove the event_id tiebreak and confirm the equal-timestamp case becomes unstable across replays.
  • Restrict the fold input to received_at in the last hour and confirm the leg regresses to picked_up, reproducing the trap.
  • Confirm every input event is still present with a processing_status, so no evidence was discarded.

Follow-up

  • An event arrives a week late for a leg that is already invoiced. Does the fold apply it, reject it as late, or apply it and emit a correction?
  • The stream is partitioned by source_id rather than by subject_id. What ordering guarantee do you actually have, and what breaks?
  • How would you materialise this fold so the customer-facing status read is not running a window function per request?

Design a class structure for a geometric system that supports different shapes (like rectangles and squares) while adhering to the Liskov Substitution Principle.

Medium · system design and architecture

Approach

  1. Choose a partition key and say what query it makes expensive.
  2. Name the failure you are designing for, then the recovery path.
  3. State the consistency you need, and where you are willing to be stale.

Follow-up

  • What breaks first when traffic grows ten times?
  • What would you drop to keep the system up under load?

Explain the core components of Kubernetes architecture and how you would manage deployment strategies, scaling, and logging in a hybrid cloud environment.

Medium · system design and architecture

Approach

  1. Name the read and write paths separately; they rarely have the same bottleneck.
  2. Name the failure you are designing for, then the recovery path.
  3. Choose a partition key and say what query it makes expensive.

Follow-up

  • What breaks first when traffic grows ten times?
  • What would you drop to keep the system up under load?

What are the primary differences between synchronous and asynchronous communication protocols in embedded systems?

Medium

Approach

  1. Clarify what is being asked and what a complete answer contains.
  2. Say what you would check first and why it is the highest-information step.
  3. Work from the requirement backwards to the design.

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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)

  1. Write the current and target payloads side by side and mark every field as unchanged, added or frozen - with nothing in a fourth category.
  2. 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.
  3. Specify the negotiation: the credential-level default, the override header, the error for an unknown or retired version, and the support window.
  4. Design the usage telemetry that tells you which credential still reads qty, and state the threshold at which you would send Sunset.
  5. 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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.

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.

Why do you want to work for Siemens, and how does your background align with our mission to transform the everyday?

Medium · behavioural and engineering judgement

Approach

  1. Name the disagreement and how you resolved it with evidence.
  2. State the situation in two sentences and spend the rest on the reasoning.
  3. Give the blast radius: what could have broken, and what you measured.

Follow-up

  • How did you know your change caused the improvement?
  • What did you decide not to do, and why?

Resolve a review disagreement over clamping a position

Easy · code review · invariants · reconciliation · disagreement

A pull request changes the pick path from a plain decrement to qty = GREATEST(qty - :n, 0) on inventory_position, because the CHECK (qty >= 0) constraint has been failing a few times a day and paging the on-call. The author argues the page is noise and a negative on-hand is meaningless anyway. You disagree. Describe a code review disagreement you had: the argument you made, how you kept it about the system rather than the person, what you conceded, and how it ended. State what the clamp destroys and what you proposed be done with the failures instead.

Approach

  1. Name the information the clamp destroys, in one sentence: the constraint failure is the only signal that the ledger and the shelf disagree, and GREATEST turns a detectable variance into a silent one that surfaces later as a picker standing at an empty location for an order the system promised.
  2. Concede the real half of the author's case immediately, because it is real: paging a human on a data condition that a human cannot fix at 3am is bad design. The disagreement is not whether the page should stop but whether stopping the page should also stop the evidence.
  3. Propose the version that satisfies both: catch the constraint violation at the application boundary, write a cycle_count_adjust movement with its own reason code and reference so the correction is a first-class row rather than an invisible edit, fail the pick task with a specific error the floor can act on, and move the alert from a page to a variance report per item-node pair.
  4. Anchor the argument in the append-only model rather than in taste. Positions are derived from inventory_movement, so a clamp makes the position stop equalling the sum of its movements and the reconciliation job now reports a permanent, unexplainable variance on that key.
  5. Say how you handled the disagreement mechanically: the comment thread, then a short synchronous conversation once it became clear the thread was circling, and a written decision so the next person reading the clamp knows why it is not there.
  6. State the concession you actually made, including if you lost. Losing a review and writing the detector anyway is a legitimate and common outcome worth describing plainly.

Follow-up

  • The clamp has already been in production for a month. How do you find out how much stock it silently absorbed?
  • Where should the adjustment movement get its reference, given that no count sheet exists at pick time?
  • The author is more senior than you and unconvinced. What is your next move?

Reverse an allocation design after peak contention

Medium · concurrency control · lock contention · reversibility · measurement

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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?