Start learning free

LESSON 160 OF 173 · BACKEND: BACKEND WITH AI

Off by One in Java

Practice

A shop doesn't send all its products at once. It hands them out in pages: GET /products?page=2 asks for the second page, with 3 products per page unless the client sends another per_page. The database builds a page with LIMIT, how many rows to give, and OFFSET, how many rows to skip first.

An AI assistant wrote this version, and a customer noticed that the Mug never shows up:

@GetMapping("/products")
Object getProducts(@RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "3") int per_page) {
    int offset = (page - 1) * per_page + 1;
    List<Map<String, Object>> rows = db.queryForList("SELECT id, name FROM products ORDER BY id LIMIT ? OFFSET ?", per_page, offset);
    return Map.of("page", page, "products", rows);
}

An offset counts rows to skip, so it starts at 0, while pages are numbered from 1. With 10 comments per page, for example, page 1 skips 0 comments and page 3 skips 20. The AI added 1, so page 1 skips the Mug and every page starts one product late. That's an off-by-one error. When code counts, check the first case, the last case and the one just outside: here, page 1, the last page and page 0.

The shop has 8 products. Your rewrite must answer:

GET /products  →  200  {"page": 1, "products": [{"id": 1, "name": "Mug"}, {"id": 2, "name": "Notebook"}, {"id": 3, "name": "Lamp"}]}
GET /products?page=2  →  200  {"page": 2, "products": [{"id": 4, "name": "Pen"}, {"id": 5, "name": "Plant"}, {"id": 6, "name": "Teapot"}]}
GET /products?page=3  →  200  {"page": 3, "products": [{"id": 7, "name": "Stapler"}, {"id": 8, "name": "Gift card"}]}
GET /products?page=4  →  200  {"page": 4, "products": []}
GET /products?page=2&per_page=4  →  200  {"page": 2, "products": [{"id": 5, "name": "Plant"}, …, {"id": 8, "name": "Gift card"}]}
GET /products?per_page=1  →  200  {"page": 1, "products": [{"id": 1, "name": "Mug"}]}
GET /products?page=8&per_page=1  →  200  {"page": 8, "products": [{"id": 8, "name": "Gift card"}]}
GET /products?page=0  →  400  {"error": "page and per_page must be 1 or more"}
GET /products?per_page=0  →  400  {"error": "page and per_page must be 1 or more"}

Your task: rewrite getProducts. Its first line already reads page and per_page as whole numbers with @RequestParam (defaults 1 and 3). Answer 400 with the error above when either is less than 1, skip exactly the products of the earlier pages, and answer the page number with its products in id order.


Assignment
Follow the Coding Tutorial to rewrite getProducts with the right offset and a 400 for pages that can't exist.


Hint
Check page 1 by hand before you trust a formula: it must skip 0 rows, not 1. Keep ORDER BY id in the query, or pages could repeat or miss products.


STEP 1 OF 5
    Ctrl + Enter to check

    Ai Off By 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