cswork / libraryOpen practice workspace

Interview guides · BaxEnergy

BaxEnergy Software Engineer Interview Questions & Guide 2026

System design practice for BaxEnergy software engineer candidates: reach a data model and an API contract, not a box diagram.

The 60-second brief

A Software Engineer at BaxEnergy sits at the intersection of high-stakes industrial operations and cutting-edge digital innovation. As a global leader in renewable energy management, BaxEnergy develops critical software solutions that monitor and optimize tens of gigawatts of power worldwide. Your work directly influences the global transition toward sustainable energy by providing the digital infrastructure necessary for grid control, asset performance, and cybersecurity.

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
  • Reserve inventory without overselling under concurrent allocation
  • Reconcile a movement ledger against derived positions

Overview

This role is not merely about writing code; it is about building robust, production-grade systems that function in complex, real-world environments. You will contribute to a suite of products that require a deep understanding of scalability, reliability, and system architecture. Whether you are working on backend infrastructure or full-stack features, you are expected to take ownership of your deliverables and collaborate closely with multidisciplinary teams to solve challenges that have a tangible impact on global energy infrastructure.

The BaxEnergy interview process

BaxEnergy candidates report 2 rounds · ≈ 2-4 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

Technical Assessments

Most of the time lost in this format is not lost to thinking. It goes to a standard-library call you half-remember, an off-by-one in a loop bound, and a debugging loop that mutates code at random until something passes. When output is wrong, stop re-reading the whole function: take the smallest input that reproduces it and walk the state through by hand, printing intermediates if the environment allows. Guessing at a fix without a failing case you understand is how a five-minute bug becomes twenty, and the clock does not pause while you do it.

What to demonstrate

  • Whether you reach the right structure without a detour, and can write it from memory rather than only recall that one exists
  • Whether overflow is considered where the language has fixed-width integers, since a signed 32-bit value stops at 2,147,483,647 and then wraps in Java, is undefined behaviour in C++, and does not arise in Python, whose integers grow instead
  • Whether recursion depth is treated as a constraint on large inputs, given that CPython's default limit is 1000 frames and a deep recursion can exhaust the stack in any language where an iterative version would not
  • Whether a failing case is isolated and explained before any edit is made to the code

How to prepare

  • From an empty file and with no references open, implement the pieces you lean on most: a heap push and pop, an iterative DFS with an explicit stack, and a binary search whose midpoint is written lo + (hi - lo) / 2, which avoids the overflow that (lo + hi) / 2 can hit in a fixed-width integer type
  • Time yourself on the ten library calls you look up most, such as sorting with a custom comparator, splitting and joining strings, and finding the next key at or above a value in an ordered map, until the lookup is gone
  • Take a solution you know is broken and, before touching it, write one sentence naming the input, the expected value and the actual value. Repeat until you do it without deciding to.

Where candidates lose points

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.

Judging delivery windows and cut-offs in UTC

A delivery appointment, a receiving cut-off and a same-day promise are all statements about local wall-clock time at a specific stop, so evaluating them in UTC misclassifies on-time performance for any leg that crosses a zone, and misses cut-offs by an hour on the two days a year a region changes offset. The subtlety that catches experienced engineers is that converting a future local time to a UTC instant at write time bakes in the offset rules as they were known then: when a jurisdiction changes its rules, an appointment stored that way silently moves, which is why a future window is better stored as local wall time plus an IANA zone identifier and resolved at read time, while a past event is correctly stored as an instant. Duration arithmetic needs the same care, because a transit that spans a DST boundary is genuinely 23 or 25 hours long in local terms even though the elapsed instant count is unchanged. Storing only a date with no zone - common on partner feeds - is the version of this that cannot be fixed downstream, so the zone has to be captured at ingestion.

Optimising an axis nobody named

Ask which resource is actually scarce here: wall-clock latency, throughput, memory footprint, cost per request, or engineering time. Shaving a constant factor off an in-memory step is wasted effort when the same function makes a blocking remote call inside the loop.

Finishing a solution without stating its complexity

Give time and space in the same breath as the code, and define n explicitly when there are two sizes, since n nodes and m edges are not interchangeable. Space is the half that gets skipped: count the auxiliary structures you allocate and the recursion stack at its deepest, not only the answer you hand back.

Questions & practice

Implement functions to manipulate data structures efficiently.

Medium · data structures and algorithms

Approach

  1. Name the brute-force solution and its complexity before improving on it.
  2. Walk one small example through your approach before writing the whole thing.
  3. Restate the input: its shape, its size, and what is guaranteed about it.

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?

Find negative and even numbers in a given list.

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. Restate the input: its shape, its size, and what is guaranteed about 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?

Merge intervals (similar to standard coding assessment platforms).

Medium · data structures and algorithms

Approach

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

Follow-up

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

Debug code snippets during a live session.

Medium · data structures and algorithms

Approach

  1. Walk one small example through your approach before writing the whole thing.
  2. Choose the data structure from the access pattern, not from familiarity.
  3. State the target complexity and say which constraint rules the naive version out.

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?

Parse partner quantity and date segments without floating point

Easy · parsing · unit-of-measure · time zones

A partner sends a delimited flat file whose segments look like QTY+17:4.500:CS and DTM+232:20260317. Quantities carry up to three decimal places in a named unit; each item has an integer pack factor to eaches that is versioned by effective date. Produce observation_event rows with an integer quantity in eaches, the unit recorded, and occurred_at resolved, rejecting any line whose conversion is not exact. Files reach 4 GB. Single pass, no floating point. State your bounds and what you do with the zoneless date.

Approach

  1. Scan with indices, not a regex. Find the segment terminator, then split the payload on : by scanning forward and slicing — O(bytes) with no backtracking and no per-line allocation beyond the output row. A regex with nested quantifiers over 4 GB is where this job silently becomes CPU-bound.
  2. Parse the decimal as an integer mantissa plus a scale. 4.500 becomes (mantissa = 4500, scale = 3): accumulate digits into an int64, count digits after the point, and never call the runtime's float parser. Reject a mantissa that would overflow int64 when multiplied by the pack factor, checking before the multiply rather than after.
  3. Convert by integer arithmetic and demand exactness: numerator = mantissa * factor; emit numerator / 10^scale only when numerator % 10^scale == 0, otherwise reject the line with a reason code. Worked: 4.500 CS at 12 eaches per case gives 54000/1000 = 54, exact. 0.125 PL at 36 eaches per pallet gives 4500/1000 = 4.5, which is half a sealed unit and must fail at the boundary, not round three services away.
  4. Select the pack factor by the file's effective date, not by the current item master. Factors are properties of a point in time — a supplier moving a case from 12 to 10 must not reinterpret last quarter's file — so the lookup is a range query on the factor's validity interval.
  5. 20260317 carries no zone and no time. Attach the IANA zone of the node the segment refers to, captured at ingestion, because it cannot be recovered downstream; then pick and document one convention for a date-only field (start of local day is the usual choice) and store the raw local string beside the resolved instant so the assumption stays visible. Complexity: O(total bytes) time, O(longest line) working space, one pass.

Worked solution (20 min)

  1. Write the integer conversion as a pure function (mantissa, scale, factor) -> Result<int64> and unit-test it before touching the file reader.
  2. Table-drive the cases: exact conversion, non-dividing conversion, a mantissa near int64 overflow, a zero quantity, and a unit not in the ladder.
  3. Wire the scanner, emitting one rejection row per bad line with the raw segment attached so the partner can be shown what was sent.
  4. Feed a fixture file containing a date on a day the destination zone changes offset, and assert the resolved instant against a hand-computed value.
  5. Re-run the whole file and confirm identical output, including identical rejections.

Expected result: 4.500 CS at factor 12 yields 54 eaches; 0.125 PL at factor 36 is rejected as non-exact rather than stored as 4 or 5; every emitted row carries both the eaches integer and the source unit; the date-only field resolves through the node's zone, not the server's.

Verify it yourself

  • No float type appears anywhere between the file bytes and the stored quantity — grep the parser for it.
  • Summing the emitted eaches and converting back to the source unit reproduces the source decimal exactly for every accepted line.
  • Changing the process time zone leaves every resolved occurred_at unchanged.

Follow-up

  • The same file is re-sent in full tomorrow with three lines changed. What makes the second ingest a no-op for the unchanged lines?
  • A line names a unit that is not in the ladder for that item. Reject the line, reject the file, or quarantine it — and who finds out?
  • The partner starts sending an offset like +0100 on some dates. Does that change your storage decision for a future appointment as well as a past event?

Decide whether available-to-promise is derived or stored

Hard · denormalisation · read path · cache staleness

Available to promise at one node equals inventory_position.qty in state on_hand minus the sum of allocation.qty in status held or committed for that key. The read sits inside checkout: 4,000 reads per second at peak, p99 budget 20 ms. inventory_position holds 40 million rows; during a promotion a single hot item-node pair carries about 2,000 open allocations. Decide whether to compute this per read or to maintain available_qty on the position row. Give both paths, the failure mode of each, and what the browse read may do that the allocate read may not.

Approach

  1. Cost the derived read in tuples, not in plans. One index probe into the position is trivial; the aggregate over allocation touches one tuple per open claim on that key. Even if only a tenth of peak lands on the hot pair, 400 reads per second times 2,000 rows is 800,000 index tuples per second for one item at one node, and it gets worse exactly as the key gets hotter. A partial index on status IN ('held','committed') does not help, because the row count is the cost, not the selectivity.
  2. Maintaining available_qty on the position row is nearly free on the contention axis, which is the point most candidates miss. The allocator already takes a row lock or a compare-and-set on that same position row, so the extra column costs no lock that was not already held, and the read collapses to a single primary-key probe well inside 20 ms.
  3. It is not free on the correctness axis. available_qty becomes a second derived value that can drift, so it joins qty under the same reconciliation job with its own re-sum against the allocation table, and a drift alert on that comparison is the operational price of the read speed.
  4. Split the two reads rather than arguing about one. The browse read may serve from a replica or a short-TTL cache and must err low, showing less than is there. The allocate read must be on the primary and inside the same transaction as the conditional update, because replication lag peaks during the write burst that made the key hot. Conflating them is what makes a replica read look safe in a benchmark.
  5. Pick the TTL against the drain rate, with a number. If a promotion burns 2,000 units in a minute, a 30-second cache is a thousand units stale, so the displayed figure should be a banded signal such as in stock or low stock rather than an exact count that the allocate step will then contradict.
  6. Do not denormalise availability across nodes into a single network number. A yes-somewhere answer cannot be allocated against, so sourcing will disagree with the page, and the customer sees a promise retracted after checkout rather than an honest out-of-stock before it.

Follow-up

  • Roll this out on a live system. How do both paths run side by side, what do you compare, and what result gates the cut-over?
  • The reconciliation job finds available_qty drifted by 3 on one key. What do you do, and what must you not do?
  • Lot-tracked items multiply the position rows per item-node. Does your answer change, and where does the read now spend its time?

Two scan-event queries that stopped using their indexes

Medium · indexing · partition pruning · brin correlation

observation_event is range-partitioned monthly on occurred_at, with a btree on (subject_type, subject_id, occurred_at), a BRIN on occurred_at and a GiST on geo. Rows are inserted in received_at order and events are routinely hours late. Two queries regressed: (A) WHERE occurred_at::date = current_date AND observed_status = 'delivered'; (B) a backfill scanning WHERE received_at >= now() - interval '1 day'. For each, say why no index is used and no partition is pruned, and give the fix.

Approach

  1. Query A wraps the column in a cast, so the predicate is over occurred_at::date and not over occurred_at. A btree or BRIN on the bare column cannot be probed, and the planner has no range to prune partitions with, so it plans every monthly partition. Fix it by rewriting to a half-open range: occurred_at >= :start AND occurred_at < :start + interval '1 day', with :start computed by the caller from the relevant IANA zone.
  2. Do not reach for an expression index as the first fix here. The timestamptz-to-date cast is STABLE rather than IMMUTABLE because it depends on the session TimeZone, so PostgreSQL refuses to index it; indexing ((occurred_at AT TIME ZONE 'UTC')::date) is accepted but silently pins the day boundary to UTC, which is the wrong day for any stop outside it.
  3. Query B filters a column that is neither indexed nor the partition key, so it is a full scan of every partition by construction. The subtler half is why the BRIN cannot rescue it: BRIN stores a min and max per block range and only pays when physical order correlates with the column. Rows arrive in received_at order while the indexed column is occurred_at, and late events interleave hours-old timestamps with current ones, so each range's min and max span days and almost every range qualifies.
  4. Measure the correlation rather than assuming it: SELECT attname, correlation FROM pg_stats WHERE tablename = 'observation_event'. Near 1 means BRIN earns its keep, near 0 means it is a full scan with extra steps. Expect received_at near 1 and occurred_at well below it on this insert pattern.
  5. Fix B with a btree on received_at, which stays compact because it is correlated with insert order, or better, drive the backfill from a monotonic cursor it already has such as event_id, which needs no new index on a table taking tens of thousands of inserts per second.
  6. Confirm with EXPLAIN (ANALYZE, BUFFERS) and read the plan, not the clock: check how many partitions appear, whether rows removed by filter dwarfs rows returned, and whether shared buffers read matches the size of one partition or of the table.

Worked solution (25 min)

  1. Load a few million rows across three monthly partitions with occurred_at deliberately shuffled against insert order.
  2. EXPLAIN (ANALYZE, BUFFERS) query A as written and record the partitions scanned and the buffers read.
  3. Rewrite A as a half-open range and re-run, comparing partitions scanned and buffers.
  4. Query pg_stats for the correlation of occurred_at and received_at, then add a btree on received_at and re-plan query B.

Expected result: Query A plans one partition and an index scan after the rewrite, versus every partition before. Query B moves from a full scan of all partitions to an index scan on received_at, and the measured correlation explains why the existing BRIN never helped.

Verify it yourself

  • The rewritten A returns byte-identical rows to the cast version for a day fully inside the chosen zone.
  • Buffers read for A drops by roughly the ratio of one partition to the whole table.
  • pg_stats shows occurred_at correlation well below received_at, matching the insert pattern.
  • Forcing enable_seqscan = off does not make the original A use the index, confirming the cast and not the planner is the cause.

Follow-up

  • Where would you put the partition boundary given events arrive up to a week late, and what happens to a row whose occurred_at falls in a partition that has already been detached?
  • The delivered-today query is asked per node in local time. Does that change your partitioning, your index, or only the caller?
  • Adding a btree to this table costs write amplification. How would you decide whether the backfill is worth it?

Rate limit a retrying caller without extending unknown windows

Hard · rate limiting · load shedding · partial failure

Availability reads run a few thousand per second inside checkout against a low-tens-of-milliseconds p99; allocation writes are far lower but concentrate on a few hundred hot item-node pairs on a promotion day. Every caller retries on 5xx and on timeout. Design the limiting and shedding contract: what is metered and keyed by what, the response headers, which operations may degrade to a conservative answer instead of failing, and how throttling interacts with an allocation whose outcome is already unknown. Deliver the policy table and the caller behaviour you require.

Approach

  1. Meter two different resources with two mechanisms. A token bucket per client credential protects the read path, where cost is CPU and fan-out; a concurrency limiter per (item_id, node_id) protects the write path, where cost is contention on one row. A client-level limit alone does not protect a hot row, and a row-level limit alone lets one client drown every row.
  2. Fix the shed order and publish it: new reads shed first, then new allocation attempts, and the resolution path never sheds. Throttling a GET of an allocation by idempotency key strands the caller in an unknown state whose only other resolution is re-issuing the write - the limiter would then be manufacturing the duplicate it exists to prevent.
  3. Keep the retry classes unambiguous at the wire level. 429 with a Retry-After delta (RFC 9110) means nothing happened, retry later with the same key; 504 means unknown, resolve by key, do not re-issue. Give each a distinct machine code, and publish remaining quota and window in response headers - the IETF RateLimit and RateLimit-Policy fields, or your own documented equivalent - so a well-behaved client paces itself instead of discovering the limit by hitting it.
  4. Write the caller's obligations into the contract, not the wiki: full-jitter exponential backoff, and a retry budget capping retries at a fraction of successful requests. A bare Retry-After value returned to thousands of callers at once re-synchronises the herd on a single instant, which is the second outage.
  5. Degrade reads, never writes. Under shed, availability may answer from the last known position with a staleness stamp and a conservative floor that subtracts in-flight allocations, and the response must say it is degraded so checkout can decide whether to show the item. Allocation must never read a replica or a cache - that is the over-allocation invariant - so under pressure the write path refuses rather than guesses, and the error direction is under-promising.
  6. Give each hot key a single-writer queue with a bounded depth and a fast reject when full. State the depth and what it implies: queue depth divided by service rate is the wait a caller inherits, and that number must fit inside the checkout budget or the queue is just a slower failure.

Worked solution (40 min)

  1. Write the policy table: resource, key, mechanism, limit, response on exceed, caller action, and whether it may shed.
  2. Compute the hot-key numbers - allocations per second on one item-node pair, service time under the row lock, queue depth - and show the implied wait against the checkout budget.
  3. Write the three wire outcomes side by side (429, 504, degraded 200) with their codes and the caller's branch for each.
  4. Specify the degraded availability payload: the floor, the staleness stamp, and the flag that tells the caller it is degraded.
  5. Simulate a retry storm on paper: 2,000 callers receive an identical Retry-After, then add full jitter and re-derive the arrival curve.

Expected result: A table with per-credential read metering and per-item-node write concurrency, a published shed order that exempts by-key resolution, 429 and 504 carrying distinct retry semantics, and a degraded read that under-promises and says so while allocation still refuses to read a replica.

Verify it yourself

  • Under full shed, a caller can still resolve an ambiguous allocation and reach a definite answer.
  • A degraded availability response is distinguishable from a fresh one without inspecting latency or guessing.
  • Load on one hot item-node pair does not raise error rates for unrelated items sharing the fleet.

Follow-up

  • Your largest caller is also the hardest retrier. How do you stop their retries consuming the quota their first attempts need?
  • What does checkout render on a degraded availability answer, and who owns the decision - you or them?
  • At what measured point do you stop shedding reads and start refusing to accept new orders at all?

Accept a reconnecting site's six hours of buffered movements

Medium · partial failure · batch api · at-least-once

A Warehouse Execution Service buffered movements locally through a six-hour network partition and now has 40,000 records to replay. Design POST /movements:batch. Cover the per-record shape (caller idempotency_key, client_sequence, item, node, state, delta_qty, uom_code, occurred_at), the size limits, the response when record 8,113 fails validation while the rest apply, and the acknowledgement the site uses to truncate its buffer. The site must lose nothing and apply nothing twice. State what it does on 429, on 413 and on a timeout.

Approach

  1. Put the idempotency at the record, not the batch. The unit of work is one movement, because a partially applied batch has to be resumable; echo the site's client_sequence back so the acknowledgement is positional and the buffer can be truncated precisely rather than approximately.
  2. Apply with one statement: INSERT ... ON CONFLICT (idempotency_key) DO NOTHING RETURNING idempotency_key, then derive per-record outcomes of applied, duplicate or rejected from what came back. That is O(n) in one round trip instead of n round trips, and it makes duplicate a success rather than an error - which it must be, since at-least-once delivery is the premise.
  3. Return 200 with a per-record array and keep business outcomes out of the transport status. One bad record must not make the response non-2xx, or the site re-sends 40,000 records whose individual fate it cannot determine. Avoid 207, which is defined for WebDAV in RFC 4918 and reaches general clients as an unrecognised 2xx.
  4. Bound the request: cap records and bytes, answer 413 with the limit in the body so the client can split deterministically, and use 429 with Retry-After for backpressure. Write the rule that the site retains every record until that record is individually acknowledged - a timeout is indistinguishable from success, so it re-sends and the keys absorb it.
  5. Decide ordering deliberately. Signed deltas commute for conservation, so movements need no global order, but state transitions and allocation consumption do not commute; either apply within an (item, node) group in client_sequence order or reject a record whose prerequisite is absent with a retryable code, and say which you chose and why.
  6. Finish with reconciliation rather than trust: after the replay, re-sum inventory_movement into inventory_position for the affected keys, compare against the running totals and raise variance instead of assuming the incremental path got it right.

Follow-up

  • Two sites reconnect at once and saturate the write path. What sheds first, and what must never shed?
  • A buffered pick conflicts with a cycle count taken during the partition. Which one wins, and what evidence survives?
  • How does the site learn that a record it sent three hours ago was rejected, and who owns it after that?

Cut-off misses and on-time drift after a clock rule change

Medium · timezones · dst · event time

Two reports arrive the same week. On the Sunday one region changed its UTC offset, 1,900 lines at nodes in that region were released an hour after the node's 17:00 local shipping cut-off and missed the trailer. Separately, on-time percentage for legs whose dest_zone differs from the origin's reads four points below the carrier's own figure, every week. promised_ship_at is a TIMESTAMPTZ computed at order creation, and on-time compares the delivery instant to the promised day in UTC. Find the single root cause and give the storage and comparison rules you would adopt.

Approach

  1. Test whether each symptom is zone-dependent before theorising. Group the cut-off misses by node time zone and by date: clustering on one date and one set of zones points at an offset rule, while a uniform spread across zones and dates would point at a scheduler delay. Group the on-time disputes by whether the UTC day and the dest_zone local day of the delivery instant differ; if every dispute falls inside that group, the comparison is the defect. The containment runs one way only: a leg delivered days early also has differing UTC and local dates and is on time under either rule, so the differing-dates group is much larger than the disputed set and finding members of it that are not disputed proves nothing.
  2. Re-derive the stored promise for the affected lines today and diff it against the stored value. A difference of exactly 3,600 seconds on rows created before the rule change is the signature of a future local time that was resolved to an instant at write time using the offset then in force. The instant did not move; the local wall time it represents did.
  3. State the distinction the fix rests on. Comparing two instants needs no zone at all, so if the promise really were an instant, on-time would be a plain comparison. The zone matters because both of these promises are statements about local wall time: a 17:00 cut-off at a node, and delivery within the promised local day at the stop.
  4. Adopt the storage rule by tense. A past event is an instant, so occurred_at, recorded_at, actual_depart_at and actual_arrive_at stay TIMESTAMPTZ. A future local policy is local wall time plus an IANA zone identifier resolved at read time, so the cut-off becomes (17:00, node.time_zone) and the promise carries promise_zone, which the schema already has. Evaluate on-time in the dest_zone local calendar.
  5. Handle duration arithmetic separately, because it breaks in the other direction. A leg departing 18:00 local and arriving 18:00 local the next day spans 23 elapsed hours across a spring-forward boundary and 25 across a fall-back, so planned transit must be expressed in one domain and converted deliberately rather than by subtracting instants and assuming 24.
  6. Close the ingestion hole and the deployment hole. Partner feeds that send a bare date with no zone must have the zone attached at ingestion from the node or stop, because it cannot be recovered later, and the tzdata version baked into the image must be refreshed on a schedule, since a stale database reproduces exactly this bug for a rule that changed after the image was built.

Follow-up

  • Not every offset change is a whole hour and some zones sit at :30 or :45. Which parts of your fix assume an hour?
  • A node in a zone that does not observe daylight saving ships to one that does. Where does the transit duration have to be computed?
  • How do you backfill on-time for the last two quarters without restating figures that were already invoiced?

Suggested practice checklist

Four days sample coding, design, fundamentals and the practical rounds at deliberately shallow depth, which is enough to surface the topics you did not know were in scope. That map, rather than a guess made on day one, decides where the last three days go.

Day 1 — Coding, one pass at shallow depth

  • Solve one problem from each of six families, an array with two pointers, hash counting, binary search, a tree traversal, a graph traversal and one dynamic program, under a hard twenty-minute cap with no extensions, marking each finished, late, or stalled.
  • For every stall, write the exact move you could not make rather than the subject, so the note reads could not turn the recurrence into a loop rather than bad at dynamic programming.
  • Fix nothing today. The value of the pass is the unfixed record.

Deliverable: Six timed attempts marked finished, late or stalled, each stall carrying a named blocking move.

Day 2 — Design, one pass at shallow depth

  • Spend twenty minutes each on three different shapes, a read-heavy feed, a write-heavy ingest path, and something needing a transaction across two entities, stopping each at requirements, interface and data model.
  • After each, write the first question you could not answer, which is usually a number you could not estimate or a failure mode you had no vocabulary for.
  • Mark which of the three you would be most relieved not to be asked, and treat that as data rather than as a preference.

Deliverable: Three shallow designs, each with the first unanswerable question written at the bottom.

Day 3 — Fundamentals and the practical rounds

  • Answer eight short questions in writing at four minutes each, covering the material that fills the gaps between the big rounds: what happens between a URL and a rendered page, what an index costs on write, when a process is preferable to a thread, and what conditions a deadlock requires.
  • Do one thirty-minute practical task of the kind a take-home compresses: read an unfamiliar two-hundred-line file and write what it does, what you would change, and the one thing you remain unsure of.
  • Score every answer fluent, correct but slow, or absent, and keep the absent ones visible.

Deliverable: Eight scored short answers and one written reading of unfamiliar code.

Day 4 — The rounds that are about you, and the map

  • Deliver three behavioural answers aloud against a timer, a conflict, a failure you owned, and a decision made without enough information, marking any that ran past three minutes or contained no number.
  • Assemble the map: every marked item from days one to three on a single page, sorted by how likely it is to appear in your loop rather than by how uncomfortable it felt.
  • Choose exactly two areas for the remaining three days and write down what you are deliberately abandoning.

Deliverable: A one-page scored map of the whole surface area with two areas chosen and the rest explicitly abandoned.

Day 5 — First chosen area, to the depth you skipped

  • Work the higher-ranked area in four focused blocks, choosing items one level above where you stalled rather than repeating what already works.
  • After each block write the rule you extracted in one sentence with its precondition attached, since a rule carrying no precondition is exactly what fails under a variation.
  • Re-attempt the day-one or day-two item that exposed this area and compare against the original timing.

Deliverable: Four worked blocks, a timed re-attempt against the original, and three one-sentence rules with preconditions.

Day 6 — Second chosen area, where the gap is coverage rather than speed

  • Treat the second area differently from the first. Day five drilled something you could already half-do; this one is usually a topic you had simply never met, so build one worked reference example end to end and keep it, rather than attempting six problems badly.
  • Write down the vocabulary you were missing on day two or three, five terms at most, each with the one sentence that makes it usable in an answer rather than the textbook definition.
  • Redo the shallow attempt that exposed this area and note whether you now fail later in the problem, because moving the failure point is the realistic gain from a single day and is worth more than a score that did not change.

Deliverable: One worked reference example for the newly covered area, a five-term vocabulary list, and a note on where the failure point moved.

Day 7 — Reassemble the loop

  • Sit two rounds back to back with no gap, ordering them so the area you chose second comes last, because the map was built from rested, isolated attempts and the loop will reach your weaker area when you are already spent.
  • Write where the second round suffered from the first, which is normally the point at which structure collapses into narration.
  • Reduce the week to one page holding only the rules you can state without reading them.

Deliverable: Mock notes on cross-round carryover plus a one-page card of rules you can recite from memory.

The bug you decided not to fix

Engineers over-index on what they repaired. A stronger answer covers something you knowingly left broken: the alert you tuned down, the data inconsistency you documented instead of chasing, the cleanup you deferred past two quarters. Give the reasoning and the condition that would have reopened it, so it reads as a decision and not as neglect.

Estimate offline site operation you have never built

Hard · estimation · partition tolerance · unknowns · reconciliation

A site lost its wide-area link for six hours and picking stopped, because every scan confirmation needed the central inventory service. You are asked for an estimate to keep the floor running through a partition and reconcile afterwards. You have never built store-and-forward. Deliverable: an estimate with a stated range, the three unknowns that dominate its variance, what you would build in one week to collapse them, and the part of the problem you would declare out of scope. Be explicit about what the site may and may not decide on its own while cut off.

Approach

  1. Decompose before you price. Buffering scans locally, replaying them on reconnect, resolving conflicts, and deciding what the site may do unilaterally are four different pieces with different risk, and an estimate that does not separate them cannot be argued with.
  2. Split the work by what the partition permits, because this is the decision that sizes everything else. Recording movements against work already released is safe offline: they are append-only facts with device-minted idempotency keys and a local sequence, and replay is a no-op for anything already applied. Allocating new demand is not safe offline, because a promise requires a lock on a position the site cannot see. So the honest scope is that the floor keeps executing released work and stops accepting new promises, and a strong answer says that out loud rather than estimating a system that cannot exist.
  3. Name the three unknowns that actually drive variance rather than listing every risk: how the handheld software buffers and whether it can mint durable keys at all, what the reconnect storm looks like when several hours of movements replay against contended positions, and how conflicts are resolved when a cycle count taken centrally during the partition contradicts the replayed picks.
  4. Propose a one-week spike that collapses those specifically: instrument one site for real device counts and offline duration distribution, write the replay against a copy of production movement volume to measure the reconnect burst, and take the conflict question to operations rather than solving it in code, since deciding which of two contradictory counts wins is a business rule.
  5. Give the estimate as a range with the assumption attached to each end, and say what moves it. A single number invites being held to it; a range whose width is explained by the three unknowns invites funding the spike that narrows it.
  6. Declare the out-of-scope part explicitly and defend it. Offline allocation, offline rating and offline label purchase all require an authority the site does not have during a partition, and pretending otherwise converts an availability problem into a correctness problem that surfaces days later as oversold stock.

Follow-up

  • The site comes back with six hours of movements and central has already adjusted two of those positions by cycle count. Which wins, and who decided?
  • How would you give the estimate differently to a director who wants a date than to the engineer who will build it?
  • What would make you refuse to give a number at all, and what would you offer instead?

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?

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?