Q7 - Flash Sale Revenue (Dynamic Pricing Across Suppliers)
We believe this question, and close variations of it, have been reported by candidates in recent Amazon online assessments and phone screens. It is a favourite opener: short enough to state in thirty seconds, and it separates people cleanly.
The trap is that it sounds like it needs a simulation. It does not, and the candidates who see why finish in ten minutes with an answer that still holds up when the numbers get large.
Scenario: Amazon is running a flash sale on one high-demand product, stocked by several suppliers at once. Pricing is dynamic: a unit sells for whatever that supplier has left on the shelf at that moment, and every sale walks that supplier's price down by one.
The rules, exactly as they play out:
- A sale takes one unit from one supplier.
- The revenue from that sale is that supplier's current stock level.
- Immediately after, that supplier's stock drops by one, so the next unit from them is worth one less.
- A supplier at zero stock is out of the sale.
You choose which supplier to sell from each time. Fill orders units so total revenue is as high as possible.
flash_sale_revenue(supplier_stock, orders)
-> the largest total revenue achievable
supplier_stockis the list of stock levels, one entry per supplier.[3, 5]means supplier A has 3 units and supplier B has 5.ordersis how many units the sale has to move in total, across all suppliers combined.- Stock levels are whole numbers and may be
0, which just means that supplier has nothing to sell. - If
ordersis larger than the total stock, sell every unit there is and stop. If there is nothing to sell, the revenue is0.
Example 1: two suppliers, six orders
supplier_stock = [3, 5] orders = 6
A has 3 units B has 5 units
SALE PRICE WHY THAT PRICE STOCK AFTER
-------------------------------------------------------------
1 5 supplier B is the tallest pile at 5 [3, 4]
2 4 B again, now 4 high [3, 3]
3 3 tied at 3, take A [2, 3]
4 3 now B at 3 [2, 2]
5 2 tied at 2, take A [1, 2]
6 2 now B at 2 [1, 1]
-------------------------------------------------------------
19 total revenue
answer: 5 + 4 + 3 + 3 + 2 + 2 = 19
The ties in sales 3 and 5 do not matter. Taking A or B first changes which supplier ends up with which leftover, but the prices collected are the same, so the revenue is the same.
Example 2: three suppliers, five orders
supplier_stock = [2, 4, 4] orders = 5
A has 2 units B has 4 units C has 4 units
SALE PRICE WHY THAT PRICE STOCK AFTER
-------------------------------------------------------------
1 4 B and C are tied at 4, take B [2, 3, 4]
2 4 now C at 4 [2, 3, 3]
3 3 B and C tied at 3, take B [2, 2, 3]
4 3 now C at 3 [2, 2, 2]
5 2 everything is 2 now, take A [1, 2, 2]
-------------------------------------------------------------
16 total revenue
answer: 4 + 4 + 3 + 3 + 2 = 16
Example 3: more orders than stock
supplier_stock = [2, 3] orders = 99
There are only 5 units in the whole sale, so all 5 sell and the sale ends:
from A: 2 + 1 = 3
from B: 3 + 2 + 1 = 6
answer: 9
Solution
Step 1: A supplier is a pile, and the price is its height
Draw each supplier as a stack of units. Selling takes the unit off the top, and what you are paid is how tall the pile was when you took it.
supplier_stock = [3, 5]
A B
[5] <- taking this unit pays 5
[4]
[3] [3]
[2] [2]
[1] [1] <- and this one pays 1
So supplier A can pay you 3, then 2, then 1. Supplier B can pay 5, then 4, 3, 2, 1. Nothing you do to one pile changes the other.
Step 2: Every price is decided before the sale starts
This is the step that turns the question from a simulation into a one-liner. A supplier holding s units will pay out exactly s, s-1, ..., 1 if you drain them, and in that order, always, no matter what you do elsewhere. Your choices never change the price list; they only change how far down each list you go.
Write out every price in the whole sale, from supplier_stock = [3, 5]:
from A: 3 2 1
from B: 5 4 3 2 1
the whole board: 5 4 3 3 2 2 1 1
|______________|
the 6 biggest, which is what orders = 6 buys
5 + 4 + 3 + 3 + 2 + 2 = 19
So the answer is just the sum of the orders largest numbers on that board. The only reason greedy works is sitting right there: each supplier's prices come out in decreasing order anyway, so taking the biggest number left on the board never breaks the rule that you have to sell a pile from the top down.
Step 3: A max-heap takes the biggest number every time
You do not have to build the board. Keep the suppliers in a max-heap keyed by current stock. Pop the tallest, add its height to the revenue, and push it back one shorter unless it just hit zero.
orders = 6, heap starts as [5, 3]
pop 5 revenue 5 push 4 heap [4, 3]
pop 4 revenue 9 push 3 heap [3, 3]
pop 3 revenue 12 push 2 heap [3, 2]
pop 3 revenue 15 push 2 heap [2, 2]
pop 2 revenue 17 push 1 heap [2, 1]
pop 2 revenue 19 push 1 heap [1, 1]
revenue 19
⚠️ Say out loud that the total can overflow 32 bits. With a million suppliers holding a million units each, the revenue runs into the hundreds of trillions. Use long in Java, long long in C++, int64 in Go. Interviewers notice when a candidate reaches for a 64-bit accumulator without being asked, and they notice when they do not.
Solution 1: max-heap
import heapq
def flash_sale_revenue(supplier_stock, orders):
# heapq is a min-heap, so store negatives to pop the tallest pile
piles = [-units for units in supplier_stock if units > 0]
heapq.heapify(piles)
revenue = 0
while orders > 0 and piles:
price = -heapq.heappop(piles) # the tallest pile sets this sale's price
revenue += price
orders -= 1
if price - 1 > 0:
heapq.heappush(piles, -(price - 1))
return revenue
Complexity Analysis
With n suppliers and k orders, building the heap is O(n) and every sale costs one pop and one push: O(n + k log n) time, O(n) space.
That is the answer most candidates give, and on a normal input it is fine. The follow-up is where the round is won.
✅ The heap on its own is a pass. Ship it, say the complexity out loud, and you have answered the question that was asked. The section below is the follow-up that separates a strong hire from a hire, and candidates report it arriving as a spoken "what if orders is a billion?" rather than as more code you are expected to type. We work it through in full so that it cannot catch you cold.
When the Order Count Runs Into the Millions
k log n stops being acceptable the moment orders is a billion, and online assessments like this one routinely set it there. One sale at a time is hopeless; you need to sell in blocks.
Step 4: Count how many units cost at least a given price
Go back to the board of prices from Step 2. A supplier with s units offers exactly one unit at every price from 1 to s, so it contributes a unit priced p or higher whenever s >= p, and it contributes s - p + 1 of them.
supplier_stock = [3, 5]
units priced 3 or more? A: 3-3+1 = 1 B: 5-3+1 = 3 total 4
units priced 2 or more? A: 3-2+1 = 2 B: 5-2+1 = 4 total 6
units priced 1 or more? A: 3 B: 5 total 8
That count only ever goes down as the price goes up, which is exactly the shape binary search needs.
Step 5: Binary search for the cut-off price
Find the highest price whose count still covers the whole order. Call it the cut-off. Every unit priced above the cut-off definitely sells, and the order is finished off with units sitting exactly at the cut-off.
orders = 6, searching prices 1 through 5:
price 3 -> only 4 units at or above it not enough, look lower
price 2 -> 6 units at or above it enough, remember 2 and look higher
price 1 -> 8 units enough, but 2 is higher and also works
cut-off = 2
Step 6: Sum each supplier's block in one arithmetic series
Above the cut-off, every supplier sells a solid run of prices with no gaps, from its stock level down to cut-off + 1. The sum of a run is the usual (first + last) * count / 2.
cut-off = 2, so take everything priced 3 or more:
A (3 units): just the 3 -> 3 1 unit
B (5 units): 5 + 4 + 3 -> 12 3 units
---- ----
15 4 units sold
6 - 4 = 2 orders still to fill, all at the cut-off price of 2: 2 * 2 = 4
total revenue 15 + 4 = 19 the same answer, no sale simulated
The top-up at the end is the part people forget. The cut-off price usually has more units sitting at it than you still need, so you take only as many as the order requires, at exactly that price.
The whole second solution in one picture, on Example 1:
The cut-off price across both supplier piles
Solution 2: binary search on the cut-off price
def units_priced_at_least(supplier_stock, price):
"""How many units in the whole sale would go for `price` or more."""
return sum(units - price + 1 for units in supplier_stock if units >= price)
def flash_sale_revenue(supplier_stock, orders):
orders = min(orders, sum(supplier_stock))
if orders == 0:
return 0
low, high, cutoff = 1, max(supplier_stock), 1
while low <= high: # highest price that still covers the order
mid = (low + high) // 2
if units_priced_at_least(supplier_stock, mid) >= orders:
cutoff, low = mid, mid + 1
else:
high = mid - 1
revenue = 0
sold = 0
for units in supplier_stock: # whole blocks priced above the cut-off
if units > cutoff:
count = units - cutoff
revenue += (units + cutoff + 1) * count // 2
sold += count
return revenue + (orders - sold) * cutoff # top up at the cut-off price
Complexity Analysis
Let n be the number of suppliers and M the largest stock level.
- Binary search runs
log Mrounds, each scanning all suppliers: O(n log M). - The final pass over the suppliers is O(n).
- Space is O(1) beyond the input.
Notice what dropped out: orders no longer appears anywhere in the running time. A billion orders costs exactly the same as six. That is the difference between a solution that passes and one that times out on the last three test cases.
Final Notes
We believe this flash-sale pricing question, and close variations of it, have appeared frequently in recent Amazon online assessments and early-round screens. It tests whether you can stop simulating a process and describe it instead.
-
The price list is fixed before you start. A supplier holding
sunits will pays, s-1, ..., 1in that order whatever you do elsewhere, so the question is only ever "whichordersof these numbers are the biggest". Everything else follows from that one sentence, and saying it out loud early is the strongest signal in the round. -
That sentence is also the proof that greedy is safe. You are not asked to trust a hunch. Because each supplier's prices already come out in decreasing order, taking the largest number left on the board can never violate the sell-from-the-top rule, so the greedy choice and the optimal choice are the same thing.
-
The heap is the honest first answer, so give it. It is correct, it is quick to write, and it shows you can reach for the right structure. Do not skip straight to the clever version and leave the interviewer unsure whether you could have coded the simple one.
-
Then ask what
orderscan be. That single clarifying question is what opens the second half of the problem. If the answer is "up to a billion", the heap is dead on arrival and the interviewer is waiting to see whether you notice. -
Binary search on the price, not on the answer. The quantity that behaves monotonically here is "how many units cost at least
p", which shrinks aspgrows. Finding the thing that moves in one direction is most of the work in any binary-search problem. -
The top-up at the cut-off is the bug everyone ships. There are usually more units sitting at the cut-off price than you still need, so you take only the shortfall, at exactly that price. Getting the blocks right and then over-counting the last price is the single most common way this solution fails its own examples.
-
Reach for a 64-bit accumulator before anyone asks. Large stock across many suppliers pushes the revenue past two billion easily, and a silent 32-bit overflow produces a confident, wrong number.
Work Example 1 by hand both ways, once with the heap and once with the cut-off, until the two land on 19 for the same reasons. The day this shows up with a billion orders attached, you will already know which half of the problem you are in.