Marketplace Data Store: Order Matching and End-of-Day Reconciliation
Company: Jane Street
Role: Machine Learning Engineer
Category: Coding & Algorithms
Difficulty: easy
Interview Round: Technical Screen
# Marketplace Data Store: Order Matching and End-of-Day Reconciliation
You are building the core of a marketplace where sellers list individual units of items for sale and buyers submit buy orders.
Each **listing** represents one unit for sale and has the fields:
- `item_name` (string) — the name of the item,
- `seller` (string) — the seller's name,
- `price` (integer) — the asking price for this unit,
- `buyer` (optional string) — `null` while the unit is unsold; set to the buyer's name once the unit is sold.
All listings live in a `DataStore` that supports two operations:
- `update(listing)` — insert a new listing or modify an existing one (for example, setting `buyer` when a unit is sold),
- `getAll()` — return all current listings.
Implement the `DataStore` and the two tasks below.
## Part 1 — Fill buy orders
You are given the current contents of the `DataStore` and a collection of buy orders, at most one per item name: a map `item_name -> (buyer_name, limit_price)`, meaning the named buyer wants to purchase exactly one unit of `item_name` and is willing to pay at most `limit_price`.
Process each buy order as follows:
- Consider the unsold listings (`buyer == null`) whose `item_name` matches the order.
- The order **fills** against the lowest-priced such listing whose `price <= limit_price`. If several qualifying listings tie at that lowest price, fill the one that was inserted into the `DataStore` earliest.
- The **final transaction price** is that listing's `price` (not the buyer's limit price). Mark the listing as sold by setting its `buyer` to `buyer_name` via `update`.
- If no unsold listing qualifies, the order does not fill and the `DataStore` is unchanged for that order.
Return a map `item_name -> final_price` containing one entry for every order that filled. Orders that did not fill do not appear in the result.
## Part 2 — End-of-day reconciliation
At the end of the trading day you receive the full day's transaction log. Each log record has:
- `buyer_name` (string),
- `item_name` (string),
- `price` (integer) — for `OK` and `PAYMENT_FAILED` records, the price the buyer transacted at; for `OUT_OF_STOCK` records, the maximum price the buyer was willing to pay,
- `timestamp` (integer) — all timestamps are distinct,
- `status` — one of:
- `OK`: the buyer bought a unit of `item_name` at `price` and the payment settled,
- `PAYMENT_FAILED`: the buyer bought a unit of `item_name` at `price`, but the payment later bounced (e.g., a credit-card problem), so the purchase is voided,
- `OUT_OF_STOCK`: the buyer wanted one unit of `item_name` for at most `price`, but no unit was available at that moment, so nothing was bought.
Reconcile the day by scanning the records in **increasing timestamp order** while maintaining a pool of *re-opened* units (units returned to the market during this scan). Apply these rules:
1. **`PAYMENT_FAILED`** at price `p`: the unit the buyer had bought re-opens for sale — add the unit `(item_name, p)` to the pool. The buyer was never successfully charged, so they receive no billing adjustment.
2. **`OK`** at price `p`: if the pool contains at least one re-opened unit of the same `item_name` with price **strictly less** than `p`, the buyer should have gotten that cheaper unit instead. Reassign the buyer to the cheapest such pool unit (earliest-added wins ties), whose price is `q`: record a **refund** of `p - q` for this buyer, remove that unit from the pool, and add the buyer's original unit `(item_name, p)` to the pool (it re-opens for sale). If the pool has no unit of this item cheaper than `p`, nothing happens.
3. **`OUT_OF_STOCK`** with limit price `b`: if the pool contains at least one re-opened unit of the same `item_name` with price `q <= b`, the buyer's unfilled demand can now be satisfied. Assign the buyer the cheapest such pool unit (earliest-added wins ties): record a **charge** of `q` for this buyer and remove the unit from the pool. If no pool unit qualifies, nothing happens.
Note that a re-opened unit can only affect records that come **after** it in timestamp order: an `OUT_OF_STOCK` order whose timestamp precedes every relevant `PAYMENT_FAILED` record is never charged.
Return the list of billing adjustments as `(buyer_name, adjustment_type, amount)` triples, where `adjustment_type` is `"refund"` or `"charge"`, in the order the adjustments are produced by the scan. A buyer may appear more than once if several of their records trigger adjustments.
### Example — Scenario A
Transaction log (already shown in timestamp order):
| buyer | item | price | timestamp | status |
|-------|-------|------:|----------:|--------|
| Alice | itemA | 100 | 1000 | PAYMENT_FAILED |
| Bob | itemA | 110 | 1010 | OK |
| Cathy | itemA | 105 | 1020 | OK |
- Alice's payment failed, so the unit `(itemA, 100)` re-opens.
- Bob successfully paid 110, but a unit of `itemA` at 100 (< 110) is in the pool: Bob is reassigned to it and refunded `110 - 100 = 10`; his original unit `(itemA, 110)` re-opens.
- Cathy paid 105; the only pool unit for `itemA` costs 110, which is not cheaper than 105, so nothing happens.
Output: `[("Bob", "refund", 10)]`
### Example — Scenario B
| buyer | item | price | timestamp | status |
|-------|-------|------:|----------:|--------|
| Alice | itemA | 100 | 1000 | PAYMENT_FAILED |
| David | itemA | 110 | 1020 | OUT_OF_STOCK |
- Alice's payment failed, so the unit `(itemA, 100)` re-opens.
- David had wanted `itemA` for at most 110 but found no stock; the pool now holds `(itemA, 100)` with `100 <= 110`, so David gets that unit and is charged 100.
Output: `[("David", "charge", 100)]`
If instead David's `OUT_OF_STOCK` record had a timestamp **before** Alice's failure, no unit would be in the pool at his position in the scan, and David would not be charged.
### Constraints
- `1 <= number of listings, buy orders, log records <= 10^5`
- `1 <= price, limit_price <= 10^9`
- Timestamps are distinct integers; the log is not guaranteed to arrive sorted.
- Names (`item_name`, `seller`, `buyer_name`) are non-empty case-sensitive strings.
- Each buyer appears at most once per timestamp; a single buyer may have multiple records across the day.
- Target overall time complexity: `O((n + m) log (n + m))` where `n` is the number of listings/orders and `m` is the number of log records.
Quick Answer: This question evaluates understanding of stateful data structures, deterministic order-matching logic, and time-ordered reconciliation of transaction logs, focusing on correct handling of pricing, tie-breaking, and re-opened inventory.
Marketplace Order Matching — Fill Buy Orders
Part 1 of Jane Street's marketplace DataStore question. You are given the current marketplace listings as three parallel arrays, in insertion order:
- `item_names[i]` — the item this listing sells,
- `prices[i]` — its asking price (integer),
- `buyers[i]` — the buyer who already bought this unit, or `""` (empty string) if the unit is still unsold.
You are also given a set of buy orders (at most one per item) as three parallel arrays:
- `order_items[j]` — the item the order wants,
- `order_buyers[j]` — the buyer's name,
- `order_limits[j]` — the most this buyer is willing to pay.
For each order, fill it against the **lowest-priced unsold listing** of that item whose `price <= limit`. If several qualifying listings tie at that lowest price, fill the one inserted **earliest** (smallest index). The transaction price is the listing's own price (not the buyer's limit). Mark that listing as sold by setting its buyer. If no unsold listing qualifies, the order does not fill.
Return a map `item_name -> final_price` with exactly one entry for every order that filled. Orders that did not fill are omitted.
Constraints
- 1 <= number of listings, number of orders <= 10^5
- 1 <= price, limit_price <= 10^9
- At most one buy order per item_name.
- buyers[i] == "" marks an unsold unit; a non-empty string marks a sold unit.
- Names (item_name, buyer) are non-empty, case-sensitive strings.
- Ties at the lowest qualifying price are broken by earliest insertion index.
Examples
Input: (["itemA","itemA","itemB"], [100,90,50], ["","",""], ["itemA","itemB"], ["Buyer1","Buyer2"], [95,60])
Expected Output: {"itemA": 90, "itemB": 50}
Explanation: The itemA order (limit 95) fills the 90 unit because the 100 unit exceeds the limit; the itemB order fills its only unit at 50.
Input: (["itemA","itemA"], [50,50], ["",""], ["itemA"], ["B"], [100])
Expected Output: {"itemA": 50}
Explanation: Two itemA units tie at price 50; the earlier-inserted one (index 0) fills, at price 50.
Hints
- You only ever need the cheapest unsold unit of an item: if the cheapest exceeds the buyer's limit, so does every other unit of that item.
- Group unsold listings by item into a min-heap keyed by (price, insertion_index) so the top is the cheapest, earliest-inserted unit.
- Because there is at most one order per item and each order fills at most one unit, a single peek (and pop on success) per order is enough — no re-scanning.
Marketplace End-of-Day Reconciliation
Part 2 of Jane Street's marketplace DataStore question. At the end of the trading day you receive the full transaction log as five parallel arrays (not necessarily sorted):
- `buyers[k]`, `item_names[k]`, `prices[k]`, `timestamps[k]` (all distinct), and
- `statuses[k]` — one of `"OK"`, `"PAYMENT_FAILED"`, `"OUT_OF_STOCK"`.
For `OK`/`PAYMENT_FAILED` records, `price` is the price the buyer transacted at; for `OUT_OF_STOCK`, it is the maximum price the buyer was willing to pay.
Scan the records in **increasing timestamp order**, maintaining a pool of *re-opened* units, and apply:
1. **PAYMENT_FAILED** at price `p`: the buyer's unit re-opens — add unit `(item, p)` to the pool. No billing adjustment (the buyer was never charged).
2. **OK** at price `p`: if the pool holds a unit of the same item priced **strictly below** `p`, reassign the buyer to the cheapest such unit `q` (earliest-added breaks ties): record a **refund** of `p - q`, remove that unit, and re-open the buyer's original unit `(item, p)`. Otherwise nothing happens.
3. **OUT_OF_STOCK** with limit `b`: if the pool holds a unit of the same item priced `q <= b`, assign the buyer the cheapest such unit (earliest-added breaks ties): record a **charge** of `q` and remove the unit. Otherwise nothing happens.
A re-opened unit can only affect records that come **after** it in timestamp order. Return the billing adjustments as `[buyer, type, amount]` triples (`type` is `"refund"` or `"charge"`), in the order the scan produces them. A buyer may appear more than once.
Constraints
- 1 <= number of log records <= 10^5
- 1 <= price, limit_price <= 10^9
- Timestamps are distinct integers; the log is not guaranteed to arrive sorted.
- status is one of "OK", "PAYMENT_FAILED", "OUT_OF_STOCK".
- For OK/PAYMENT_FAILED, price is the transacted price; for OUT_OF_STOCK, price is the buyer's max limit.
- A single buyer may have multiple records across the day and may receive multiple adjustments.
Examples
Input: (["Alice","Bob","Cathy"], ["itemA","itemA","itemA"], [100,110,105], [1000,1010,1020], ["PAYMENT_FAILED","OK","OK"])
Expected Output: [["Bob","refund",10]]
Explanation: Scenario A. Alice's failure re-opens (itemA,100). Bob paid 110 but the 100 unit is cheaper, so Bob is refunded 10 and his 110 unit re-opens. Cathy paid 105 but the only pool unit now costs 110 (not cheaper), so nothing happens.
Input: (["Alice","David"], ["itemA","itemA"], [100,110], [1000,1020], ["PAYMENT_FAILED","OUT_OF_STOCK"])
Expected Output: [["David","charge",100]]
Explanation: Scenario B. Alice's failure re-opens (itemA,100). David wanted itemA for <= 110 and the 100 unit now qualifies, so David is charged 100.
Hints
- The log arrives unsorted — sort by timestamp first, because a re-opened unit can only affect strictly-later records.
- Keep a separate min-heap per item, keyed by (price, add_sequence), so the top is the cheapest re-opened unit and ties go to the earliest-added.
- For OK at price p, only the pool's minimum matters: if the cheapest unit is strictly < p it triggers a refund (and the buyer's own unit then re-opens); otherwise nothing. For OUT_OF_STOCK the cheapest unit triggers a charge iff its price <= the buyer's limit.