Interview guides · Knapheide Manufacturing
Knapheide Manufacturing Software Engineer Interview Questions & Guide 2026
Code reading practice for Knapheide Manufacturing software engineer prep: work out what unfamiliar code does before judging it.
The 60-second brief
A Software Engineer at Knapheide Manufacturing plays a pivotal role in bridging the gap between traditional industrial craftsmanship and modern digital efficiency. As a leader in work truck bodies and truck equipment, Knapheide Manufacturing relies on software solutions to optimize assembly operations, manage complex tooling lifecycles, and maintain the high-quality standards that define the brand. You will be responsible for creating robust, scalable, and efficient code that directly supports the manufacturing floor and the broader organizational goals.
Treat capacity estimation as a conversion skill rather than a table to memorise: turn a user count and an action rate into requests per second and bytes per day, then name the component that number breaks first. The figure only matters if it changes the design.
Focus areas
- Reserve inventory without overselling under concurrent allocation
- Make every external side effect safe to retry
- Reconcile a movement ledger against derived positions
Overview
This role is not just about writing code; it is about understanding the intersection of software logic and physical manufacturing processes. You will collaborate with cross-functional teams to design systems that improve safety, quality, and delivery speed. Whether you are building internal tools, managing data systems, or automating workflows, your work will have a tangible impact on how products move from design to the finished vehicle, making this an ideal position for engineers who enjoy seeing the real-world results of their technical labor.
The Knapheide Manufacturing interview process
Knapheide Manufacturing candidates report 3 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.
Initial Screening
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 Evaluations
Input bounds are the part of the prompt most often skimmed, and they usually contain the answer. They tell you which complexity class is admissible, which narrows the search before you have thought about the problem itself. As a rough planning figure, a compiled language does on the order of 10^8 simple operations per second and an interpreted one roughly an order of magnitude less. So n up to about twenty admits enumerating subsets, a few thousand admits a quadratic pass, and a million admits neither: you need near-linear, or linear with a log factor. If the bounds are missing, ask for them.
What to demonstrate
- Whether the approach is justified by the stated input size rather than by whichever pattern you recognised first
- Whether you ask about the properties that change the algorithm: whether the input arrives sorted, whether duplicates occur, whether values are bounded integers, whether it all fits in memory
- Whether you can name the bottleneck in your own solution and what would remove it, even when you deliberately leave it in place
- Whether a claimed speedup is real, since memoising a recursion only helps when subproblems genuinely overlap and the state can be keyed cheaply
How to prepare
- For each algorithm you rely on, write down the largest n it handles in roughly a second, then check two of those figures by timing them in the language you will actually type in
- For two weeks, write one line naming your target complexity and the bound that justifies it before you write any code, then compare that line with what you ended up submitting
- Practise the conversion backwards: given a required O(n log n), list the mechanisms that get you there (sorting, a heap, an ordered map, divide and conquer) and choose by what the problem needs to query, not by what you used last
Practical Technical Assessment
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
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.
Ordering events by arrival rather than by event time
Offline handhelds, batch partner feeds and store-and-forward gateways deliver observations out of order as a matter of course, so a pipeline that folds whatever arrived last will flap a delivered shipment back to in transit and recompute on-time performance from the wrong facts. Message-broker ordering guarantees do not rescue this: per-partition ordering only holds within a partition, so unless the producer keys by the entity being tracked, two events for one leg can land on different partitions and be processed concurrently. The defence has two halves that are often confused - deduplicate on a stable key, then fold with a monotonic status lattice ordered by occurred_at - and both are needed, because deduplication alone still lets a stale event win. Note also that occurred_at is device-reported and therefore sometimes wrong, which is why storing received_at and a measured clock offset beside it is what makes event-time logic auditable instead of merely plausible.
Listing technologies instead of trade-offs
Name the property the design needs first, such as ordered range scans, multi-entity transactions, cheap appends, or a predictable p99, then pick something that provides it and say what it gives up in exchange. Almost any component is defensible once you state the requirement it satisfies and the one it sacrifices.
Hardcoding to the sample inputs
Solve the stated problem rather than the two examples; special-casing a literal to make a sample pass is obvious immediately and reads as either a misunderstanding or an attempt to fake progress. If you genuinely cannot generalise yet, say which part is a stub and what would replace it.
Questions & practice
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
- 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.
- Emit 2n endpoints,
(t, +1)at each arrival and(t, -1)at each departure, sort byt, and at equaltorder the-1before 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. - 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. - Complexity: O(n log n) dominated by the sort, O(n) space. If rows already arrive ordered by
planned_arrive_atfrom 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. - 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.
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?
Trace a suspect lot forward to every shipped order line
Medium · graph traversal · genealogy · indexing
lot_edge(parent_lot_id, child_lot_id) has 400 million rows with btree indexes on both (parent_lot_id, child_lot_id) and (child_lot_id, parent_lot_id). lot_shipment(lot_id, order_line_id) maps lots to shipped lines. Given one suspect received lot, return every downstream order_line_id. The reachable subgraph can reach 2 million lots and 12 levels; the full edge table does not fit in memory. Give the traversal, its complexity in terms of the reachable subgraph, and what you do if the data contains a cycle.
Approach
- Genealogy is a directed acyclic graph, not a tree: a production run consumes several lots and one lot splits across many shipments, so a node is reached by several paths and a tree traversal revisits it combinatorially. Breadth-first from the suspect lot over the
(parent_lot_id, ...)index, with a hash set of visited lot ids, is O(V' + E') where V' and E' count only the reachable subgraph — 2 million 8-byte ids is 16 MB of payload and a few tens of MB with open-addressing overhead, which is why the visited set is affordable and the edge table's 400 million rows never enter memory. - Batch each level into one indexed read. Issue
WHERE parent_lot_id = ANY($1)with a few thousand parents per call instead of a query per node: at 12 levels and 2 million nodes, per-node round trips dominate the wall clock entirely, and the recall's deadline is a real-world response time, not a query-plan aesthetic. The batch size is the tuning knob between round-trip count and per-statement planning cost. - Keep the visited set even though the schema says DAG. Rework and repack loops — material returned into an earlier lot — do occur, and an unguarded traversal on a cyclic edge set does not terminate; a set membership test is O(1) and turns a hang into a correct answer plus a data-quality alert. Also cap the traversal by level and by node count, and fail loudly on the cap rather than returning a partial set silently.
- Join to
lot_shipmentonce at the end over every visited lot id, not over the leaves and not per node. A lot is routinely both shipped and consumed: half a lot goes out on an order line while the other half is repacked into a child lot, so it has alot_shipmentrow and outgoing edges at the same time and is not a leaf. Restricting the join to nodes with no outgoing edges drops exactly those order lines, which is under-recall — the one failure mode this trace is not allowed to have. One bulk join against an index onlot_idover the whole visited set is a single pass; per-node lookups multiply the round-trip problem by the number of visited lots. - State the semantics honestly: reachability answers which shipped lines could contain material from the suspect lot, and without propagating edge quantities it is a superset of physical containment. For a recall that is the correct direction of error — over-recall, never under-recall — and saying so is the point, because a trace that returns quickly and incompletely will be believed. The upstream question, which received lots are in a suspect shipped unit, is the same traversal over the reverse index, which is why both indexes exist.
Worked solution (30 min)
- Draw a six-lot fixture containing a merge (two parents into one child), a split (one parent into three children), and one interior lot that is partly shipped on an order line and partly consumed into a child lot, then write down the expected reachable set and the expected order lines by hand.
- Implement level-batched BFS with an explicit frontier list, a visited hash set, and a per-level batch read.
- Add a cycle to the fixture and confirm the traversal terminates and flags it rather than hanging.
- Bulk-join
lot_shipmentonce over every visited lot id — the whole visited set, not the leaves — and count the queries issued, confirming it is one per level plus one. - Re-run with the frontier order reversed to confirm the output set is unchanged.
Expected result: Every lot downstream of the suspect appears exactly once in the visited set; the merge node is visited once, not twice; the partly-shipped interior lot contributes its order line even though it has children; the cyclic fixture terminates with the same reachable set plus a flagged edge; the query count is levels + 1, not proportional to node count.
Verify it yourself
- The reachable set is a superset of the set you computed by hand, and equal to it on the acyclic fixture.
- Restricting the final join to lots with no outgoing edges drops the partly-shipped interior lot's order line — the failing test that pins the over-recall rule to code.
- Running the same traversal over the reverse index from a leaf returns the suspect lot.
- Removing the visited set makes the diamond fixture return duplicates — a failing test that documents why the set is there.
Follow-up
- A recursive CTE would express this in ten lines. What is its cost here if the index on the traversal column is missing, and how would you notice?
- You need the quantity of suspect material in each downstream line, not just reachability. What has to be on the edge, and what does that do to the traversal?
- The traversal must run while a production run is writing new edges. What does a consistent answer mean here, and what do you cut it at?
Find the hottest write keys under a fixed memory budget
Medium · top-k · heap · streaming sketches
One day of writes to inventory_movement is 200 million rows; each carries (item_id, node_id). Distinct pairs reach 60 million. Return the 200 pairs with the most writes so they can be moved behind a single writer before the next peak. First give the exact answer assuming you may hold 60 million counters, then give an answer under a 200 MB budget, and state the error guarantee that second answer carries. Both must be one pass over the day's rows unless you argue for a second.
Approach
- Exact version: one hash pass to build
(item_id, node_id) -> int64, then a single scan of the map maintaining a min-heap of size K = 200, pushing and popping when the incoming count exceeds the heap root. O(n + d log K) with n = 2e8 and d = 6e7, versus O(d log d) if you sort all 60 million entries — a factor of roughly log d / log K, about 3.4x, for no benefit. The heap must be a min-heap: the root is the weakest survivor, so eviction is O(log K). - Cost the map honestly. 60 million entries at a 16-byte key, an int64 count and per-entry overhead is 2-3 GB in most runtimes, which is the whole reason for the second version rather than a theoretical aside.
- Bounded version: Misra-Gries with m counters. Keep at most m keys with counts; on a key already present increment it; on a new key when fewer than m are held, insert at 1; otherwise decrement every held counter and drop those reaching zero. With m = 1,000,000 at roughly 40 bytes per counter, that is about 40 MB, comfortably inside 200 MB alongside the heap.
- State the guarantee as a number, not as 'approximate'. Misra-Gries returns an estimate satisfying f - n/(m+1) <= f_hat <= f, so with n = 2e8 and m = 1e6 every count is an undercount by at most 200. If the 200th hottest pair has hundreds of thousands of writes, a 200-count error cannot reorder the top of the list — and if the counts near rank 200 are within 200 of each other, the ranking there was never meaningful for the decision being made. The sketch also merges: run one per shard, combine, prune back to m, and the bound holds with n the combined length.
- If exactness is genuinely required, argue for two passes rather than more memory: the Misra-Gries pass yields a candidate superset of size m, and a second pass counts only those candidates exactly in O(m) memory. That is the standard way to get an exact top-K under a memory bound, and the cost is one extra read of the day's rows.
Follow-up
- The decision downstream is which keys get a single writer. Does a rank error at position 190 change that decision, and what does the answer tell you about how much accuracy to buy?
- Write volume on one key is bursty — a promotion concentrates it into twenty minutes. Does a daily top-K find it, and what window would?
- How do you keep the sketch across a worker restart without replaying the day?
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
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
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?
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
- 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.
- 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.
- 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.
- 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.
- 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.
- 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)
- 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.
- Write the CTE selecting affected subject_ids by received_at, then the DISTINCT ON fold joined to the rank lookup.
- Run it and confirm delivered is returned, then shuffle the insert order of the same batch into a fresh table and re-run.
- 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?
Write pseudo-code that, given three integers (a, b, c), returns their sum; however, if any two of the variables are equal, do not include one (e.g., if a=b, return a+c or b+c but not a+b+c).
Medium
Approach
- State your assumptions explicitly before working the problem.
- Say what you would check first and why it is the highest-information step.
- Work from the requirement backwards to the design.
Follow-up
- How would you know your answer was wrong?
- What assumption would you test first?
They ask questions about an area of your knowledge until you fail to gauge your depth.
Medium
Approach
- Clarify what is being asked and what a complete answer contains.
- State your assumptions explicitly before working the problem.
- Work from the requirement backwards to the design.
Follow-up
- How would you know your answer was wrong?
- What assumption would you test first?
What exactly are Bootstrap.js and jQuery, and why are they helpful?
Medium
Approach
- Work from the requirement backwards to the design.
- State your assumptions explicitly before working the problem.
- Say what you would check first and why it is the highest-information step.
Follow-up
- What assumption would you test first?
- How would you know your answer was wrong?
Evolve an availability contract without breaking floor devices
Hard · versioning · backward compatibility · units
GET /availability currently returns {item_id, node_id, qty}, where qty is denominated in whatever the item master says today - eaches for some items, cases for others. Three clients consume it: a checkout service you deploy, a partner integration on a twelve-month release cycle, and handheld firmware on the warehouse floor you cannot force-upgrade. You must expose the unit explicitly, split qty into on_hand, available and reserved, and add a new inventory state. Deliver the field plan, the version negotiation mechanism, the deprecation path, and how you prove nobody still reads the old field.
Approach
- Start from the rule that survives contact with un-upgradable clients: never change the meaning or the denomination of a live field. qty keeps precisely its current semantics forever, and the new truth ships as new fields - available_qty with an explicit uom_code, plus the on_hand and reserved split - so the handheld keeps reading exactly what it always read.
- Be precise about which additions are safe. New fields are safe only if readers ignore unknown ones, so state tolerant-reader expectations in the contract. Adding a value to an existing enum is not safe: a client switching exhaustively over inventory state breaks on the new state. Either declare the enum open in the current version with a mandated fallback branch, or carry the new state in a parallel field that old clients never see. Choose one and write it down.
- Pin versions per client credential rather than per deploy - an account-level default, overridable per request by a version header - so nobody is upgraded by your release. Name the cost honestly: you now serve N shapes from one code path, so cap N with a published support window and a rule that a frozen version gets no changes, not even helpful ones.
- Prove the dependency instead of assuming it. Count reads of the deprecated field per client credential, then send Sunset (RFC 8594) with a date and a deprecation link on a horizon longer than the slowest client's release cycle - a twelve-month partner cycle sets the floor, and the handheld fleet may not clear it at all, which is a planning input rather than a surprise.
- Guard it in CI with consumer-driven contract tests that fail on any field removal, type change or enum widening in a frozen version. A review checklist does not survive a rushed release; the test does.
- Close the loop on the unit trap explicitly: when a supplier changes a case from twelve to ten, the bare qty must not silently re-denominate. Carry the conversion factor's version alongside the number, and state that values already served keep the factor in force when they were computed.
Worked solution (40 min)
- Write the current and target payloads side by side and mark every field as unchanged, added or frozen - with nothing in a fourth category.
- For the new inventory state, write what each of the three clients does when it encounters an unrecognised value today, and pick open-enum or parallel-field accordingly.
- Specify the negotiation: the credential-level default, the override header, the error for an unknown or retired version, and the support window.
- Design the usage telemetry that tells you which credential still reads qty, and state the threshold at which you would send Sunset.
- Write the CI contract test that fails on a removal, a type change or an enum widening in a frozen version, and run it against the proposed change.
Expected result: An additive plan where qty is frozen rather than redefined, the unit travels on every new quantity field, enum evolution is settled explicitly, versions are pinned per credential, and the deprecation date is derived from measured usage plus the slowest client's release cycle.
Verify it yourself
- A replay of captured handheld requests against the new build produces byte-compatible values in every field that firmware reads.
- A client pinned to the old version never sees the new state value in any field it parses.
- The contract test fails when someone changes qty's type or adds an enum value to the frozen version.
Follow-up
- A client sends a version header you retired last quarter. What do you return, and what will their code do with it?
- You need one breaking change on one endpoint. How do you ship it without versioning the whole API?
- The Sunset date arrives and the handheld fleet still reads the deprecated field. What are your options, in order of cost?
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
- 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.
- 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.
- 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.
- 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.
- 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.
- 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
Day one measures instead of guessing, under a fixed rubric, and the remaining hours are allocated in proportion to the gaps before any studying begins. The allocation is deliberately not renegotiated midweek, because the area that feels worst on day three is usually the one that is moving.
Day 1 — Diagnostic, scored before you study anything
- Sit a 110-minute diagnostic in four blocks: forty-five minutes on two coding problems, twenty-five on one design prompt taken to interface and data model, twenty of short-answer fundamentals, and twenty delivering two behavioural answers aloud.
- Score each block from 0 to 3 on a fixed rubric where 3 is correct and fluent, 2 is correct but slow or prompted, 1 is partially correct and 0 is stuck, grading the artifact rather than how the attempt felt.
- Allocate days two to five in proportion to 3 minus each block's score, write the allocation down, and commit to leaving it alone.
Deliverable: A scored rubric and a fixed hour allocation for the rest of the week.
Day 2 — Largest gap: find the boundary rather than the subject
- Split the weakest area into named sub-skills and rate each separately. For coding those are restating the problem, choosing the structure, stating the invariant, turning the invariant into loop bounds, handling empty and single-element input, and accounting for complexity out loud.
- Attempt three items positioned just above where the rating drops off, and for each write the first move you failed to make.
- Re-attempt one of them from blank four hours later with nothing open.
Deliverable: A sub-skill map with the two blocking sub-skills circled.
Day 3 — Drill the blocking sub-skill by repeating the shape
- Do eight short repetitions of the same shape rather than eight different problems, so what gets practised is the pattern and not the puzzle.
- State the rule you now hold in one sentence, then test it against a case built to break it, a sliding window over an array containing negative values, or a cache-aside read path whose invalidation message is dropped.
- Have someone else read your one-sentence rule and find the precondition you left out.
Deliverable: One rule statement with its preconditions attached and one counterexample that would have caught the incomplete version.
Day 4 — Second gap, plus maintenance on the strongest area
- Run the same sub-skill decomposition on the second-largest gap in half the time.
- Spend twenty-five timed minutes on the block you scored highest, choosing the hardest item you can still finish rather than a warm-up.
- Write whether each area fails you on recall, on setup, or on execution, and set the fix accordingly: repetition for recall, a written checklist for setup, timed work for execution.
Deliverable: A second sub-skill map plus a one-line failure diagnosis for each area.
Day 5 — The gap that is not a skill
- Record one technical and one behavioural answer, then count two things in the playback: seconds before your first clarifying question, and sentences you began without knowing where they would end.
- Practise saying that you do not know, followed by how you would find out, without letting it soften into a guess, and practise stating a complexity or an estimate before being asked for it.
- Redeliver one answer under a hard ninety-second cap, which forces structure ahead of detail.
Deliverable: Two recordings with a counted reduction in time-to-first-question.
Day 6 — Retest under day-one conditions
- Sit the same 110-minute structure with new prompts of comparable difficulty and score it on the identical rubric.
- For any block that did not move, change the method rather than adding hours: a block stuck at 1 usually means the practice was too varied, not too short.
- Write down which single block you would still lose the offer on.
Deliverable: A second scored rubric placed beside the first, with one named remaining risk.
Day 7 — Full loop under interview conditions
- Run a sixty-minute mock over the two blocks that moved least, with an interviewer briefed to interrupt and change direction mid-answer.
- Write the recovery script for going blank: restate the question, state your assumption, name the first thing you would check.
- Say every rule from the week aloud without reading it, and cut any you cannot state in a single sentence, since a rule you have to reconstruct mid-answer will not survive an interruption.
Deliverable: A one-page card holding the recovery script and only the rules you could state from memory.
Disagreement needs an outcome you did not choose
Conflict answers where you were right and everyone came round are the weakest ones. Stronger: the evidence you went and collected, what would have changed your mind, and what you did in the weeks after the call went against you. Implementing a design you argued against, properly, is a specific and checkable behaviour.
What makes you the best candidate for this job?
Medium · behavioural and engineering judgement
Approach
- Give the blast radius: what could have broken, and what you measured.
- Name the disagreement and how you resolved it with evidence.
- Close with what you would do differently, concretely.
Follow-up
- What would you do differently if you ran that again?
- How did you know your change caused the improvement?
What is your biggest weakness?
Medium · behavioural and engineering judgement
Approach
- Close with what you would do differently, concretely.
- Name the disagreement and how you resolved it with evidence.
- 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?
Tell me about a time when you did something without supervision.
Medium · behavioural and engineering judgement
Approach
- Give the blast radius: what could have broken, and what you measured.
- Name the disagreement and how you resolved it with evidence.
- State the situation in two sentences and spend the rest on the reasoning.
Follow-up
- How did you know your change caused the improvement?
- What did you decide not to do, and why?