cswork / libraryOpen practice workspace

Interview guides · Molson Coors Beverage Company

Molson Coors Beverage Company Software Engineer Interview Questions & Guide 2026

Timed coding practice for Molson Coors Beverage Company software engineer candidates: full attempts under a clock, then scored against a rubric.

The 60-second brief

A Software Engineer at Molson Coors Beverage Company plays a pivotal role in bridging the gap between cutting-edge technology and world-class beverage production. You are not just writing code; you are building the digital backbone that supports complex manufacturing environments, supply chain logistics, and internal operations. Your work directly impacts how millions of consumers experience the company’s iconic portfolio of brands.

Seniority moves the scope further than the words in the title do. An earlier-career loop mostly checks that you implement something correctly and can reason about its cost, while a senior loop checks that you can pick between two defensible designs and say what you gave up.

Focus areas

  • Keep a site operating during a network partition
  • Make every external side effect safe to retry
  • Reserve inventory without overselling under concurrent allocation

Overview

Success in this role requires a blend of technical precision and operational awareness. You will often work within production facilities or corporate environments, troubleshooting systems like PLCs, SCADA, and DCS to ensure peak efficiency. Whether you are optimizing conveyor speeds, managing data flows for fillers, or automating safety protocols, your contributions are essential to maintaining the high standards of a global leader in the beverage industry.

This is a challenging, high-impact environment where you will frequently face complex, real-world problems. You must be comfortable working in cross-functional teams, collaborating with engineers, plant managers, and operations staff. If you enjoy applying technical expertise to solve tangible, physical-world challenges, this role offers a unique opportunity to influence the operational excellence of a historic company.

The Molson Coors Beverage Company interview process

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

Phone Screen

The title covers product work, platform work, infrastructure, mobile and frontend, and those are different jobs with different loops behind them. A screening call is the cheapest place to find out which one the seat is, and asking reads as experienced rather than fussy. The questions that separate them: what the team is on call for, what the last three projects were, and whether any round happens inside an existing repository instead of a blank file. Then say which of that you have done and which you have not. Claiming the whole posting is the fastest way to be found out one round later.

What to demonstrate

  • Whether you can locate your experience inside one flavour of the role honestly instead of claiming the entire requirements list
  • Whether you name what you have not done, which an experienced screener reads as a level signal and can plan the loop around
  • Whether what you want next matches what the seat is: someone who wants greenfield work landing on a team that mostly operates an existing system is a hire that leaves within the year

How to prepare

  • Mark every line of the posting as done, adjacent or new, and write one sentence for each adjacent line naming the closest thing you actually built
  • Split your last two years into rough percentages across feature work, operating and debugging live systems, and design or review, so a question about scope gets numbers rather than adjectives
  • Bring three questions that discriminate between seats: what the team is paged for, how much of the work is changing existing code versus standing up something new, and what shipped in the last quarter

Panel Interviews

Coding rounds mostly set a floor. They decide whether you clear the bar, not where you land on the ladder. Level tends to come out of the design discussion and the ownership stories, so the question worth auditing beforehand is whether the scope you describe matches the scope of the job. Work that stops at your own service, or a story whose hard part was writing the code rather than getting several people to agree on an interface, reads a level below where you think you are interviewing, and that gap is usually resolved downwards.

What to demonstrate

  • Whether the largest thing you describe owning ran end to end — the decision, the migration path, the rollout, and what you did when it went wrong — or stopped at the change you merged
  • Whether design answers include what you would not build, what you would defer, and what you would measure before committing, rather than only what the boxes are
  • Whether a disagreement in a story was settled with something checkable — a benchmark, a prototype, a written proposal — instead of by seniority or by waiting it out
  • Whether you can say which calls you made alone and which you escalated, and why the line sat where it did

How to prepare

  • Write your largest piece of owned work as a timeline of decisions — who decided what, when, and what you did when the plan broke — then delete every sentence whose subject is "we" and see how much survives
  • Take one system you know well and drill the migration answer: how old and new paths run side by side under live traffic, how you compare their outputs, what the rollback is once writes are going to both, and which step you would not automate
  • Map each line of the ladder in the job posting to a specific thing you have done, find the line you cannot support, and prepare the closest evidence you have plus an honest account of the gap

Technical Assessment

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

Cultural Fit Evaluation

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

Final Panel Evaluation

A day like this is several different games in a row, and the expensive mistake is carrying the previous one into the next room. Coding rewards narrow precision and finishing inside a timer. Design rewards breadth, stated assumptions and naming what you are deliberately not building. Behavioural rewards specificity about people and decisions. Candidates who over-engineer a coding problem they were supposed to finish, or who start sketching class hierarchies before anyone has agreed what the system has to do, are usually still playing the last round. Between rooms, name out loud which game the next one is.

What to demonstrate

  • Whether the coding round ends with something that runs and has been traced against a degenerate input, rather than an extensible design that was never finished
  • Whether a design discussion opens by agreeing on traffic shape, read-to-write ratio and what is allowed to be stale, instead of proceeding from an architecture you arrived with
  • Whether a behavioural answer names a person, a disagreement and what you did about it, rather than describing the system the story happened inside
  • Whether the opening habits still appear late in the day: restating the problem, asking for constraints, saying the plan before typing

How to prepare

  • Book three mocks of different types back to back on one afternoon and ask each interviewer afterwards which round you answered in the wrong mode
  • Write a three-line opening script per round type — coding: restate, name the approach and its cost, then type; design: ask for scale, read-write mix and what must not break; behavioural: name the person, the stakes and the decision — and run it off a card so the switch is mechanical rather than remembered
  • Practise coding with a timer you do not extend, stopping when it stops, so the trained reflex is to finish a correct solution rather than to keep improving one

Where candidates lose points

Floating-point quantities and implicit unit-of-measure conversions

Binary floating point cannot represent most decimal fractions exactly, so a chain of pallet-to-case-to-each conversions accumulates a residue that a later rounding turns into a unit that was created or destroyed, breaking conservation with no concurrency involved at all. The bug is slow and non-local: it surfaces as a cycle-count variance weeks later, at a node nobody changed, and it is untraceable because the arithmetic looked correct at every individual step. The fix is to hold quantities as integers in the smallest transacting unit, carry the UoM explicitly on every row that carries a number, store conversion factors as integers that are versioned in time, and reject a conversion that does not divide exactly instead of rounding it. The same discipline applies to money on the freight side, where an accessorial in floating-point dollars produces invoice reconciliation differences of a cent that cost more to investigate than the freight.

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.

Sorting when the problem never required a total order

Match the algorithm to the guarantee actually needed: the top k comes from a size-k heap in O(n log k) time and O(k) space, distinctness needs a set rather than an ordering, and a small bounded integer key range admits a linear counting pass. A full O(n log n) sort is the right default only when you genuinely need everything in order.

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

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

  1. 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.
  2. 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.
  3. 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.
  4. Join to lot_shipment once 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 a lot_shipment row 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 on lot_id over the whole visited set is a single pass; per-node lookups multiply the round-trip problem by the number of visited lots.
  5. 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)

  1. 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.
  2. Implement level-batched BFS with an explicit frontier list, a visited hash set, and a per-level batch read.
  3. Add a cycle to the fixture and confirm the traversal terminates and flags it rather than hanging.
  4. Bulk-join lot_shipment once 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.
  5. 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

  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?

Parse partner quantity and date segments without floating point

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

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

Approach

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

Follow-up

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

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

Medium · window functions · event time · monotonic fold

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

Approach

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

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 the reconciliation query that finds ledger and position variance

Easy · aggregation · outer joins · reconciliation

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

Approach

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

Worked solution (25 min)

  1. Write the aggregate CTE with the watermark predicate, then the FULL OUTER JOIN and the IS DISTINCT FROM filter, projecting COALESCE(m.item_id, p.item_id) and both quantities.
  2. Seed a fixture with four cases: agreeing key, disagreeing key, movements with no position row, position row with no movements.
  3. Run it and confirm exactly three rows come back, with the agreeing key absent.
  4. Rewrite the same comparison as an incremental sum from reconciled_through_movement_id and confirm it reports the same three keys on a second run after new movements land.

Expected result: A result set of (item_id, node_id, lot_id, state, ledger_qty, stored_qty, ledger_qty - stored_qty) containing one row per disagreeing key and both orphan cases, and zero rows on a consistent fixture.

Verify it yourself

  • Swap the FULL OUTER JOIN for a LEFT JOIN and confirm the movements-only orphan vanishes from the output.
  • Replace IS DISTINCT FROM with <> and confirm the rows with a NULL on one side stop being reported.
  • Insert a movement while the query runs and confirm the result does not change, proving both sides came from one snapshot.
  • Compare the incremental result against a full re-sum of the same partition and require an exact match.

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?

Idempotent movement writes from handhelds that retry blindly

Easy · idempotency · append-only ledger · transactions

Handhelds post movements to the Availability Service at 2,000 per second across sites and retry on any timeout without knowing whether the first attempt committed. Each request carries item_id, node_id, state, a signed delta_qty with its uom_code, reason_code, a caller-supplied idempotency_key and a device occurred_at. Design the write: the transaction boundary across inventory_movement and inventory_position, what a duplicate returns, and what happens when the resulting position would go negative. Do not assume occurred_at precedes recorded_at.

Approach

  1. One local transaction. INSERT into inventory_movement with ON CONFLICT (idempotency_key) DO NOTHING RETURNING movement_id; if zero rows come back the write already happened, so read the existing row by that key and return its original outcome. The unique index is the idempotency mechanism, which means no cache, no request log and no in-memory set has to be correct for this to hold.
  2. Apply the delta to inventory_position in the same transaction: UPDATE ... SET qty = qty + ?, version = version + 1, last_movement_id = ? WHERE (item_id, node_id, lot_id, state) = ... AND version = ?, then test the affected-row count and re-read plus retry a bounded number of times on zero. Same transaction is exactly what stops the ledger and its derived aggregate from diverging under a crash.
  3. Decide the negative case explicitly. Let CHECK (qty >= 0) abort the transaction, return a typed error and raise an exception task. Clamping at zero hides the missing receipt, and the first visible symptom then becomes a picker standing at an empty location for a promise the system already made.
  4. Handle the in-flight duplicate, not just the settled one: a retry arriving before the first attempt commits blocks on the unique index and resolves to the conflict branch once the first commits, so the second caller's latency is bounded below by the first's. Set the device timeout above that, or the retry storm is self-inflicted.
  5. Keep occurred_at as reported and recorded_at as server time, store clock_offset_ms beside them, and never derive ordering from occurred_at alone. A device whose clock runs ahead produces occurred_at > recorded_at, which is a row to flag for the fold and the audit, not a request to reject.

Follow-up

  • A device buffers 4,000 movements offline and posts them in one burst. What changes: batch size, ordering, and whether the idempotency key is per row or per batch?
  • Write the reconciliation query that re-sums inventory_movement per key against inventory_position, and say what it must bound the sum by so a busy row does not report a false variance.
  • A supervisor says yesterday's delta was wrong by two cases. What exactly gets written?

Paginate an append-only movement ledger under continuous writes

Easy · pagination · keyset · append-only

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

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

  1. Write the page query and the index it requires, then state the cost per page in terms of index height and page size.
  2. 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.
  3. 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.
  4. Pick the watermark lag or the commit-ordered feed, and write the number or the mechanism down rather than leaving it as an assumption.
  5. 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.

Expected result: A keyset cursor over movement_id backed by an index on (node_id, movement_id), a stated lag or commit-ordered source that makes a skipped row impossible, an explicit at-least-once guarantee, and occurred_at excluded from the ordering key.

Verify it yourself

  • A full pass taken while inserts continue returns every committed row at least once and skips none.
  • A row inserted with occurred_at eighteen hours old still appears in a later page.
  • p95 page latency is flat in page number rather than growing with it.

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?

What is your knowledge regarding safety, safety devices, and LOTO protocols?

Medium · debugging and production issues

Approach

  1. Separate the trigger from the cause; the deploy is rarely the bug.
  2. Establish what changed and when, before forming any theory.
  3. Say what evidence would prove you wrong, then go and look for it.

Follow-up

  • How would you tell a cause from a coincidence here?
  • What would you add now so this is faster to diagnose next time?

Build simple Logix 5000 and Factory Talk applications.

Medium · debugging and production issues

Approach

  1. Pick a bisection that eliminates candidates whichever way it turns out.
  2. Check the instrumentation before believing the symptom.
  3. Say what evidence would prove you wrong, then go and look for it.

Follow-up

  • How would you tell a cause from a coincidence here?
  • What would you look at first, and what would it rule out?

How would you troubleshoot a problem in equipment that you have no idea how it works?

Medium · debugging and production issues

Approach

  1. Separate the trigger from the cause; the deploy is rarely the bug.
  2. Say what evidence would prove you wrong, then go and look for it.
  3. Establish what changed and when, before forming any theory.

Follow-up

  • How would you tell a cause from a coincidence here?
  • What would you look at first, and what would it rule out?

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.

Review and mentoring: what you let through

Counting review comments or mentees proves nothing. The useful version is a specific change you approved with a reservation you stated, or one you blocked and the delay that cost. Say which standard you were holding and why it was worth the friction. A mentoring story needs the thing the other person can now do without you.

Why do you want to work for Molson Coors Beverage Company?

Medium · behavioural and engineering judgement

Approach

  1. Pick a story where you made the decision, not one where you watched it.
  2. Give the blast radius: what could have broken, and what you measured.
  3. Name the disagreement and how you resolved it with evidence.

Follow-up

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

Describe a situation where you had to balance time and productivity.

Medium · behavioural and engineering judgement

Approach

  1. Pick a story where you made the decision, not one where you watched it.
  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 the last time you had a dispute with your boss.

Medium · behavioural and engineering judgement

Approach

  1. Give the blast radius: what could have broken, and what you measured.
  2. Close with what you would do differently, concretely.
  3. Name the disagreement and how you resolved it with evidence.

Follow-up

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