Start learning free

LESSON 127 OF 188 · BACKEND: SPEED

Fix the N+1 in Python

Practice

Measure first: the old GET /customers/1/orders ran one query for the customer's orders, then one more per order for that order's items. Customer 1 has 2 orders, so that was 3 queries; a customer with 50 orders would cost 51. This is the N+1 problem, and it hides in any loop that runs a query.

A JOIN brings rows of two tables together in one query. For example, a route showing one order's items could get each item's quantity with its product's name in a single query:

query = (
    "SELECT order_items.quantity, products.name "
    "FROM order_items "
    "JOIN products "
    "ON products.id = order_items.product_id "
    "WHERE order_items.order_id = ? "
)
rows = run_query(query, (order_id,)).fetchall()

Python glues string pieces written one after another into one string, so each piece ends with a space. Each row of the orders JOIN is one item, with its order's columns next to it, so an order with two items comes as two rows. A dictionary from order id to its entry groups them back into orders.

Your code must give the same answers as before, with one query per request:

GET /customers/1/orders  →  200  [{"id": 1, "items": [{"product_id": 1, "quantity": 2}, {"product_id": 4, "quantity": 5}], "ordered": "2026-03-02", "status": "paid"}, {"id": 3, "items": [{"product_id": 2, "quantity": 3}, {"product_id": 4, "quantity": 10}], "ordered": "2026-04-05", "status": "paid"}]
GET /debug/queries       →  200  {"queries": 1}
GET /customers/3/orders  →  200  [{"id": 4, "items": [{"product_id": 5, "quantity": 1}, {"product_id": 1, "quantity": 1}], "ordered": "2026-04-18", "status": "shipped"}, {"id": 7, "items": [{"product_id": 1, "quantity": 4}, {"product_id": 2, "quantity": 2}], "ordered": "2026-05-30", "status": "paid"}]
GET /debug/queries       →  200  {"queries": 2}
GET /customers/4/orders  →  200  [{"id": 5, "items": [{"product_id": 6, "quantity": 2}, {"product_id": 3, "quantity": 1}], "ordered": "2026-05-07", "status": "paid"}]
GET /customers/5/orders  →  200  []
GET /debug/queries       →  200  {"queries": 4}

Your task: in get_customer_orders, build one query that joins orders and order_items for this customer, sorted by order id. Run it once, then group its rows into orders with a dictionary entries.


Assignment
Follow the Coding Tutorial to replace the query-per-order loop with one JOIN and group its rows in Python.


Hint
An order with two items comes as two rows with the same row["id"]. Make a new entry only when that id isn't in entries yet, append that same entry to result, and add every row's item to entries[row["id"]]["items"].


STEP 1 OF 11
    Ctrl + Enter to check

    Speed N Plus One in Python

    Keep going with Python1,600+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free