Skip to content

Instantly share code, notes, and snippets.

@liamnichols
Created January 30, 2024 10:31
Show Gist options
  • Save liamnichols/0064b7c67241854624065ab12e42d261 to your computer and use it in GitHub Desktop.
Save liamnichols/0064b7c67241854624065ab12e42d261 to your computer and use it in GitHub Desktop.
A bit of an overview of issues using offset-based pagination when concerned with mutation of the paginated content

Offset based pagination and mutating lists

Offset pagination offers a simple solution when it comes to paging a collection of data where the contents of the collection infrequently updates.

When using offset-based pagination, the client will specify the page of data that they require, and the size of the page. For example, lets consider requesting letters of the alphabet. When working with a page size of 5, the data will be broken out into the following pages:

Letter | A B C D E  F G H I J  K L M N O  P Q R S T  U V W X Y  Z
 Page  | 1 1 1 1 1  2 2 2 2 2  3 3 3 3 3  4 4 4 4 4  5 5 5 5 5  6

This is great, because you can pretty safely assume that page 4 will always contain the letters P, Q, R, S and T.

However this becomes a less reliable solution when the contents of the collection can change. For example, lets assume that we provide a list of the users favourite letters:

Fav Letter | R G B Q W  E R T Y U  I O P 
   Page    | 1 1 1 1 1  2 2 2 2 2  3 3 3

After fetching the first two pages (R, G, B, Q, W, E, R, T, Y, U) successfully, the client might also decide to remove the letters R, G and B from their favourites.

This would change the contents of the collection to the following:

Fav Letter | Q W E R T  Y U I O P 
   Page    | 1 1 1 1 1  2 2 2 2 2

Becuse three elements at the start of the collection were removed, items are shifted onto other pages now causing the client to enter an invalid state.

The client would still assume that there is a third page of data, but when requesting it, the page would be empty because it no longer exists. Additionally, if the client was to request page 2 again, it would now receive Y, U, I, O and P instead of E, R, T, Y and U like it saw previously.

The only option here is for the client to start again from the top, which can result in a pretty frustrating experience for the user.

To provide a visual example of this:

Saved.Recipes.mp4

In the video above, the user scrolled through over 8 pages of saved recipes and then decided to remove a recipe from the list.

Because the client used offset pagination, this would have invalidated all pages after the removed recipe and caused data to be lost should it attempt to fetch the next page. As a result, the client opted to reset the paginatior and return the user at the top in order to refetch the data from the first page.

Afternatives to offset pagination

As an alternative to offset pagination, it is possible instead adopt a concept called cursor-based pagination.

Instead of requesting data by the page number (which can change), you use a contextual cursor and fetch a number of items before or after a specific cursor.

null generally indicates the start of the collection, and a value that is order-aware can be used as the cursor. For example, if the list of favourite letters was ordered by the date that the letter was added to the collection, the cursor might be the "created_at" value.

Referring back to the following data set:

Fav Letter | R G B Q W  E R T Y U  I O P 

Even after removing R, G and B, the client could reliably fetch I, O and P by fetching a 5 items after the created_at date of the U entry.

Note that it's typical for the API to include the 'before' and 'after' cursors that can be used to fetch the next and previous pages of data instead of the client having to construct the cursor itself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment