HomeNikola Knezevic

In this article

Banner

Keyset (Cursor) Pagination in .NET using EF Core

30 Apr 2025
5 min

Special Thanks to Our Sponsors:

Sponsor Logo

EF Core is too slow? Discover how you can easily insert 14x faster (reducing saving time by 94%).

Boost your performance with our method within EF Core: Bulk Insert, update, delete and merge.

Thousands of satisfied customers have trusted our library since 2014.

👉 Learn more

Sponsor Newsletter

Offset pagination is probably the most common implementation I’ve worked with, it’s simple, intuitive and works well, especially when you need random access to different pages.

However, it doesn't scale well with large datasets, as performance tends to degrade with higher offsets.

That’s why, today, we’re going to explore a more efficient alternative for large datasets.

Keyset (Cursor) Pagination

Keyset (Cursor) Pagination is a more efficient way to paginate through large datasets.

Instead of using an arbitrary page number and skipping rows with OFFSET, keyset pagination uses a consistent and indexed sort key.

Key is typically a unique, sequential column like Id. It basically remembers the last item from the previous page and uses that value to fetch the next set of results.

Keyset Cursor Pagination Diagram
sql
SELECT p."Id", p."Name", p."Description", p."Price"
FROM "Products" AS p
WHERE p."Id" > 9000
LIMIT 10

Keyset pagination is ideal in scenarios where users navigate forward through a feed.

Great example is infinite scrolling, however, despite its advantages keyset pagination isn’t a silver bullet. One of its main limitations is the lack of random access.

Users can’t jump to an arbitrary page because pagination is based on values from the previous page, not page numbers.

Another challenge arises when sorting by columns that aren’t unique. If you sort only by something like Name, and multiple records have the same value, it becomes hard to know where you left off.

You can mitigate this by including a secondary sort column, but it still adds complexity.

Keyset Pagination With Ef Core

You can implement keyset pagination in EF Core easily using the Where and Take methods:

csharp
var result = await _context.Products
    .Where(p => p.Id > Reference)
    .Select(p => new ProductResponse(p.Id, p.Name, p.Price))
    .Take(ItemsPerPage)
    .ToListAsync();
  • Where(p => p.Id > Reference) – This filters the products to only include those with an Id greater than a specific Reference value.
  • Take(ItemsPerPage) – Retrieves the required number of records.

I prefer to create a generic PagedResponse class, which can be helpful if you need to include additional metadata:

csharp
public sealed record KeysetPagedList<TKey, TItem>(
    TKey NextReference,
    IEnumerable<TItem> Items);
  • NextReference – This holds the value you’ll use for the next page request.
  • Items - The actual paginated results.

Keyset vs Offset Pagination

As you've probably noticed, keyset and offset pagination, while both used for pagination, serve different use cases depending on the scenario.

Since we previously mentioned that keyset pagination is much faster, I decided to run a benchmark to compare them side by side:

csharp
[Benchmark(Baseline = true)]
public async Task<OffsetPagedList<ProductResponse>> OffsetPagination()
{
    var queryable = _context.Products
        .Select(p => new ProductResponse(
            p.Id,
            p.Name,
            p.Price));

    var result = await queryable
        .Skip((PageNumber - 1) * ItemsPerPage)
        .Take(ItemsPerPage)
        .ToListAsync();

    var count = await queryable.CountAsync();

    var response = new OffsetPagedList<ProductResponse>(
        result,
        count);

    return response;
}
    
[Benchmark]
public async Task<KeysetPagedList<int, ProductResponse>> KeysetPagination()
{
    var result = await _context.Products
        .Where(p => p.Id > Reference)
        .Select(p => new ProductResponse(p.Id, p.Name, p.Price))
        .Take(ItemsPerPage)
        .ToListAsync();

    var response = new KeysetPagedList<int, ProductResponse>(result[^1].Id, result);

    return response;
}
benchmark
| Method           | Mean       | Error     | Ratio         | Allocated | Alloc Ratio  |
|------------------|------------|-----------|---------------|-----------|--------------|
| OffsetPagination | 4,362.9 us | 26.84 us  | baseline      | 16.83 KB  |              |
| KeysetPagination |   606.1 us | 11.34 us  | 7.20x faster  | 12.88 KB  | 1.31x less   |

From the results, we can clearly see that keyset pagination is much faster.

Conclusion

Keyset pagination is a fast and efficient way to paginate large datasets by using a stable, indexed column as a reference point.

While it sacrifices the ability to jump to arbitrary pages, it significantly improves performance and consistency, especially for real-time or infinite-scroll applications.

If you want to check out examples I created, you can find the source code here:

Source Code

I hope you enjoyed it, subscribe and get a notification when a new blog is up!

Subscribe

Stay tuned for valuable insights every Thursday morning.