cswork / libraryOpen practice workspace

Interview guides · Multimatic Manufacturing

Multimatic Manufacturing Software Engineer Interview Questions & Guide 2026

Multimatic Manufacturing software engineer prep on partial failure: a timed-out request has an unknown outcome, not a failed one.

The 60-second brief

A Software Engineer at Multimatic Manufacturing plays a critical role in bridging the gap between high-level engineering design and precision manufacturing execution. You are not just writing code; you are building the digital infrastructure that enables the production of complex, high-performance automotive components. Your work directly impacts the efficiency of production cells, the accuracy of simulation models, and the overall quality of the products that leave the floor.

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
  • Keep a site operating during a network partition

Overview

This role is intellectually demanding and highly specialized. You will likely contribute to projects involving CAD/CAE integration, process optimization, and the interpretation of complex technical specifications. Because Multimatic Manufacturing operates at the intersection of mechanical excellence and software-driven precision, you must be comfortable working alongside mechanical and manufacturing engineers to translate physical constraints into robust software solutions.

Expect an environment that values deep technical expertise and a practical, "hands-on" mindset. The work is often centered on solving tangible problems—improving cycle times, enhancing data accuracy in simulations, or streamlining data-heavy manufacturing processes. You will be a key contributor to the company’s ability to remain competitive in a fast-paced, high-stakes automotive industry.

The Multimatic Manufacturing interview process

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

Phone Screen

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

What to demonstrate

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

How to prepare

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

Technical Discussion

What this round decides is narrow: whether you can produce code that runs and is correct on inputs nobody showed you. An elegant solution that does not compile scores below a plain one that does, so write a correct brute force first, say out loud that you know its cost, and improve it with the working version still on screen. What separates strong answers is who finds the broken case. Trace your own code against an empty input, a single element, and duplicate keys before you say you are finished, because being told is far more expensive than noticing.

What to demonstrate

  • Whether degenerate inputs get checked without being asked for: an empty collection, one element, every element equal, and the extreme value the input type allows
  • Whether the complexity you state matches the code you actually wrote, including a sort or a copy sitting inside a loop
  • Whether the finished answer is verified against the worked examples before you call it done, rather than assumed correct because the code reads correctly

How to prepare

  • Take five problems you have already solved and, without running anything, write down what each returns for empty input, a single element, and all-duplicates. Then run them and count how many you predicted wrong.
  • Drill the brute force as its own skill: on ten problems, write only the obviously-correct slow version and time how long it takes to get it passing. If that is more than a few minutes, that is what to practise, not the optimal version.
  • Add a fixed last step before you submit anything, reading only the loop bounds and the initial value of each accumulator, which is where most off-by-one errors live

In-Person Component

An unlabelled round is first an information problem, and the cheapest information is free. Whoever schedules it can usually tell you how long it runs, who will be in the room and what they work on, whether you will be writing code and in what environment, and whether anything is being sent beforehand. Ask in writing so the answer is on record, then prepare for the two or three formats those answers still leave open instead of betting on one. What separates a strong candidate is not guessing right; it is having an opening that works whichever one it turns out to be.

What to demonstrate

  • Whether you can start work from an ambiguous brief, since tolerating a vague scope without stalling is the same thing the job asks for
  • Whether the questions you asked beforehand were ones that change your preparation, such as duration, medium and who is joining, rather than ones whose answers you could not have acted on
  • Whether you adapt when the round turns out to be something other than what you were told, instead of spending the first ten minutes visibly recalibrating

How to prepare

  • Send one short scheduling message asking four things: how long, who is joining and what they work on, whether you will be writing code and where, and whether to prepare anything in advance. Treat a vague reply as real information, since it means the round is loosely structured and you will be shaping it yourself.
  • Write one opening that works in any of the formats still open: restate in your own words what you have been asked to do, then ask which of two directions is more useful to them. Say it aloud until it stops sounding recited.
  • Set up for the two most likely formats before the call starts, with a blank editor in the language you would choose and a shared document you can type into, so a format surprise costs you nothing in the first minutes

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.

Treating the database as the truth about physical stock

Every system in this domain drifts from reality, through miscounts, unrecorded damage, mis-scans and theft, so a design with no adjustment path forces the drift somewhere worse. The usual shape is a CHECK constraint or application guard that clamps a position at zero: the negative that would have exposed a missing receipt is silently swallowed, and the first visible symptom is a picker standing at an empty location for an order the system promised. The correct posture is to let the invariant fail loudly - record the cycle-count adjustment as a first-class movement with its own reason code, surface the variance, and let planning see that this location is unreliable - because an adjustment that is indistinguishable from a normal pick destroys the only signal that would have found the root cause. A related version of the same mistake is deleting or updating a movement row to fix a mistake, which leaves the position correct and the history a lie.

Tests that assert on the implementation rather than the behaviour

Assert on what a caller can observe, not on the number of internal calls or the shape of a private field. A test that breaks on every refactor but still passes when the answer is wrong costs more than it protects.

Issuing one query per row of a result set

Fetch related rows in a single batched query keyed by the ids you already hold, or join them into the original query. A per-row round trip multiplies network latency by the row count, and it looks perfectly fine against the ten rows in your development database.

Questions & practice

Re-sum a movement ledger and report positions that disagree

Easy · aggregation · reconciliation · append-only ledger

inventory_movement holds 3 billion immutable rows: item_id, node_id, lot_id (NULL when the item is lot-untracked), state, delta_qty (signed int64 in the item's smallest transacting unit), uom_code, and reversed_by. inventory_position holds one row per (item_id, node_id, lot_id, state) with qty, version and last_movement_id, using lot_id = 0 as the sentinel for untracked. Up to 80 million distinct keys. Report every key whose summed movements disagree with the stored qty, while writes continue. State your time and space bounds and how you bound the comparison.

Approach

  1. Fix the key first. inventory_movement.lot_id is NULL for untracked items and inventory_position.lot_id is 0, so the group key is (item_id, node_id, COALESCE(lot_id, 0), state). Getting this wrong does not error — it silently produces two groups that each look like a variance, and the report becomes noise nobody reads.
  2. Cut the ledger at a watermark, but do not take W = max(movement_id) at the start of the scan. A sequence hands out an id before the inserting transaction commits, so at the instant you read that maximum there are ids below it still in flight and invisible to your snapshot. Summing movement_id <= W misses them on this run, and advancing reconciled_through_movement_id to W makes the miss permanent: every later incremental run starts above those ids and they are never summed again. That is a hole in the ledger's own re-derivation, not a transient skew.
  3. Take a watermark that is provably settled instead. Bound write transactions with a statement or transaction timeout so 'the longest write' is a number T, record (observed_at, max_movement_id) samples periodically, and use as W_safe the largest sampled id whose observed_at is older than T — every id at or below it has committed or rolled back. Sum rows with movement_id <= W_safe, compare only against position rows with last_movement_id <= W_safe, treat anything newer as a write that raced you rather than a variance, and advance reconciled_through_movement_id only to W_safe so the next run starts there instead of re-reading 3 billion rows. If the ledger carries a commit timestamp, cutting on that is the same guarantee without the sampling table.
  4. Sum delta_qty as int64, and include reversal rows. reversed_by is a back-pointer for audit, not an exclusion filter: an original of +10 and its reversal of -10 must both be summed to reach 0. Excluding the original while keeping the reversal produces -10 and a false variance on every corrected key.
  5. Guard the denomination rather than trusting it. uom_code is stored per row because pack factors change over time, so reject — do not sum — any row whose uom_code is not the item's smallest transacting unit, and report those keys separately. A mixed-denomination sum is arithmetically meaningless and looks exactly like a real variance.
  6. Hash aggregation is O(n) time and O(distinct keys) space: 80 million entries at roughly 40-56 bytes each in a typical runtime is 3-5 GB, so quote the number. The bounded-memory alternative is an external sort-merge on the group key — O(n log n) comparisons, resident memory bounded by the merge fan-in rather than by key count, and it streams — or hash-partition by hash(item_id) % P and run P independent passes for 1/P of the peak.

Worked solution (25 min)

  1. Write the group key expression down explicitly, including the NULL-to-0 mapping, before any aggregation code.
  2. Derive W_safe from a sample older than the transaction-duration bound T, and state in one sentence which rows on each side are in scope — this is the part that makes the report trustworthy under live writes, and the part that decides whether an in-flight row is re-read next run or lost forever.
  3. Implement the hash aggregation with an int64 accumulator and a uom_code assertion per row, emitting rejected rows to a second output rather than into the sum.
  4. Build a fixture with four keys: one clean, one with a movement missing from the position, one with an original plus its reversal, one with a row in cases where the item transacts in eaches.
  5. Run it, then re-run with the fixture rows shuffled, with a concurrent writer appending past the watermark, and with a writer that reserved an id below max(movement_id) and commits only after the scan finishes — then run the incremental pass and confirm that row is summed there.

Expected result: Exactly one key is reported as a quantity variance; the reversed key reconciles to zero difference; the mixed-unit key appears in the rejected-rows output rather than the variance list; rows appended past the watermark change nothing; the row whose id was reserved below max(movement_id) but committed late still sits above W_safe, so the next incremental run sums it instead of skipping it permanently.

Verify it yourself

  • Summing the full ledger for a clean key equals its inventory_position.qty exactly, with no tolerance — a tolerance here would hide the defect the job exists to find.
  • Two runs at the same W_safe over the same data produce byte-identical output regardless of row order.
  • Setting the watermark to max(movement_id) instead of W_safe makes the late-committing fixture row vanish from this run and from every run after it — the failing test that shows the sampled watermark is load-bearing.
  • Deleting the reversed_by filter (if you wrote one) changes the result, which tells you the filter was load-bearing and wrong.

Follow-up

  • A key shows a variance of exactly one pick, repeatedly, at one node. What do you look at first, and what would distinguish a duplicate movement from a missed one?
  • The job takes six hours and the variance report is stale by the time anyone reads it. How would you make it incremental without losing the guarantee that it re-derives from the ledger?
  • Who writes the correcting movement, and what reason code does it carry?

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

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

Split an order across the fewest nodes that can fill it

Hard · set cover · bitmask dp · approximation

A fulfilment order has L lines, L up to 25. After filtering by capability, service time and stock, N candidate nodes remain; N is usually under 14 but can reach 200. Each node covers a known subset of the lines in full. Return a minimum-size set of nodes covering every line, tie-broken by total distance. Give an exact algorithm for the small case with its exact cost in operations and bytes, say precisely why enumerating node subsets stops working, and give what you ship when N is 200.

Approach

  1. Name it: this is minimum-cardinality set cover, which is NP-hard, so the useful move is choosing which parameter you are exponential in rather than hunting for a polynomial exact algorithm. Enumerating subsets of nodes is 2^N and correct — at N = 14 that is 16,384 subsets and finishes instantly, which is exactly why it survives review and then fails in production at N = 200, where 2^200 is about 1.6e60.
  2. Be exponential in L instead, because L is the parameter you bound. Index the DP by the set of lines still uncovered: dp[rem] is the cheapest way to cover the lines in rem, with dp[0] = (0, 0) and the value a (node_count, total_distance) pair compared lexicographically. Let b be the lowest set bit of rem; any cover must contain some node that stocks line b, so the transition ranges only over those nodes: dp[rem] = min over v with b in cover(v) of dp[rem & ~cover(v)] + (1, dist(v)). Worst case O(2^L * N) — at L = 25 and N = 14 that is 33,554,432 states times 14, about 4.7e8 transitions — and the lowest-bit restriction cuts the real branching factor to the nodes stocking one specific line.
  3. Quote the memory for what you actually store, not for a count array alone. Over 2^25 masks you need three dense arrays: uint8 node count (33.5 MB), uint32 total distance (134.2 MB, integer metres so the tie-break compares exactly and the array stays fixed-width), and uint8 chosen node (33.5 MB, since N <= 200 fits a byte) — about 201 MB, roughly six times the 33.5 MB that a bare count array would suggest. The same layout at L = 20 is 6.3 MB, which is why this is comfortable up to about 20 lines and needs a deliberate decision at 25.
  4. Reconstruction is free in this formulation and needs no parent-mask array: because every transition eliminates the lowest uncovered line, the predecessor of rem under the stored choice v is exactly rem & ~cover(v), so walk rem from the full mask down to 0 emitting choice[rem]. If the reachable state count is far below 2^L — coverage sets are usually highly correlated — a top-down memoized recursion over a hash map trades the 201 MB for roughly 40-60 bytes per visited state; measure the visited count first, because a hash map that ends up touching most masks is strictly worse than the dense arrays. Add a branch-and-bound cut of ceil(popcount(rem) / max_v popcount(cover(v) & rem)), which prunes long before the theoretical bound bites.
  5. When exact is out of reach, ship greedy with a stated guarantee: repeatedly take the node covering the most currently-uncovered lines, using bitset popcounts, O(N * ceil(L/64)) word operations per pick and at most min(N, L) picks. Greedy is within H(L) <= ln L + 1 of optimal (Chvatal), and no polynomial algorithm achieves (1 - o(1)) ln L unless P = NP (Dinur and Steurer, 2014) — so effort belongs in the pre-filter and the tie-break, not in a cleverer heuristic. At L = 25 the bound is H(25), about 3.82x worst case, while the realistic gap on correlated coverage sets is zero or one node.
  6. Close on the objective, because fewest nodes is not cheapest. Two shipments from distant nodes routinely cost more than three from near ones, and a split also costs a second box, a second carrier pickup and a worse customer experience. If cost is the real objective it is weighted set cover — greedy by cost divided by newly-covered lines, same logarithmic bound — and if a node can cover a line only partially, the line mask is no longer sufficient state, since you must track remaining quantity per line; that is the point at which you stop hand-rolling and either restrict splits or hand an MILP to a solver with a time budget.

Follow-up

  • The pre-filter is what keeps N small. What is in it, and what happens to your exact path the day someone loosens it?
  • One node can cover a line only partially. Show where the bitmask formulation breaks and what state replaces it.
  • You have 40 milliseconds inside a checkout call. Which of these runs there, and what runs asynchronously afterwards?

Attribute multi-stop freight cost without fanning it out

Medium · many-to-many joins · window functions · integer money

shipment_leg(leg_id, shipment_id, sequence_no, linehaul_cost_cents, accessorial_cost_cents, actual_arrive_at) joins to fulfilment_order_line through leg_order_line(leg_id, order_line_id, allocated_weight_g). One multi-stop truckload leg carries 40 order lines. The current report SUMs linehaul_cost_cents after joining all three tables and returns roughly forty times the real freight spend. Write the query that returns cost per order for one month, explain the fan-out in terms of the join's row count, and make the per-order amounts sum exactly to the leg totals in integer cents.

Approach

  1. Name the cardinality: joining shipment_leg to leg_order_line produces one row per (leg, line) pair, so the result has SUM over legs of lines-per-leg rows rather than one row per leg. SUM(linehaul_cost_cents) over that result counts each leg's cost once per line it carries, which is why 40 lines gives 40 times the spend. The bug is not the SUM, it is aggregating a parent measure across a child-expanded row set.
  2. Compute a share before aggregating anything. In one pass: leg.linehaul_cost_cents * b.allocated_weight_g / SUM(b.allocated_weight_g) OVER (PARTITION BY b.leg_id), which keeps the denominator local to the leg and needs no self-join.
  3. Do the division in integer cents and settle the remainder deliberately. Floor each share, then distribute the leftover cents one each by descending remainder, ordered by a stable tiebreak such as order_line_id. Floating-point shares leave a residue that makes the freight accrual miss by a cent per leg, which costs more to investigate than the freight.
  4. Aggregate to the order only after the share exists: GROUP BY order_id over the per-line allocated cents. Restrict the month on shipment_leg.actual_arrive_at, not on the order date, so a leg is attributed in the period it actually ran.
  5. Index for the shape of the query: leg_order_line needs (leg_id) for the window partition and (order_line_id) for the reverse lookup, and shipment_leg needs an index on actual_arrive_at to bound the month without a full scan.
  6. Apply the same treatment to accessorial_cost_cents separately. Accessorials such as a detention charge often belong to one stop rather than to the whole leg, so weight-sharing them across every line on the load misattributes cost even though the total ties out.

Worked solution (30 min)

  1. Build a fixture with one leg at 100,003 cents carrying three lines weighted 1000 g, 1000 g and 1 g.
  2. Write the naive join and observe the total reported as three times the leg cost.
  3. Rewrite using the SUM(...) OVER (PARTITION BY leg_id) share, floor to integer cents, and add a largest-remainder pass ordered by remainder then order_line_id.
  4. Aggregate to order level and compare the grand total against SELECT SUM(linehaul_cost_cents + accessorial_cost_cents) FROM shipment_leg for the same window.

Expected result: Per-order allocated cents whose total equals the sum of leg costs for the month exactly, with no leg double-counted and the 2-cent remainder on the fixture distributed deterministically rather than lost or duplicated.

Verify it yourself

  • The reconciliation total matches to the cent, not to within rounding.
  • COUNT(DISTINCT leg_id) in the result equals the leg count for the month, proving no leg was expanded.
  • Re-run with the line order shuffled and confirm the same order receives the same remainder cent.
  • Add a fourth line to the leg and confirm the leg total is unchanged while the shares redistribute.

Follow-up

  • A line is cancelled after the leg ran. Does its share of the freight disappear, move to the remaining lines, or stay as a cost with no revenue?
  • Weight is the wrong basis for a light bulky pallet. What would you use instead, and does the reconciliation check still hold?
  • The carrier invoice arrives three weeks later and differs from the rated cost. Where does the correction land, and does it re-run this allocation?

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.

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?

Shown an analysis result, how would you explain what it is or how it was loaded?

Medium

Approach

  1. Say what you would check first and why it is the highest-information step.
  2. Clarify what is being asked and what a complete answer contains.
  3. 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 can you suggest to get better results in a project?

Medium

Approach

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

Follow-up

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

Evolve an availability contract without breaking floor devices

Hard · versioning · backward compatibility · units

GET /availability currently returns {item_id, node_id, qty}, where qty is denominated in whatever the item master says today - eaches for some items, cases for others. Three clients consume it: a checkout service you deploy, a partner integration on a twelve-month release cycle, and handheld firmware on the warehouse floor you cannot force-upgrade. You must expose the unit explicitly, split qty into on_hand, available and reserved, and add a new inventory state. Deliver the field plan, the version negotiation mechanism, the deprecation path, and how you prove nobody still reads the old field.

Approach

  1. Start from the rule that survives contact with un-upgradable clients: never change the meaning or the denomination of a live field. qty keeps precisely its current semantics forever, and the new truth ships as new fields - available_qty with an explicit uom_code, plus the on_hand and reserved split - so the handheld keeps reading exactly what it always read.
  2. Be precise about which additions are safe. New fields are safe only if readers ignore unknown ones, so state tolerant-reader expectations in the contract. Adding a value to an existing enum is not safe: a client switching exhaustively over inventory state breaks on the new state. Either declare the enum open in the current version with a mandated fallback branch, or carry the new state in a parallel field that old clients never see. Choose one and write it down.
  3. Pin versions per client credential rather than per deploy - an account-level default, overridable per request by a version header - so nobody is upgraded by your release. Name the cost honestly: you now serve N shapes from one code path, so cap N with a published support window and a rule that a frozen version gets no changes, not even helpful ones.
  4. Prove the dependency instead of assuming it. Count reads of the deprecated field per client credential, then send Sunset (RFC 8594) with a date and a deprecation link on a horizon longer than the slowest client's release cycle - a twelve-month partner cycle sets the floor, and the handheld fleet may not clear it at all, which is a planning input rather than a surprise.
  5. Guard it in CI with consumer-driven contract tests that fail on any field removal, type change or enum widening in a frozen version. A review checklist does not survive a rushed release; the test does.
  6. Close the loop on the unit trap explicitly: when a supplier changes a case from twelve to ten, the bare qty must not silently re-denominate. Carry the conversion factor's version alongside the number, and state that values already served keep the factor in force when they were computed.

Worked solution (40 min)

  1. Write the current and target payloads side by side and mark every field as unchanged, added or frozen - with nothing in a fourth category.
  2. For the new inventory state, write what each of the three clients does when it encounters an unrecognised value today, and pick open-enum or parallel-field accordingly.
  3. Specify the negotiation: the credential-level default, the override header, the error for an unknown or retired version, and the support window.
  4. Design the usage telemetry that tells you which credential still reads qty, and state the threshold at which you would send Sunset.
  5. Write the CI contract test that fails on a removal, a type change or an enum widening in a frozen version, and run it against the proposed change.

Expected result: An additive plan where qty is frozen rather than redefined, the unit travels on every new quantity field, enum evolution is settled explicitly, versions are pinned per credential, and the deprecation date is derived from measured usage plus the slowest client's release cycle.

Verify it yourself

  • A replay of captured handheld requests against the new build produces byte-compatible values in every field that firmware reads.
  • A client pinned to the old version never sees the new state value in any field it parses.
  • The contract test fails when someone changes qty's type or adds an enum value to the frozen version.

Follow-up

  • A client sends a version header you retired last quarter. What do you return, and what will their code do with it?
  • You need one breaking change on one endpoint. How do you ship it without versioning the whole API?
  • The Sunset date arrives and the handheld fleet still reads the deprecated field. What are your options, in order of cost?

Wave release screen slows linearly with the line count

Easy · n+1 · batching · latency

A wave-release screen lists open fulfilment_order_line rows for one node and, per line, its held allocation rows and the on_hand inventory_position quantity for that (item_id, node_id, lot_id, state). p95 is 40 ms for a 25-line wave and 3.4 s for an 800-line wave. The database reports 1,601 statements for the 800-line request, each under 1 ms, and no individual query is slow. Diagnose the cause, give the fix, and state the statement count and p95 you expect afterwards.

Approach

  1. Read the shape out of the numbers before forming a theory. 1,601 statements for 800 rows is 1 + 2N: one driver query plus two per line. Every statement being sub-millisecond rules out a bad plan, so the time is round trips, not work.
  2. Check that the arithmetic accounts for the whole regression. 1,600 round trips at roughly 2 ms each is about 3.2 s against a 40 ms baseline, which covers the observed 3.4 s. If it had only covered half, there would be a second defect and the N+1 fix alone would disappoint.
  3. Identify the two per-row statements by their normalised text: a single-row allocation lookup keyed by order_line_id and a single-row inventory_position lookup keyed by the four-column primary key. Confirm they are lazy relationship loads by removing those two fields from the response and watching the count fall to 1.
  4. Replace them with two batch statements. Collect the order_line_ids and item_ids from the driver query, then SELECT ... FROM allocation WHERE order_line_id = ANY($1) AND status IN ('held','committed') and SELECT ... FROM inventory_position WHERE node_id = $1 AND item_id = ANY($2) AND state = 'on_hand', and join them into maps in application memory. Three statements per request, independent of wave size.
  5. Verify the access paths rather than assuming them. The partial unique index on allocation (order_line_id, node_id, lot_id) WHERE status IN ('held','committed') leads with order_line_id, so it serves the first batch. inventory_position's primary key is (item_id, node_id, lot_id, state), so the second batch scans on the item_id, node_id prefix and filters on state; lot_id sits between them and is unconstrained, which is fine for a few hundred keys but is the reason this is a prefix scan rather than a point lookup.

Follow-up

  • The screen adds a per-line column showing the last movement's reason_code. How do you fetch that for 800 lines without reintroducing the fan-out?
  • A wave can now reach 12,000 lines. At what point does ANY($1) stop being the right tool, and what replaces it?
  • Nothing here was a slow query. What monitor would have caught this before an operator did?

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.

Describe your experience with specific CAD/CAE tools listed on your resume.

Medium · behavioural and engineering judgement

Approach

  1. State the situation in two sentences and spend the rest on the reasoning.
  2. Give the blast radius: what could have broken, and what you measured.
  3. Pick a story where you made the decision, not one where you watched it.

Follow-up

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

Why do you want to work with Multimatic Manufacturing?

Medium · behavioural and engineering judgement

Approach

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

Follow-up

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

What are your strengths and weaknesses?

Medium · behavioural and engineering judgement

Approach

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

Follow-up

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

Tell me about a situation where you had a conflict with a co-worker and how you handled it.

Medium · behavioural and engineering judgement

Approach

  1. Close with what you would do differently, concretely.
  2. Give the blast radius: what could have broken, and what you measured.
  3. Pick a story where you made the decision, not one where you watched it.

Follow-up

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