Thuta Learning
IntermediateData & Databasesbeginner

Sorting, Pagination, and search_after

What you'll walk away with

  • Explain the core ideas behind Sorting, Pagination, and search_after
  • Run the sample Elasticsearch query or code and verify its output
  • Apply the technique correctly to the Tutorial Platform and production scenarios

Build the mental model

By default, Elasticsearch returns search results sorted by `_score` descending, but the `sort` parameter can reorder them by a field (say, `publishedAt` descending) — `sort` only works on `keyword`, numeric, or date fields (attempting it on a `text` field causes errors or wrong ordering). Pagination navigates through pages using `from` (how many documents to skip) plus `size` (how many to return), but internally each shard sorts `from + size` documents and returns them to a coordinating node, which merges and re-sorts them — as `from` grows large (a deep page like page 10,000), the memory and CPU cost grows too, and exceeding Elasticsearch's default `index.max_result_window` (10,000) causes an outright error. `search_after` solves this problem — instead of jumping to a page number, it's a cursor-based approach that says "continue from the last document's sort values", so each shard doesn't need a full re-sort, only continuing the search from the cursor position forward. This directly parallels the Redis course's pagination lesson (offset vs cursor): `from`/`size` is like a database's `OFFSET`/`LIMIT`, and `search_after` shares the same concept as cursor-based pagination — like a social media feed's "scroll for more posts" pattern, where you never jump to a page number, only ever pass along a "continue from here" pointer.

Connect it to a real scenario

Sort the Tutorial Platform's search result list by `publishedAt` descending — for the admin UI's "page 1, page 2" navigation, `from`/`size` works fine through the first 10 pages (100 results). But a public-facing "infinite scroll" search UI can let a user scroll through hundreds of results for "redis", easily crossing the deep-pagination limit — implement `search_after` there, combining `publishedAt` with a tie-breaker of `_id` (a unique field) as two sort keys; without a tie-breaker, documents sharing the same `publishedAt` can get duplicated or skipped across page boundaries.

Try the working example

http
GET /tutorials/_search
{
  "size": 10,
  "query": { "match": { "body": "redis" } },
  "sort": [
    { "publishedAt": "desc" },
    { "_id": "asc" }
  ],
  "search_after": ["2026-08-01T00:00:00Z", "tutorial-42"]
}
You should see
You get the next page of "redis" search results, continuing from the previous page's last document's sort values as a cursor.

5-minute try-it

Write a `search_after` query sorted by `lessonCount` descending with `_id` ascending as a tie-breaker, and predict what happens if you run the same request with `from: 50000, size: 10` instead.

One important caution

Assuming `from`/`size` scales fine for deep pagination (page 500+) and designing a production UI around it without accounting for the `max_result_window` limit — large `from` values cause errors or high latency.

Implementing `search_after` with only one sort key (like `publishedAt` alone) and no tie-breaker field with unique values — documents sharing the same value can be duplicated or skipped across page boundaries.

Elasticsearch Guide — Paginate Search ResultsElastic

Easy traps

  • Assuming `from`/`size` scales fine for deep pagination (page 500+) and designing a production UI around it without accounting for the `max_result_window` limit — large `from` values cause errors or high latency.
  • Implementing `search_after` with only one sort key (like `publishedAt` alone) and no tie-breaker field with unique values — documents sharing the same value can be duplicated or skipped across page boundaries.
  • Validate sample queries and requests on a local or test instance with recoverable data before applying them to production.

Exercise

Write a `search_after` query sorted by `lessonCount` descending with `_id` ascending as a tie-breaker, and predict what happens if you run the same request with `from: 50000, size: 10` instead.

You'll know it worked when: You get the next page of "redis" search results, continuing from the previous page's last document's sort values as a cursor.

Sorting, Pagination, and search_after | Thuta Learning