Pagination
Splits a long list into pages with Prev, Next and page numbers. Learn numbered pages vs "Load more" vs infinite scroll, why the page belongs in the URL, and the boundary bugs hiding on page 1, the last page and page 999.
You’re shopping for a tent. You scroll to the bottom of page 1, tap 2, and the first tent on page 2 is… the last tent from page 1. You open a promising one on page 3, press Back, and you’re on page 1 again. You give up and buy somewhere else.
Pagination looks like a row of small numbers. Underneath, it’s arithmetic (which items belong on page 3?), state (where was the user?) and boundaries (page 1, the last page, page 0, page 999). Those three are exactly where testers find bugs.
What is it?
PaginationPaginationSplitting a long list into pages that load one at a time.Open in glossary → splits a long list (products, search results, orders, transactions) into pages that load one at a time. The user moves with Previous / Next and page numbers. Pages keep each request small and fast, and give users a sense of where they are: page 5 of 16.
You’ll meet it in web shops and search results, bank transaction histories, admin tables, and in the API behind almost every list in a mobile app, even when the app hides it behind a “Load more” button or infinite scrollInfinite scrollLoading more items automatically when the user reaches the bottom of a list.Open in glossary →.
Anatomy
Showing 81–100 of 312
Numbered pagination on a web shop, page 5 of 16
- 1
Result summary
“Showing 81–100 of 312”. Tells users where they are. Must be right on the last page too.
- 2
Previous
Goes one page back. Disabled (or hidden) on page 1.
- 3
Page number
Jumps to that page. A link or button, at least 44×44 pt on touch screens.
- 4
Current page
Highlighted and marked with aria-current="page". Not just a color change.
- 5
Ellipsis (…)
Shows that pages are skipped. Not clickable, hidden from screen readers.
- 6
Last page
Lets users jump to the end. Its number must match the real number of pages.
- 7
Next
Goes one page forward. Disabled on the last page.
- 8
Page size selector
“Per page: 20 / 50 / 100”. Changing it must land on a page that exists.
Numbered pages, “Load more” or infinite scroll?
There are three common ways to reveal the rest of a long list. Each one is valid in the right place. Switch between them and try to reach the footer:
Tents & packs
- Alpine Backpack€9.99
- Breeze Backpack€46.99
- Cedar Backpack€83.99
- Dune Backpack€120.99
- Ember Backpack€157.99
- Fjord Backpack€14.99
- Glacier Backpack€51.99
- Harbor Backpack€88.99
Numbered pages
Scroll down and use Prev / Next. The footer is right below the pager.
- + You always know where you are (page 3 of 8)
- + Page can live in the URL: share, refresh, Back
- + Footer is always reachable
- − Extra taps to see more
- − Small targets on phones if done badly
Items on screen: 8 of 60 · page 1 of 8
| Pattern | Best for | Watch out for |
|---|---|---|
| Numbered pages | Search results, shops, tables, anything users compare or come back to | Tiny targets on phones, wrong page math |
| “Load more” button | Mobile lists, product grids | Losing loaded items and position after Back |
| Infinite scroll | Social feeds, photo streams, “just browsing” | Unreachable footer, no sense of position, memory, accessibility |
Designs & variants
- Full pager (web, desktop): ‹ 1 … 4 5 6 … 16 ›. Shows first, last, the current page and its neighbors, with an ellipsis for the gaps.
- Compact pager (phones): ‹ Prev · Page 5 of 16 · Next ›. Fewer, bigger targets. Material and Apple’s guidelines don’t define a numbered pager; mobile apps usually prefer “Load more” or infinite scroll.
- Prev / Next only: for feeds or cursor-based APIs where the total isn’t known.
- With a page size selector: “Per page: 20 / 50 / 100”, common in tables and admin tools.
- With a result summary: “Showing 81–100 of 312”. Small text, many bugs.
When to use it
- Users need to find, compare and come back to items: shops, search results, statements, admin tables.
- The list is long (hundreds or thousands of items) and loading everything would be slow.
- The page should be shareable or bookmarkable (“look at page 3 of the sale”).
- There is important content below the list: a footer, filters, contact details.
Good and bad uses
Short list, show it all
A settings screen with 14 options, or a cart with 6 items. Just show the list.
Pages for a handful of items
“Page 1 of 2” for 12 notifications. Users have to tap to see the last two, and gain nothing.
Infinite scroll for a feed
A photo feed users browse without a goal. Nobody needs to come back to “page 7”.
Infinite scroll on a shop with a footer
The Returns and Contact links in the footer run away every time the user scrolls toward them.
How good pagination behaves
The page lives in the URL
On the web, the current page (and page size, sort, filters) belongs in the address: a query parameterQuery parameterData in a URL after the ?, like ?q=mug&sort=price, often used to keep search and filter state.Open in glossary → like shop.example.com/products?page=3&size=20. That single decision makes Back, Refresh, bookmarks and shared links all work. If the URL never changes, Back from a product takes the user to page 1, and a refresh throws away their place.
In apps, the same idea applies: after opening an item and going back, the list must be exactly where it was: same page, same loaded items, same scroll position.
The math is right at every boundary
Page n shows items (n − 1) × size + 1 to n × size, but never more than the total. Classic boundary value analysisBoundary value analysisTesting values at and just around the edges of valid ranges, where bugs love to hide.Open in glossary → applies; test these pages every time:
| Page | What should happen |
|---|---|
| 1 | Prev disabled. Items 1–20. |
| 2 | Starts with item 21. Nothing from page 1 repeats. |
| Last (16) | Next disabled. “Showing 301–312 of 312”. Only 12 items. |
| 0, −1, abc | Redirect to page 1 (or a clear error). Never a crash. |
| 999 | A helpful “This page doesn’t exist” with a link to the last page. Not an endless spinner. |
Also check the edges of the data, not just of the pager: 0 results (an empty state, no pager), exactly one page (no pager, or both arrows disabled), exactly 20 items with 20 per page (one page, not two with an empty second one), and a last page with exactly one item.
Changing the page size lands somewhere sensible
On page 8 with 20 per page, the user switches to 50 per page. There are now only 7 pages, so “page 8” doesn’t exist. Good apps go back to page 1, or better, to the page that contains the item the user was looking at (item 141 → page 3). Same rule when a filter reduces the results: never leave the user on a page that no longer exists.
Data that changes between pages
Lists aren’t frozen. While you read page 1, new listings get posted and others sell out. With offset pagination (“skip 20, take 20”), that shifts everything: you see duplicates when items are added, and miss items when items are removed. Cursor pagination (“20 items after item X”) largely avoids it, because each page starts right after the last item you actually received. Try both:
Page 1 (what you saw)
newest first
- City bike
- Desk chair
- Coffee grinder
- Guitar
Since then: 0 new, 0 removed
As a tester you can’t choose the strategy, but you can find the bug: open page 1, have someone (or an admin tool, or a second device) add or delete items, then go to page 2. Report duplicates and missing items with the exact items involved.
Common bugs
| # | Bug | What you see | Typical severity |
|---|---|---|---|
| 1 | Off-by-one offset | Last item of page 1 repeats on page 2, or one item is never shown | High |
| 2 | Wrong result summary | “Showing 301–320 of 312” on the last page | Low |
| 3 | Next enabled on the last page | Leads to an empty page or an error | Medium |
| 4 | Out-of-range page | ?page=999 shows an endless spinner, a crash or a blank page | Medium |
| 5 | Page size change keeps the page | Page 8 of 7: an empty list | Medium |
| 6 | Page not in the URL | Back, refresh or a shared link always open page 1 | Medium–High |
| 7 | Duplicates or gaps after data changes | The same item twice, or items missing across pages | Medium |
| 8 | Wrong total | “16 pages” but page 16 is empty, or the count ignores a filter | Medium |
| 9 | Tiny targets on phones | 24 px page numbers, users hit 4 instead of 5 | Medium |
| 10 | Filters/sort reset when paging | Page 2 shows unsorted, unfiltered results | High |
Stress-test it
A correct pager on a correct list. The page and page size are in the URL. Try the browser Back and Reload buttons, type ?page=999, ?page=0 or ?page=abc in the address, switch Results to 0 or 7, and change Per page while you’re on the last page.
Outdoor gear
HTTP 200 OKShowing 1–20 of 312
- #1Alpine Backpack€9.99
- #2Breeze Backpack€46.99
- #3Cedar Backpack€83.99
- #4Dune Backpack€120.99
- #5Ember Backpack€157.99
- #6Fjord Backpack€14.99
- #7Glacier Backpack€51.99
- #8Harbor Backpack€88.99
- #9Indigo Backpack€125.99
- #10Juniper Backpack€162.99
- #11Kestrel Backpack€19.99
- #12Lagoon Backpack€56.99
- #13Maple Backpack€93.99
- #14Nova Backpack€130.99
- #15Onyx Backpack€167.99
- #16Pine Backpack€24.99
- #17Quartz Backpack€61.99
- #18Ridge Backpack€98.99
- #19Sierra Backpack€135.99
- #20Tundra Backpack€172.99
Screen reader on the pager
“Pagination, navigation” … “Page 1, current page, button”
Try these
- Go to page 3, then browser Back and Reload
- Type
?page=999,?page=0or?page=abc - Open the last page, then switch Per page to 50
Find the bug
This web shop lists 312 products, 20 per page, and has six bugs. Explore it like a tester:
- Compare the last product on page 1 with the first one on page 2.
- Go to the last page (16) and read everything on it. Then look at the arrows.
- Use the links panel: a bookmark to
?page=3and an old link to?page=999. Watch the Network log. - Go to page 8 and change Per page.
- Go to page 3, open a product, then use the browser’s Back (and try Reload).
🐞 Bug hunt: Outdoor gear list
This demo has 6 hidden bugs. Explore it like a tester, then check your findings.
Loading products…
Links from emails & bookmarks
Network
- API requests appear here…
- 1
Hint: Note the last product on page 1, then go to page 2.
- 2
Hint: Go to page 16 and read the text under the title.
- 3
Hint: On page 16, look at the › arrow and tap it.
- 4
Hint: Open the shop.example.com/products?page=999 link and wait. Check the Network log.
- 5
Hint: Go to page 8, then set Per page to 50 or 100.
- 6
Hint: Go to page 3, open a product, press the browser Back button.
How to test it
Checklist
Pagination test checklist
0 of 14 checked
Example test cases
TC-PAG-001No duplicates between page 1 and page 2positivehigh
Preconditions
- 312 products, 20 per page
- Sorted by "Newest"
Steps
- Open the product list
- Note the last product on page 1
- Go to page 2
Expected result
Page 2 starts with product 21. The last product of page 1 does not appear on page 2.
TC-PAG-002Last page boundariesedgehigh
Preconditions
- 312 products, 20 per page
Steps
- Go to page 16
Expected result
12 products are shown, the summary says "Showing 301–312 of 312", Next is disabled.
TC-PAG-003Page survives Back and refreshpositivehigh
Steps
- Go to page 3
- Open any product
- Press the browser Back button
- Refresh the page
Expected result
Both times the list shows page 3 and the URL contains ?page=3.
TC-PAG-004Page number beyond the last pagenegativemedium
Steps
- Open shop.example.com/products?page=999
Expected result
A message explains the page does not exist and offers a link to the last page (16). No endless spinner. The server responds 404 (or redirects).
TC-PAG-005Invalid page valuesnegativemedium
Steps
- Open ?page=0
- Open ?page=-1
- Open ?page=abc
- Open ?page=2.5
Test data
page = 0, -1, abc, 2.5
Expected result
Each URL redirects to page 1 or shows a clear error. No crash, no empty list, no "Showing -19–0".
TC-PAG-006Changing page size on a late pageedgemedium
Steps
- Go to page 8 (20 per page)
- Change Per page to 50
Expected result
The list shows an existing page (page 3, containing item 141, or page 1). The URL and summary are updated.
TC-PAG-007Filter and sort are kept across pagespositivehigh
Steps
- Filter by "Tents" and sort by "Price: low to high"
- Go to page 2 and page 3
Expected result
Every page only shows tents, prices keep increasing across pages, and the filter chips stay selected.
TC-PAG-008Screen reader announces the current pagepositivemedium
Steps
- Turn on VoiceOver or NVDA
- Navigate to the pager by landmarks
- Move to the current page number
Expected result
The pager is announced as a "Pagination" navigation landmark and the current page as "Page 3, current page". The ellipsis is not read.
Tip: click a test case to expand it.
Tools
- The URL bar is your best tool: edit
?page=and?size=by hand to hit every boundary in seconds. - DevTools › Network (see DevTools for testers): filter by the API path and check
page,offset,limitorcursorfor each click. Anoffset=19on page 2 is a bug you can prove. - API directly (Postman): request the last page,
page=0, a negative page and a huge page; check status codes and thetotalfield. - Mobile apps: use a proxy tool (Charles, Proxyman) to see the paging requests behind “Load more” and infinite scroll.
Accessibility
- Landmark. The pager is a navigation landmark, so screen readerScreen readerSoftware that reads the screen aloud for blind and low-vision users, like VoiceOver or TalkBack.Open in glossary → users can jump straight to it. If there are two pagers (top and bottom), label them differently or keep only one.
Dev note
Wrap the pager in<nav aria-label="Pagination">. - Current page. Mark it with a visual style that isn’t only color (bold, filled background). Screen readers then say “Page 3, current page”.
Dev note
Mark it witharia-current="page". - Real links or buttons. Page numbers should be links or buttons. Give arrow-only controls an accessible name: “Previous page”, “Next page”. Hide the ellipsis from screen readers.
Dev note
Links like<a href="?page=3">, not clickable<span>s. - Disabled states. Prev on page 1 and Next on the last page should be disabled and announced as dimmed or unavailable, not just greyed out with a working click.
- KeyboardKeyboard navigationUsing a website with only the keyboard: Tab to move, Enter or Space to activate, Esc to close.Open in glossary →. Everything reachable with Tab, visible focus, Enter activates. After changing page, move focus to the top of the results (or the results heading) so keyboard users don’t have to tab back up through the whole page.
- Touch targetsTouch targetThe area of the screen that responds to a tap, which should be large enough for a finger.Open in glossary →. At least 44×44 pt on iOS and 48×48 dp on Android. On the web, WCAG 2.5.8 (AA) asks for at least 24×24 CSS px, and 44×44 px (2.5.5, AAA) is the comfortable target. A row of ten 24 px numbers is a classic mobile failure; a compact “Page 3 of 16” with big Prev/Next buttons is better.
- Infinite scroll is hard for keyboard and screen reader users: new items appear without notice and the footer is unreachable. Offer a “Load more” button or a way to pause loading.
Mobile vs web
- No native numbered pager. Lists usually load the next page when the user nears the end (infinite scroll) or show a Load more row.
Dev note
UITableView, SwiftUIList. - Test on a slow network: is there a loading row at the bottom? What happens if loading fails: a Retry button, or a silent dead end?
- Pull to refresh while on “page 3” of loaded items: does the list reset cleanly without duplicates?
- Open an item, go back: the list must keep every loaded item and the scroll position.
- Lists usually load pages as you scroll.
Dev note
RecyclerViewwith the Paging library, or aLazyColumnin Compose. - Check rotation and process death (Developer options › Don’t keep activities): does the list come back on the same position, or restart from the top?
- Fast scrolling (“fling”) through hundreds of items: watch for duplicates, gaps, placeholders that never fill and jumps in position.
- Use airplane mode mid-scroll: a clear error row with Retry, not an endless spinner.
- Numbered pagination is most common here. The page must be in the URL: test Back, Forward, Refresh, bookmarks and shared links.
- Edit the URL by hand:
?page=0,?page=999,?page=abc,?size=10000(does the server cap the size?). - Check the responsive layout: at 360 px, does the pager wrap neatly, switch to a compact version, or overflow the page?
- Search engines index paginated pages: every page should have its own URL and return 200; non-existent pages should return 404, not an empty 200 page.
Check your understanding
Quick quiz
0/6 answered · 0 correct
1.The last product on page 1 is also the first product on page 2. What is the most likely cause?
2.There are 312 products and 20 per page. What should the summary say on the last page?
3.A user goes to page 3, opens a product and presses Back. They land on page 1. What is the root cause most likely to be?
4.Which pattern makes the footer (Help, Contact) hard or impossible to reach?
5.You are on page 8 (20 per page, 312 items) and switch to 50 per page. What is a good result?
6.Which attribute tells screen reader users which page they are on?
Key takeaways
- Pagination is math, state and boundaries. Always compare the last item of one page with the first of the next.
- Test page 1, page 2, the last page, page 0, page 999 and invalid values like
abc. - The page belongs in the URL so Back, refresh and shared links work. In apps, the list must keep its place after going back.
- Changing the page size, sort or filters must never leave the user on a page that doesn’t exist.
- Data that changes between page loads causes duplicates and gaps with offset paging.
- Choose the pattern on purpose: numbered pages to find and return, Load more for mobile lists, infinite scroll for feeds only.
- Accessibility: a nav landmark,
aria-current="page", real links or buttons, and 44 pt / 48 dp touch targets.
Finished this page?Nice work, this page is complete!
Mark it as done to track your progress. It's saved in this browser, no account needed.
Its review cards are now in Bites ⚡