Skip and limit pagination
Use cases of skip and limit pagination, performance benefits, and implementation strategies.
Skip and limit pagination is a straightforward method for dividing large datasets into manageable pages. By specifying how many items to skip and how many to fetch, this approach is ideal for traditional page navigation and smaller datasets.
Use cases
Skip and limit pagination is best suited for the following scenarios where predictable, numbered pages are important:
- Small to medium datasets – Efficient for fewer than 10,000 items.
- Traditional page navigation – Ideal for "Page 1, Page 2, Page 3" style interfaces.
- Admin panels and reports – Provides structured navigation and exact page numbers.
The following query retrieves a paginated list of articles, skipping a specified number of items and limiting the results. It sorts articles by their publish date in descending order.
query GetPagesWithSkipLimit($skip: Int = 0, $limit: Int = 10) {
ArticlePage(
limit: $limit,
skip: $skip
orderBy: { StartPublish: DESC }
) {
items {
Name
RelativePath
StartPublish
}
total
}
}Example variables to fetch page 3 with 10 items per page, skipping the first 20 items:
{
"skip": 20,
"limit": 10
}Performance characteristics
Advantages
- Simple implementation – Direct page number calculations.
- Performant for typical use cases – Optimized for the first 10,000 results.
- Predictable memory usage – Consistent resource consumption.
- Easy navigation – Jump to any page directly.
- Better for user interfaces – Page numbers such as "Go to page X".
Limitations
- Performance degradation – Slows down with very large offsets (10,000+ skip values).
- Inconsistent results – If content is added or removed during pagination.
- Resource-intensive – For deep pagination in large datasets.
Real-world implementation
Skip and limit pagination is good for traditional page navigation, which is perfect for article listings, search results, and admin panels.
The following GraphQL query retrieves a paginated list of article pages filtered by category and sorted by publish date:
query GetArticlePages($page: Int = 1, $pageSize: Int = 10, $category: String) {
ArticlePage(
where: { Category: { eq: $category } }
limit: $pageSize,
orderBy: { StartPublish: DESC }
) {
items {
Name
TeaserText
RelativePath
StartPublish
Category
}
total
}
}The following React component handles pagination logic, fetches data using GraphQL, and renders navigation controls, including previous and next, and a page selector drop-down list:
function ArticlePagination({ category }) {
const [currentPage, setCurrentPage] = useState(1);
const pageSize = 10;
const { data, loading, error } = useQuery(GET_ARTICLE_PAGES, {
variables: {
page: currentPage,
pageSize,
category
}
});
const totalPages = Math.ceil(data?.ArticlePage?.total / pageSize);
const hasNextPage = currentPage < totalPages;
const hasPreviousPage = currentPage > 1;
return (
<div>
<ArticleList items={data?.ArticlePage?.items} />
<Pagination>
<button
disabled={!hasPreviousPage}
onClick={() => setCurrentPage(prev => prev - 1)}
>
Previous
</button>
<span>Page {currentPage} of {totalPages}</span>
<button
disabled={!hasNextPage}
onClick={() => setCurrentPage(prev => prev + 1)}
>
Next
</button>
{/* Jump to page functionality */}
<select
value={currentPage}
onChange={(e) => setCurrentPage(Number(e.target.value))}
>
{Array.from({length: totalPages}, (_, i) => (
<option key={i + 1} value={i + 1}>
Page {i + 1}
</option>
))}
</select>
</Pagination>
</div>
);
}Performance optimization tips
Limit maximum skip values
Restrict the maximum skip value to prevent performance degradation. This ensures queries remain efficient without attempting to skip excessive items.
query SafeSkipLimit($skip: Int = 0, $limit: Int = 10) {
ArticlePage(
# Prevent performance issues with very large skip values
skip: $skip
limit: $limit
) {
items { Name }
total
}
}Use indexed fields for sorting
Enhance query performance by sorting with indexed fields. Indexed sorting is faster and more efficient than using non-indexed fields.
query OptimizedSorting {
ArticlePage(
# Use indexed fields for better performance
orderBy: { StartPublish: DESC } # Indexed
# Avoid: orderBy: { CustomTextField: ASC } # Not indexed
) {
items { Name }
}
}Error handling and edge cases
The following function safely executes a paginated query, handles empty results, and redirects to the last valid page if the requested page exceeds the total number of pages:
async function safePaginatedQuery(page, pageSize) {
try {
const skip = (page - 1) * pageSize;
const { data } = await client.query({
query: PAGINATED_QUERY,
variables: { skip, limit: pageSize }
});
// Handle empty results
if (!data.ArticlePage.items.length && page > 1) {
const totalPages = Math.ceil(data.ArticlePage.total / pageSize);
if (page > totalPages) {
// Redirect to last valid page
return await safePaginatedQuery(totalPages, pageSize);
}
}
return data;
} catch (error) {
console.error('Pagination error:', error);
throw new Error('Failed to load page');
}
}Advanced pagination patterns
Enhance the user experience with these advanced pagination strategies for large datasets.
- Hybrid approach for large datasets
- Search results with smart pagination
- Adaptation with result count
Hybrid approach for large datasets
Combine pagination methods to optimize user experience. The following example uses infinite scrolling for large datasets and traditional pagination for smaller ones.
function HybridPagination({ totalItems }) {
const [viewMode, setViewMode] = useState('pages'); // 'pages' or 'infinite'
const isLargeDataset = totalItems > 10000;
if (isLargeDataset && viewMode === 'infinite') {
return <CursorBasedInfiniteScroll />;
} else {
return <SkipLimitPagination maxPages={500} />; // Limit deep pagination
}
}Search results with smart pagination
Implement smart pagination for search results, adapting the method based on the number of results.
query SearchWithSmartPagination($searchTerm: String!, $skip: Int = 0, $limit: Int = 10) {
Content(
where: {
_fulltext: { match: $searchTerm }
}
skip: $skip
limit: $limit
orderBy: { _ranking: SEMANTIC }
) {
items {
Name
_score
_typeName
RelativePath
}
total
}
}Adaptation with result count
Switch to infinite scroll for large result sets, maintaining traditional pagination for smaller ones.
function SearchResultsPagination({ searchTerm }) {
const [page, setPage] = useState(1);
const pageSize = 10;
const { data } = useQuery(SEARCH_QUERY, {
variables: { searchTerm, skip: (page - 1) * pageSize, limit: pageSize }
});
const totalResults = data?.Content?.total || 0;
// Switch to infinite scroll for large result sets
if (totalResults > 1000) {
return <InfiniteScrollResults searchTerm={searchTerm} />;
}
return <TraditionalPagination data={data} page={page} setPage={setPage} />;
}Updated 16 days ago
