Start learning free

LESSON 127 OF 173 · BACKEND: SPEED

Fix the N+1 in Java

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:

String query = "SELECT order_items.quantity, products.name ";
query += "FROM order_items ";
query += "JOIN products ";
query += "ON products.id = order_items.product_id ";
query += "WHERE order_items.order_id = ? ";
List<Map<String, Object>> rows = runQuery(query, orderId);

Each += glues one more piece onto the end of the 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 HashMap from order id to its list of items 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 getCustomerOrders, 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 HashMap entries from each order id to its list of items.


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


Hint
An order with two items comes as two rows with the same row.get("id"). Only when that id isn't in entries yet, put a new empty ArrayList there and add the order to result with that same list as "items". Every row then adds its item to entries.get(row.get("id")).


STEP 1 OF 11
    Ctrl + Enter to check

    Speed N Plus One in Java

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