Coding sample solution

Coding: calculate shipping costs

How to talk through this coding question, from a first idea to working code, in an interview.

Coding interview · Sample solution in Python · Independent, not affiliated with Stripe

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

AskWhat 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

InputOutput
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 CA16000, then 20500
Same table, but US laptop is [(2, 1000), (None, 900)]. Same order to US15700
CA mouse is [(10, 750), (50, 600), (None, 500)]. 20 mice and 5 laptops to CA, then 60 mice to CA19000, 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.

  1. Look up the country's rates. Error if it's missing.
  2. For each line, look up the product's rule. Error if it's missing.
  3. item_cost: if the rule is a number, multiply by quantity.
  4. Otherwise walk the tiers, adding (end - done) × unit cost for each one, until done reaches the quantity.
  5. If the tiers run out first, raise an error instead of undercharging.

The code

solution.pypython
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
CostWhy
TimeO(L × T)L lines in the order, each walking at most T tiers
SpaceO(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

MinutesWhat you're doing
0–5Ask about the format, missing data and how tiers work
5–10Examples, and plan item_cost
10–20Part 1: flat costs
20–35Part 2: tiers
35–45Test the examples, boundaries and errors
45–60Follow-ups: fixed fees and merged lines

Key concepts used here

We wrote this sample solution ourselves. Practice Loop is independent and isn't affiliated with or endorsed by Stripe.