The coding question
Given an order (a country plus a list of products and quantities) and a table of shipping costs per country and product, return the total shipping cost. Then support costs that change by quantity tier.
Ask a few questions first
| Ask | What we'll assume |
|---|---|
| What do the inputs look like? | The order is a dict with a country and a list of items, each a product and a quantity. The table maps country to product to a cost per unit, in cents. |
| What if the country or product isn't in the table? | Raise an error saying which one. Quietly charging 0 would undercharge. |
| How do tiers work? | Like tax brackets: the first 2 units cost 1000 each, every unit after that costs 900. Ask, because the other reading (all units at the rate of the tier you land in) gives a different answer. |
| How are tiers written? | A list of (up_to, cost per unit), in order, where up_to is a running total and the last one is None for 'and beyond'. |
| Can the same product show up twice in an order? | Yes. Price each line on its own. Merging lines first is a follow-up, since it changes the answer with tiers. |
A couple of examples
| Input | Output |
|---|---|
| Flat table: US mouse 550, laptop 1000. CA mouse 750, laptop 1100. Order: 20 mice and 5 laptops to US, then the same order to CA | 16000, then 20500 |
| Same table, but US laptop is [(2, 1000), (None, 900)]. Same order to US | 15700 |
| CA mouse is [(10, 750), (50, 600), (None, 500)]. 20 mice and 5 laptops to CA, then 60 mice to CA | 19000, then 36500 |
Start with brute force
For tiers, loop over every unit one at a time, find which tier that unit falls in, and add its cost. It's easy to get right, so it's a good way to check the faster version.
Cost: O(Q × T) per line for quantity Q and T tiers, which gets silly for an order of 100,000 units.
A better way
Part 1 is two hash map lookups per line: country, then product, times quantity. The real work is deciding where the pricing logic lives.
Put it all in one function, item_cost(rule, quantity), and have the order loop call it. Part 1's rule is a number. Part 2's rule is a list of tiers. The order code never changes, and a later part (fixed fees per tier, discounts) only touches item_cost. In a bigger codebase each rule kind would be its own class with a cost method, which is the same object-oriented design idea.
For tiers, walk them in order, keeping how many units you've priced so far. Each tier covers units from there up to its limit, or up to the quantity if that comes first. Stop once every unit's priced.
- Look up the country's rates. Error if it's missing.
- For each line, look up the product's rule. Error if it's missing.
- item_cost: if the rule is a number, multiply by quantity.
- Otherwise walk the tiers, adding (end - done) × unit cost for each one, until done reaches the quantity.
- If the tiers run out first, raise an error instead of undercharging.
The code
def item_cost(rule, quantity):
# Every pricing rule goes through here, so new kinds only change this.
if quantity < 0:
raise ValueError("quantity can't be negative")
# Part 1: a flat cost per unit, like 550.
if isinstance(rule, int):
return rule * quantity
# Part 2: tiers, like [(2, 1000), (None, 900)]. Units 1 to 2 cost
# 1000 each, and every unit after that costs 900.
total, done = 0, 0
for up_to, unit_cost in rule:
if done >= quantity:
break
end = quantity if up_to is None else min(quantity, up_to)
total += (end - done) * unit_cost
done = end
if done < quantity:
raise ValueError("tiers stop before " + str(quantity) + " units")
return total
def shipping_cost(order, table):
# table: country -> product -> rule. Costs are in cents.
country = order["country"]
if country not in table:
raise ValueError("no shipping costs for " + country)
rates = table[country]
total = 0
for item in order["items"]:
product = item["product"]
if product not in rates:
raise ValueError("no shipping cost for " + product + " to " + country)
total += item_cost(rates[product], item["quantity"])
return total| Cost | Why | |
|---|---|---|
| Time | O(L × T) | L lines in the order, each walking at most T tiers |
| Space | O(1) | Just running totals, on top of the inputs |
Test it
Walk through one of the examples out loud, then check the cases that tend to break things:
- Right on a tier boundary: 2 laptops to US cost 2000 and 3 cost 2900, so the third unit is the first at 900.
- An order that spans three tiers: 60 mice to CA is 10 × 750 + 40 × 600 + 10 × 500 = 36500.
- An empty order, or a quantity of 0, costs 0.
- A country or product that isn't in the table raises an error naming it.
- Tiers that stop at 3 units when someone orders 5 raise an error instead of charging for 3.
- The same product on two lines is priced as two separate lines.
Common mistakes
- Charging every unit at the tier the total lands in, without asking which way tiers work.
- Off-by-one at the boundary, so unit 2 gets the second tier's price.
- Putting tier logic inside the order loop, so the next part means rewriting it.
- Using floats for money. Keep cents as integers.
Follow-up questions
What if some tiers are a fixed fee instead of per unit?
Add a kind to each tier, like ('fixed', 2000) or ('per_unit', 900), and branch on it inside item_cost. Nothing else changes, which is why the pricing lives in one place.
What if the same product appears on several lines?
Add up quantities per product first, then price each product once. With tiers that gives a cheaper and fairer answer, so check which one they want.
What if the table is huge and orders come in all day?
Load the table once and keep it in memory. Validate it up front (tiers in order, last one open-ended) so a bad table fails at load, not on a customer's order.
How the time adds up
| Minutes | What you're doing |
|---|---|
| 0–5 | Ask about the format, missing data and how tiers work |
| 5–10 | Examples, and plan item_cost |
| 10–20 | Part 1: flat costs |
| 20–35 | Part 2: tiers |
| 35–45 | Test the examples, boundaries and errors |
| 45–60 | Follow-ups: fixed fees and merged lines |
Key concepts used here
- Hash maps and sliding windowsSwap nested loops for a single pass.
- Object-oriented designSplit a small program into classes that are easy to change.
We wrote this sample solution ourselves. Practice Loop is independent and isn't affiliated with or endorsed by Stripe.