skip to content

When would you deliberately choose Slice over Page, and what performance problem does that solve?

level: middleimportance: should knowfreq 55%

answer

  1. COUNT runs every request
  2. count ~ data cost on joins/big tables
  3. Slice = halve the queries
  4. lose totals + arbitrary page jump
  5. OFFSET still slow either way

basics

~20 s

Choose Slice when the UI only needs 'is there more?' (infinite scroll, load-more) and never shows a total. It avoids the extra COUNT query that Page runs, which can be very expensive on large or joined tables.

solid answer

~40 s

Page runs a second SELECT COUNT query so it can report total elements and total pages. On big tables, or queries with complex WHERE clauses and joins, that count can be as costly as the data fetch itself and it runs on every page request. If your UI is infinite scroll or a 'Load more' button, you only need hasNext(), which Slice provides via a single query that fetches pageSize + 1 rows. Switching Page to Slice literally halves the query count and drops the most expensive part on wide tables. The trade-off: you lose getTotalElements()/getTotalPages(), so you can't render 'Page 3 of 47' or a results-count badge, and you can't jump to an arbitrary page. If the product genuinely needs a total, keep Page or compute an approximate/cached count separately.

go deeper

for a junior

Can say Slice avoids the count and suits infinite scroll.

for a middle

Should quantify: two queries -> one, and note count cost scales with table size/joins.

for a senior

Should mention approximate/cached counts and that OFFSET remains the deeper problem.

for a principal

Should frame it as a product-vs-cost trade-off and know when to invest in keyset scrolling instead.

## The cost you're avoiding A `Page<T>` result is backed by two SQL statements: 1. the **data query** — `... WHERE <filters> ORDER BY <sort> LIMIT pageSize OFFSET pageNumber*pageSize`, 2. the **count query** — `SELECT COUNT(...) ... WHERE <filters>` (no sort, no paging). The count is not free. Databases often cannot answer `COUNT(*)` from cheap metadata when there's a `WHERE` clause or joins — they must scan an index or the table. With multi-table joins, filtered predicates, or tens of millions of rows, the count can take **as long as or longer than** the data query. And it repeats on *every* pagination request, even though the total rarely changes between clicks. ## What Slice gives up and keeps `Slice<T>` keeps `getContent()`, `hasNext()`, `hasPrevious()`, `getNumber()`, `getSize()`. It drops `getTotalElements()` and `getTotalPages()`. Because it doesn't need a total, it runs **one** query with `LIMIT pageSize + 1`; the extra row (if present) proves there's a next slice and is then discarded. ## Decision guide - **Use Slice** when: - The interaction is infinite scroll, a feed, or a 'Load more' button. - The table is large / the query has heavy joins / the count is measurably slow. - Product doesn't need an exact total or page numbers. - **Use Page** when: - The UI shows 'Showing 1–20 of 12,431' or numbered page links. - Users must jump to an arbitrary page. - The dataset is small enough that count cost is negligible. ## Middle-ground techniques - **Cached / approximate counts** — precompute totals periodically or use database estimate stats (e.g. Postgres `pg_class.reltuples`) when 'about 12k results' is acceptable, then still return a `Slice` for the rows. - **Count only on first page** — some UIs fetch the total once and reuse it. ## Gotchas - Both `Page` and `Slice` still use `OFFSET`, so deep pages remain slow no matter which you pick — that problem needs keyset scrolling (`Window`/`ScrollPosition`), not Slice. - A `Slice` returning fewer than `pageSize` rows means `hasNext()` is false — don't infer 'no more data' from a full slice; always check `hasNext()`. - Make sure `Sort` is stable (unique tiebreaker like `id`), or slices can duplicate/skip rows across scrolls.

  • The product still wants to show an approximate total count. How can you keep Slice's cheap fetch but display a number?
    Compute the total out-of-band: a periodically cached count, or the database's estimated row count (e.g. Postgres reltuples / EXPLAIN estimate). Return the rows as a Slice and attach the approximate total separately, so per-request pagination stays single-query.

saying these in an interview costs you the question

  • Saying Slice is faster because it fetches fewer rows (it fetches one MORE row)
  • Claiming Slice solves the deep-offset performance problem
  • Assuming the count query is essentially free

context