As a Software Engineer at Millennium Print Group, you will operate at the intersection of advanced manufacturing and high-scale software systems. Your role is vital to ensuring that Millennium Print Group's production environments, supply chain logistics, and quality assurance workflows are not only functional but optimized for maximum efficiency. You will be responsible for developing the technical backbone that allows Millennium Print Group to maintain its competitive edge in the printing and packaging industry.
This position is inherently strategic. You will be tasked with solving complex problems that arise from scaling industrial systems, managing technical governance, and integrating software solutions into physical hardware environments. Whether you are focusing on systems integration, quality engineering, or program governance, your work directly influences the reliability of Millennium Print Group's products and the speed at which the company brings solutions to its clients.
Initial Screening
reportedThe 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 Interviews
reportedInput 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
Behavioral Interviews
reportedMany of these questions are about something that went wrong, and the grading sits mostly in the hours after you knew. Who found out first, whether that was you or an alert or a user, how long it took you to say it out loud, and whether the people who needed the news got it while they could still act on it. Engineers under-tell this part because it feels like confessing. The pattern it is looking for is the opposite: the quiet fix, an incident absorbed without telling anyone, after which nothing changed and the same failure is still available.
What to demonstrate
- How the problem was found, and whether that route was one you had built or one that happened to you, since a user reporting it first means your instrumentation did not cover that failure
- Whether time-to-detect and time-to-tell are separate numbers in your account and whether you know both, because a fast fix that nobody heard about until the retro is a different answer from a slow one that was announced immediately
- Whether the resolution left something durable behind, a check that fires or a default that changed, rather than depending on people remembering to be careful
- Whether you can say what the failure cost without either inflating it or waving it away
How to prepare
- Reconstruct one incident you were part of as a timeline with clock times: first bad request, first signal, first person who knew, first message outside the team, mitigation, permanent fix. The gaps between those entries are what gets asked about
- Look up the configuration of the signal that caught it, including its evaluation window and threshold. An alert defined on a five-minute aggregate cannot fire until the condition holds across that window, which puts a floor under time-to-detect that has nothing to do with how severe the failure was. Be able to say what that floor was and whether anyone had chosen it deliberately
- Prepare one story where you escalated early and the severity turned out to be smaller than you thought, including what it cost the people you pulled in. Without it, every answer you give about raising alarms is unfalsifiable
PracHub editorial advice for the preparation topics above.
Holding a database transaction open across a physical or third-party operation
It is natural to open a transaction, lock the position, call the rating or tendering API, and commit on the response, and it works perfectly until the partner's p99 goes from 200 milliseconds to thirty seconds. At that point every request holding a lock on a hot item-node pair queues behind it, the connection pool fills with transactions that are waiting on the network rather than on the database, and an unrelated service sharing the pool fails at the same moment. In this domain the effect is amplified because demand concentrates on a few hot rows during a promotion or a seasonal peak, exactly when partner latency is also degraded. The structural fix is to keep transactions short and local - commit the state change together with an outbox row, let a relay perform the external call, and reconcile asynchronously - accepting at-least-once delivery and making the effect idempotent rather than trying to stretch a transaction over something the database cannot roll back.
Retrying an irreversible external call without an owned idempotency key
Timeouts and connection resets are ambiguous by construction - the partner may have committed before the response was lost - so a generic retry policy around an HTTP client is, in this domain, a machine for buying two labels and dispatching two trucks. Relying on the partner's deduplication is not a substitute, because their window is usually short, their key is often derived from fields you may legitimately change on retry, and many older integrations have no such concept at all. The workable pattern is to generate the key yourself, persist it with an explicit unknown status before the call, and resolve ambiguity by querying the partner for that key rather than re-issuing; the sweeper that does this is the component that has to be correct, not the call site. Teams get this right for payments and then forget that a carrier tender, a warehouse work release and an EDI shipping notice have the same shape.
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.
Retrying a write that is not safe to repeat
A timeout tells you nothing about whether the server applied the write, so a blind retry of a create or a charge can duplicate it. Either make the operation idempotent, with a caller-supplied key the server deduplicates on or a conditional update, or do not retry it; and use exponential backoff with jitter so the retries of many clients do not synchronise into a second outage.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
Parse partner quantity and date segments without floating point
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
- 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. - Parse the decimal as an integer mantissa plus a scale.
4.500becomes(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. - Convert by integer arithmetic and demand exactness:
numerator = mantissa * factor; emitnumerator / 10^scaleonly whennumerator % 10^scale == 0, otherwise reject the line with a reason code. Worked:4.500 CSat 12 eaches per case gives 54000/1000 = 54, exact.0.125 PLat 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. - 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.
20260317carries 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.
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
+0100on some dates. Does that change your storage decision for a future appointment as well as a past event?
Find the hottest write keys under a fixed memory budget
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?
Split an order across the fewest nodes that can fill it
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
- 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.
- 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 inrem, withdp[0] = (0, 0)and the value a(node_count, total_distance)pair compared lexicographically. Letbbe the lowest set bit ofrem; any cover must contain some node that stocks lineb, 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. - Quote the memory for what you actually store, not for a count array alone. Over 2^25 masks you need three dense arrays:
uint8node count (33.5 MB),uint32total distance (134.2 MB, integer metres so the tie-break compares exactly and the array stays fixed-width), anduint8chosen 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. - Reconstruction is free in this formulation and needs no parent-mask array: because every transition eliminates the lowest uncovered line, the predecessor of
remunder the stored choicevis exactlyrem & ~cover(v), so walkremfrom the full mask down to 0 emittingchoice[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 ofceil(popcount(rem) / max_v popcount(cover(v) & rem)), which prunes long before the theoretical bound bites. - 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.
- 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.
Worked solution 40 min
- Write
cover(v)as an L-bit mask per node and confirm that the union of all masks equals the full mask, otherwise the order is unfillable and the answer is a backorder, not a cover. - Implement the DP over uncovered masks with the lowest-uncovered-line transition, the
(node_count, total_distance)value and thechoicearray, then measure resident memory and wall time at L = 20 and L = 25 against the quoted 6.3 MB and 201 MB. - Implement greedy separately and run both on 10,000 random instances with L = 12, N = 10, comparing sizes.
- Construct the textbook instance where greedy is strictly worse than optimal — nested sets of geometrically decreasing size plus two disjoint halves — and confirm your greedy reproduces the gap.
- Add the distance tie-break to both and confirm the exact path returns the cheapest minimum-size cover, not merely a minimum-size one.
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?
Add a required unit column and uniqueness to a live ledger
inventory_movement holds 4.2 billion rows on PostgreSQL 15 and takes about 2,000 inserts per second around the clock. Two changes are required with no downtime: add uom_code TEXT NOT NULL, whose truthful value must come from the conversion factors in force at each row's occurred_at rather than from today's item master; and make idempotency_key UNIQUE, although duplicates predating the retry fix already exist. Give the ordered steps, the lock each one takes, its expected duration and its rollback.
Approach
- ADD COLUMN with a non-volatile DEFAULT has been metadata-only since PostgreSQL 11, so no rewrite, but it still takes ACCESS EXCLUSIVE for an instant. That lock queues behind any long-running reader while blocking every statement behind it, so one 10-minute analytics scan turns a millisecond change into a 10-minute outage. Set lock_timeout to 2s and retry in a loop rather than waiting.
- Reach NOT NULL without a scan under an exclusive lock: ADD CONSTRAINT ... CHECK (uom_code IS NOT NULL) NOT VALID, which is instant, then VALIDATE CONSTRAINT, which takes only SHARE UPDATE EXCLUSIVE and runs concurrently with inserts, then SET NOT NULL, which PostgreSQL 12 and later proves from the validated check and so skips its own scan. Drop the redundant check afterwards.
- The truthful backfill is the long pole and must read the versioned conversion factor as of each row's occurred_at, not the current item master, or a supplier moving a case from 12 units to 10 retroactively reinterprets last quarter. Batch by primary-key range, a few thousand rows per transaction, with a sleep between batches and a resumable cursor in a control table. At 5,000 rows a second, 4.2 billion rows is roughly ten days of throttled work: that is a schedule, not a failure, and it must not block the deploy.
- The UNIQUE index cannot be built until the duplicates are gone. CREATE UNIQUE INDEX CONCURRENTLY aborts on the first violation and leaves an INVALID index behind that is ignored by queries but still carries update overhead on every insert, and it can only be removed with DROP INDEX CONCURRENTLY. So: find the duplicates, resolve each one as a compensating reversal rather than a DELETE, since every duplicate key is a double-applied movement and deleting it fixes the constraint while leaving the position wrong.
- Then CREATE UNIQUE INDEX CONCURRENTLY, which makes two passes over the table and waits out concurrent transactions, so budget hours here, and note it cannot run inside a transaction block: a migration framework that wraps every step in BEGIN will fail on this one. Promote it with ALTER TABLE ... ADD CONSTRAINT ... UNIQUE USING INDEX, which is a brief ACCESS EXCLUSIVE.
- Question whether the constraint should cover all history. At roughly 40 bytes per key plus overhead, a btree over 4.2 billion rows is in the hundreds of gigabytes, while a retry never arrives a year later. A partial unique index bounded by a fixed occurred_at literal, rotated forward on a schedule, gives the same protection for a fraction of the size, and the predicate must be a literal because now() is not immutable.
Worked solution 45 min
- Write the migration as numbered steps with the lock mode, expected duration and rollback beside each, including the lock_timeout retry wrapper on every ACCESS EXCLUSIVE step.
- On a copy at reduced scale, run ADD COLUMN while a long SELECT is open and observe writers queueing behind the lock request, then repeat with lock_timeout set and confirm the migration backs off instead.
- Run the batched backfill against a versioned conversion table, kill it mid-run and restart it, confirming the control-table cursor resumes without redoing or skipping a batch.
- Seed duplicate idempotency keys, attempt CREATE UNIQUE INDEX CONCURRENTLY, and confirm the INVALID index left behind, then drop it concurrently, resolve the duplicates as reversals and rebuild.
- Compare the full unique index size against the partial one bounded by a fixed occurred_at literal.
Follow-up
- Sequence the application deploy against these steps. When exactly may the code start writing uom_code, and when may it start requiring it?
- The backfill is on day six and the conversion-factor table is itself corrected. What happens to the rows already written?
- How do you roll back after SET NOT NULL is in place and half the traffic depends on the column?
Write the reconciliation query that finds ledger and position variance
inventory_movement(movement_id, item_id, node_id, lot_id, state, delta_qty) is the ledger; inventory_position(item_id, node_id, lot_id, state, qty, version, last_movement_id, reconciled_through_movement_id) is its running sum, written in the same transaction as each movement. Write one query that reports every key whose stored qty disagrees with the sum of its movements, including keys present in only one of the two tables, bounded by a watermark movement_id you pass in. State what the job does with each variance, and how you avoid re-summing four billion rows every night.
Approach
- Aggregate once and join once: a CTE doing SELECT item_id, node_id, lot_id, state, SUM(delta_qty) AS ledger_qty FROM inventory_movement WHERE movement_id <= :w GROUP BY 1,2,3,4, then FULL OUTER JOIN inventory_position USING those four columns. One pass plus one hash aggregate, O(n) in movements scanned; a correlated subquery per position row instead costs one index scan per key and is orders of magnitude slower at 40 million keys.
- Use FULL OUTER JOIN, not LEFT. A key with movements and no position row is the signature of a lost position update, and a position row with no movements is the signature of a write that bypassed the ledger. Both disappear under a LEFT JOIN from either side.
- Compare with IS DISTINCT FROM and COALESCE the join keys out of both sides, so a missing row reads as a variance rather than as NULL = NULL evaluating to unknown and being filtered away.
- Run both sides in one snapshot: either a single statement, or a REPEATABLE READ transaction. Reading positions in one transaction and movements in another manufactures variances for every key written in between.
- Make it incremental with reconciled_through_movement_id: sum only movements in (reconciled_through, :w] and add that delta to the previously verified total. The catch is that sequence values are handed out before commit, so a movement whose id is below :w can become visible after the scan and would then never be re-summed. Set :w to a max(movement_id) observed a few minutes earlier, beyond the longest write transaction, and full-scan one partition per night on a rotation as a backstop.
- A variance is reported, not repaired. Rewriting qty to match the ledger hides the double-applied retry that caused it; only a physical count may produce a cycle_count_adjust movement, and the location should be flagged unreliable to planning until it is counted.
Follow-up
- The job finds 14 variances, all on one node, all negative. What do you look at first, and what would make you suspect the reconciliation job itself rather than the data?
- How do you run this without the nightly scan competing with the ATP read path for buffer cache and I/O?
- What does the query return for a key whose only movements are a receipt and its exact reversal, and is that row worth reporting?
What methodologies do you follow to ensure your code is scalable and m…
What methodologies do you follow to ensure your code is scalable and maintainable?
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?
If a critical system component fails, what is your step-by-step diagno…
If a critical system component fails, what is your step-by-step diagnostic process?
Approach
- Clarify what is being asked and what a complete answer contains.
- Work from the requirement backwards to the design.
- 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?
Paginate an append-only movement ledger under continuous writes
A partner's nightly reconciliation job pulls every inventory_movement row for its nodes since its last run, while the table is being appended to throughout. The columns available are movement_id BIGINT (sequence-assigned), node_id, occurred_at (device-reported) and recorded_at (insert time). Specify the cursor format, the ordering key, the page-size policy and the delivery guarantee you promise across job restarts. State explicitly what happens to a row inserted today whose occurred_at is eighteen hours in the past, and how the client resumes after a crash mid-page.
Approach
- Use a keyset cursor, not OFFSET: WHERE node_id = ANY($1) AND movement_id > $2 ORDER BY movement_id LIMIT n, served by an index on (node_id, movement_id). Each page costs O(log n + k); OFFSET re-walks the skipped prefix at O(offset + k), so page 900 of a nightly pull is the page that times out.
- Order by insert order, never by occurred_at. A handheld that reconnects after a partition inserts movements stamped hours earlier; a cursor advanced past that instant will never return them. occurred_at is a filter and a display field, recorded_at and movement_id are the feed order - conflating the two is how a reconciliation silently misses a day of picks.
- Name the sequence-gap hazard and close it. The sequence hands out movement_id at INSERT, but transactions do not commit in id order, so a reader that takes max(id) from a page can step over a row that commits afterwards with a lower id. Either hold the cursor behind a watermark older than the longest writer transaction (bounded by statement_timeout plus idle_in_transaction_session_timeout) or read a commit-ordered source such as logical decoding or a transactional outbox, and say which you chose.
- Make the cursor opaque - encode the ordering key plus a schema tag - so you can change the key later without a coordinated client release. Return next_cursor and has_more; do not return a total count, which is a second full scan and is stale before it is serialised.
- State the guarantee honestly: at-least-once across restarts, because a client that crashes after processing a page and before persisting the cursor will re-request it. Delivery is idempotent on the partner side through movement_id, and exactly-once over a network is not on offer.
Worked solution 20 min
- Write the page query and the index it requires, then state the cost per page in terms of index height and page size.
- Trace a late insert: a device buffers at 09:00, reconnects at 15:00, the row lands with occurred_at 09:00 and a current movement_id. Confirm it is delivered under your ordering key.
- Trace two interleaved writers to show the sequence-gap skip concretely: txn A takes id 100 and commits late, txn B takes 101 and commits first, a reader pages past 101.
- Pick the watermark lag or the commit-ordered feed, and write the number or the mechanism down rather than leaving it as an assumption.
- Write the resume test: page the table while a writer inserts continuously, kill the client mid-page, resume from the persisted cursor, and assert no row is missing.
Follow-up
- The partner asks for 10,000-row pages and still times out. What do you change before raising the limit?
- They ask for a changes-since-timestamp parameter instead of a cursor. What breaks, and what do you offer instead?
- They need to re-read a three-day window to settle a dispute. How does that coexist with the cursor contract?
Duplicate bookings and second trailers after a partner slowdown
A partner's booking API ran at 45 s p99 for ninety minutes. Afterwards 214 shipment_leg rows carry two booking references, 41 stops received two trailers, and finance holds duplicate invoices. booking_idempotency_key is generated and persisted on the leg before the call and is sent to the partner, whose documented dedupe window is 60 seconds. The HTTP client retries timeouts three times, and a sweeper re-tenders any leg still in status 'planned' after ten minutes. Explain how duplicates happened despite the key, and give the corrected flow.
Approach
- Establish that the duplicates are timeouts rather than double-clicks. Join the gateway request log to shipment_leg for the 214 legs and check whether each duplicate pair carries the same idempotency key and how far apart the calls landed. Same key, calls minutes apart, first attempt ending in a client timeout is the ambiguous-outcome signature; different keys would mean the key is being regenerated and is a different bug.
- Name why the key did not help. The key was correct and persisted, but deduplication was delegated to the partner, whose window is 60 seconds. A retry at 45 s may land inside it; the third retry does not, and the ten-minute sweeper re-tender certainly does not, so the partner sees a new request and books a second truck. A short partner window plus a long retry horizon is an at-least-once system wearing an at-most-once label.
- Identify the missing state. The leg goes planned to tendered only on a 2xx, so a timeout leaves it exactly as if nothing was sent, which is false: the partner may have committed before the connection dropped. The state machine needs an explicit in-doubt status between requested and confirmed, and nothing may issue a new tender for a leg in that state.
- Replace re-issuing with asking. On any ambiguous outcome, move the leg to in-doubt and let a sweeper resolve it by querying the partner for that key or for bookings against our shipment reference, and only tender again when the query definitively returns nothing. Where the partner offers no query, the manifest or the status feed is the authority and resolution is slower, so the in-doubt state must be allowed to persist rather than being timed out into a retry.
- Move retry ownership out of the process. A retry decorator around an HTTP client disappears on deploy or crash, so the persisted state is the only thing that can drive recovery: a sweeper reading legs in requested or in-doubt, with bounded attempts, backoff and an alert on in-doubt depth. The call site should not retry an irreversible effect at all.
- Deal with the 214 that already exist, since code cannot undo them. Reconcile against the partner's manifest, cancel the duplicate where the cancellation window still allows it, and for legs already executed record the second cost as a compensating entry and dispute the invoice. State plainly that this is a business process with a cost, which is exactly why the guard belongs before the call.
Follow-up
- The partner offers no lookup by your key and no search by reference. What is your resolution path and how long may a leg stay in doubt?
- Which other effects in this system have the same shape and the same missing state?
- How do you test this without a degraded partner, and what does the fault injection have to simulate that a plain timeout does not?
Roughly ninety minutes on weeknights with one longer weekend block. The plan cuts scope rather than compressing everything, on the assumption that one thing finished per night beats four half-started.
Prepare, practise & reflect
One practical outcome each day. Spend longer where you need it.
0 / 7 done01Fix the scope and take a cold baseline
- Read the role description and write the three things the loop will almost certainly test, then write an explicit not-doing list and keep it visible all week.
- Take one twenty-five-minute coding problem and one fifteen-minute design prompt cold, and write the single sentence naming what blocked each, because those two sentences decide where the remaining evenings go.
- Set the week's rule: one thing finished every night, including the night you only have forty minutes.
Deliverable: A one-page scope with a not-doing list and two cold attempts, each carrying one sentence on what blocked it.
Practice prompt ↗Practice prompt ↗Worked solution ↗02One pattern, written three times from blank
- Choose the single pattern most likely to appear in your loop and write it three times from an empty file rather than editing the previous attempt.
- On the third pass, write the invariant as a comment before the loop body and the complexity before the first line of code.
- Stop at ninety minutes even if the third version is imperfect, and write the one thing you would fix given another hour.
Deliverable: Three independent implementations of the same pattern plus a note on what changed between them.
Practice prompt ↗Practice prompt ↗03One design, only to the depth you can defend
- Take one system shape and go only as far as requirements, interface and data model, refusing to draw a box you could not survive a follow-up about.
- Attach one number to each non-functional requirement, deriving it rather than asserting it, and write the assumption the number rests on.
- Write the one tradeoff you are choosing against and the observation that would make you reverse it.
Deliverable: One design at interface-and-schema depth with derived numbers and one written reversible tradeoff.
Practice prompt ↗Practice prompt ↗04Only the fundamentals you will have to defend
- Write, in under two hundred words each, the answers to the two questions that follow almost any implementation: why this structure and not the obvious alternative, and what happens to this code at a hundred times the input.
- Write what an index actually costs: faster lookups on the indexed columns against a write that now maintains a second structure, plus the cases where the planner declines to use it anyway, low selectivity, or a predicate wrapping the column in a function.
- Delete any answer you cannot deliver aloud in under a minute, since an answer that needs reading is not an answer you have.
Deliverable: Three written answers, each under two hundred words and each timed aloud.
Practice prompt ↗Practice prompt ↗Worked solution ↗05Your own work, timed
- Write a ninety-second and a four-minute version of your main project and time both aloud rather than reading them.
- Prepare the two follow-ups that always come: what you would do differently, and how you knew it worked.
- Put one number in the first sentence and be ready to say exactly where it came from and what it excludes.
Deliverable: Two timed narratives with one defensible number in the opening line.
Practice prompt ↗Practice prompt ↗06The one full rehearsal, in the weekend block
- Run a sixty-minute mock covering a coding round and a design round in one sitting with no break, because sustained attention is the thing evenings have not trained.
- Immediately afterwards, and before hearing any feedback, write the three moments you lost the thread.
- Spend the rest of the block only on those three moments, and on nothing you merely feel shaky about.
Deliverable: Mock notes naming three failure moments with a specific fix written under each.
Practice prompt ↗Practice prompt ↗07Taper
- Write the twenty-minute warm-up you will actually do on the morning: one problem you can already solve from a blank file, one design you can narrate, and nothing you have never seen.
- Re-read only your own notes from this week and open no new material.
- Write the logistics down: the editor or shared document you will be working in, whether execution and lookups are permitted, and the sentence you will use when you do not know something.
Deliverable: A one-page card holding the design structure, the project numbers, and the logistics.
Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
For anything that touched live traffic, be ready to say how you would have undone it: a flag, a staged rollout, dual writes with the old path still authoritative. Once the old column is dropped or the source rows are overwritten there is no reverse, so name what you kept a copy of and for how long.
Describe a time you had to adapt a software solution to meet a specifi…
Describe a time you had to adapt a software solution to meet a specific manufacturing or supply chain requirement.
Approach
- Close with what you would do differently, concretely.
- Give the blast radius: what could have broken, and what you measured.
- State the situation in two sentences and spend the rest on the reasoning.
Follow-up
- What did you decide not to do, and why?
- What would you do differently if you ran that again?
Describe a situation where you identified a process inefficiency and t…
Describe a situation where you identified a process inefficiency and took the initiative to fix it.
Approach
- State the situation in two sentences and spend the rest on the reasoning.
- Pick a story where you made the decision, not one where you watched it.
- Close with what you would do differently, concretely.
Follow-up
- How did you know your change caused the improvement?
- What did you decide not to do, and why?
How do you handle disagreements within your team regarding architectur…
How do you handle disagreements within your team regarding architectural or technical direction?
Approach
- Name the disagreement and how you resolved it with evidence.
- State the situation in two sentences and spend the rest on the reasoning.
- Close with what you would do differently, concretely.
Follow-up
- How did you know your change caused the improvement?
- What did you decide not to do, and why?
Tell us about a time you had to explain a complex technical issue to a…
Tell us about a time you had to explain a complex technical issue to a non-technical stakeholder.
Approach
- Give the blast radius: what could have broken, and what you measured.
- State the situation in two sentences and spend the rest on the reasoning.
- Name the disagreement and how you resolved it with evidence.
Follow-up
- What would you do differently if you ran that again?
- What did you decide not to do, and why?
- 01
Describe a time you had to adapt a software solution to meet a specific manufacturing or supply chain requirement.
- 02
Describe a situation where you identified a process inefficiency and took the initiative to fix it.
- 03
How do you handle disagreements within your team regarding architectural or technical direction?
- 04
Tell us about a time you had to explain a complex technical issue to a non-technical stakeholder.
Is this an official Millennium Print Group interview guide?
No. It is PracHub's own research and practice material for the Software Engineer role at Millennium Print Group. Rounds and questions reflect what candidates have reported, not a process Millennium Print Group has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗How much preparation time should I dedicate to the interview?
Plan to spend at least one week focusing on your past projects and preparing to speak to the "why" behind your technical decisions.
PracHub interview research ↗Is the work environment collaborative or independent?
It is highly collaborative. While you will have individual tasks, you will be working closely with other engineers and cross-functional partners daily.
PracHub interview research ↗What differentiates a top-tier candidate?
The most successful candidates are those who show a deep interest in how their code impacts the physical manufacturing process and the overall business objectives.
PracHub interview research ↗What is the timeline from the first screen to an offer?
The timeline varies based on current hiring needs; the process aims to respect your time while giving interviewers a complete picture of your skills.
PracHub interview research ↗Sources & methodology 3 sources ↗
No official company page is cited. Rounds and questions come from candidate reports and PracHub editorial material; each source shows the date it was read.
- 01PracHub interview research ↗
PracHub editorial research into this company and role, maintained with this guide. Candidate-reported, not an employer publication.
PracHub page · Accessed 2026-09-22 - 02PracHub Software Engineer practice ↗
Cross-company practice questions for this role.
PracHub page · Accessed 2026-09-22 - 03PracHub interview preparation framework ↗
The framework the preparation plan follows.
PracHub page · Accessed 2026-09-22