Go to blue arrow
back to Tech Blog
Development
Tiago Franco

10 August 2026

Min Read

Pagy vs Kaminari vs will_paginate: the memory case for Pagy in 2026

Dark UI table of star names and radii with Page 1 of 4 controls for paginating Ruby on Rails with Pagy.

The short answer: on the pagination benchmark published by Pagy's author, Pagy is the lightest of the three mainstream Ruby on Rails gems by a wide margin. Rendering the same navigation over the same records, it allocates 184 objects where will_paginate allocates around 3,200 and Kaminari around 6,400. On an app serving thousands of simultaneous users, that gap sits in your memory ceiling per instance, which is to say the number of instances you pay for.

One thing to clear up first, because it dates a lot of the writing still floating around: Pagy is no longer the scrappy newcomer. It first shipped in February 2018 and is now a mature, heavily-adopted library. What has changed is the API. Pagy 43 was a ground-up redesign, so most tutorials you'll find (including, until now, ours) show calls that no longer exist. I've been building Rails apps since version 0.8, and I've paginated with all three of these gems in production; this piece is rewritten against the current version.

blue arrow to the left
Imaginary Cloud logo

Where the three gems actually stand in 2026

Before the benchmark, the maintenance picture, because "which is fastest" matters less than "which is still being looked after" when you're committing a codebase to it for years.

GemCurrent versionLatest releaseMaintenance signal
Pagy43.x2026Actively developed; frequent releases; full redesign in v43
Kaminari1.2.2December 2021Still the most-installed by far, but no release in over a year
will_paginate4.0.1June 2024Officially in maintenance mode: no new features, and the README itself points users to alternatives

A few things follow from that table.

will_paginate is not dead, but its own maintainer has drawn a line under it. It picked up Rails 7 support in the 4.0 release and got a patch in 2024, so the "last commit was 2017" framing you'll see in older posts (ours included) is simply wrong. The correct reason to avoid it for greenfield work isn't neglect. It's that the maintainer has explicitly put it into maintenance mode and stopped adding features. Fair basis for a decision; wrong basis if you're citing a 2017 commit date.

Kaminari is the incumbent, and that still counts for something. It has the largest installed base of the three by a wide margin, a deep body of documentation, view templates you can generate and theme, and per-model configuration. But its last release was in December 2021. It works, it's stable, and on a low-traffic admin panel none of the performance discussion below will ever be visible to you. Momentum, though, is clearly elsewhere.

Pagy is where the active development energy is. Beyond raw speed it now ships several pagination techniques in one gem (offset, countless, keyset, keynav, calendar and search), plus server-side or client-side rendering, and interactive dev tools. That breadth, not just the memory number, is a large part of why people pick it now.

blue arrow to the left
Imaginary Cloud logo

Rails' library boom, and why new arrivals are rare

The number of new RubyGems published each year grew through the early 2010s, peaked around 2015, and has declined since. That doesn't mean nothing new is worth your attention. It means the genuinely useful arrival is rarer now, and easier to miss. Pagy was one of them in 2018; the point of an update like this is that "rare and easy to miss" also describes the changes to a good library once the community has stopped writing about it.

New RubyGems published per year

2010 to 2026

Verified Estimated
~4k
~8k
~14k
~22k
27k
~21k
~17k
~13k
~11k
~10k
~9k
~9k
~8k
~8k
~9k
~9k
~9k
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026

Verified figures: 2014 (~27,000) and 2018 (~10,600 / ~29 per day), from Infinum's RubyGems analyses.

blue arrow to the left
Imaginary Cloud logo

What is Pagy?

If you've worked with Rails before, you've probably used will_paginate or Kaminari to paginate the index pages of an application. Pagination is the work of splitting a long list of records into numbered pages and rendering the links between them. If you're new to Rails, you'll be looking for a pagination approach fairly soon.

Pagy is a pagination library built with performance as the first priority, without being awkward to use on a new or existing application. It's framework-agnostic: it works with Rails and with lighter Ruby frameworks such as Sinatra and Padrino, and it paginates Active Record relations as well as plain arrays and other collections.

Another pagination gem was a fair thing to be sceptical about back in 2018. Fragmentation isn't good for open source, and some of the improvements Pagy brought were first proposed for Kaminari and rejected, on the grounds that they'd require substantial changes to that gem's core. That rejection is half the reason Pagy exists. Eight years on, the scepticism has mostly resolved itself: the download and star counts put Pagy firmly among the mainstream choices rather than off to one side.

blue arrow to the left
Imaginary Cloud logo

Why pagination memory matters in a Rails app

Think of every gem on a request path as luggage. You carry it on every trip, whether or not you open it, and pagination is the bag that comes along on almost every listing page in your product. A heavy bag isn't a problem on one trip. It's a problem when you're making ten thousand of them an hour.

The figures that follow come from the benchmark suite published by Pagy's author, which renders the same Bootstrap pagination over the same records with each gem in turn.

What the benchmark measures, and what it doesn't

The suite records two things per gem: the memory allocated to do the work, and the number of Ruby objects allocated. It does not measure the database query behind the page, which is the same regardless of which gem you use. It does not measure end-user page-load time either, which is dominated by the query, the view and the network. And it's published by the author of one of the three gems, so read it as a like-for-like comparison of pagination overhead rather than as an independent trial.

Two honesty notes on the numbers below. First, the published suite still benchmarks the legacy versions (Pagy v3, will_paginate 3.1.7, Kaminari 1.1.1), so treat them as the overhead of each gem's classic implementation. Second, Pagy's current v43 claims further gains over its own v3 baseline, so if anything the modern gap is wider, not narrower. The figures are the overhead, and nothing else.

The figures

Gem (version benchmarked)Objects allocatedTotal memory allocatedMemory growth, 2 to 20 pages
Pagy (v3.0.0)184~19 KBFlat (no measurable increase)
will_paginate (3.1.7)~3,198~341 KB~2x
Kaminari (1.1.1)~6,445~704 KB~4x

Object allocation is where the difference is starkest. Kaminari creates roughly 6,400 objects to render the navigation; will_paginate around 3,200. Both are heavy for the job. Pagy does it with 184.

The growth column matters just as much as the totals. As you widen the pagination bar from 2 links to 20, Pagy's memory use stays essentially flat, will_paginate roughly doubles, and Kaminari roughly quadruples. On a busy listing page with a wide page bar, that slope is paid on every request.

There's a code-size story underneath the memory story, too. Producing the identical output, Pagy is around 4 files and ~140 lines; Kaminari spans ~36 files, will_paginate ~23. Less code doing the same job usually means less allocation and less to reason about, and here it shows up directly in the profile.

Objects allocated to render pagination

Same navigation, same records. Lower is better.

Pagy
184
will_paginate
~3,198
Kaminari
~6,445

Ruby objects allocated to render the same pagination output. Source: Pagy author's benchmark suite (ddnexus/pagination-comparison), legacy gem versions. Pagy 43 claims further gains over the v3 baseline shown here.

So what does that buy you? Not speed, mostly

Every object allocated is memory held, then reclaimed by garbage collection (the background work Ruby does to free memory no longer in use), and all of it happens on a path that runs on most listing pages in the product. Cutting per-request allocation by an order of magnitude doesn't make one page load noticeably faster. It raises how many concurrent requests a given instance can hold before you add another one. On an infrastructure bill sized by instance count, that's where the saving lands, and the migration that unlocks it is measured in hours rather than sprints.

We see this pattern in our own delivery work. When we rebuilt FlippedNormals, a computer-graphics marketplace, the old WordPress platform could no longer keep up with its traffic, so we migrated it to a custom Ruby on Rails application and moved it onto AWS for a more scalable infrastructure. The whole migration took two months and lifted traffic by 4% after relaunch. It's a useful case for this discussion precisely because of its shape: a catalogue of more than 28,000 products means listing, search and browse pages are on the hot path, hit on close to every request. Those are exactly the pages where a pagination gem's per-request cost is multiplied by your traffic, and where the choice made early quietly sets the resource ceiling later.

blue arrow to the left
Imaginary Cloud logo

Why Pagy uses less memory

  • It runs its calculations on integers rather than on Ruby objects, so its cost doesn't grow with the size of the collection.
  • The core is tiny, a few dozen lines, and every module and method is public API, directly overridable without monkey-patching or subclassing.
  • It produces its own HTML, URLs, pluralisation and interpolation, staying away from your application models.
  • It uses purpose-built code rather than generic helpers, and in v43 it autoloads only the methods you actually call, so unused features cost no memory.

There's a large collateral benefit in all of that: the code is genuinely easy to read, which is why the author has been able to profile it line by line.

blue arrow to the left
Imaginary Cloud logo

How to find the gems sitting on your hot paths

Pagination is one case of a wider pattern, and the pattern is what we look for when we review a Rails stack. We call it a gem resource audit. Three steps.

1. Inventory what runs on every request. Not the whole Gemfile, only the gems invoked on the pages your traffic actually hits: the index and listing pages, the search results, the dashboard. Pagination, serialisation, authorisation checks and view helpers usually make this list. A gem used once a day in a background job isn't worth measuring.

2. Measure allocation, not wall time. Object allocation per request is the figure that predicts how an application behaves under concurrency, and it's stable enough to compare two implementations. memory_profiler and benchmark-ips will give you both allocation and timing for a single action. The difference between two gems on the same action is usually obvious in the first run.

3. Model the result in instances, not megabytes. A saving only becomes an argument when it's expressed as headroom: how many more concurrent requests an instance holds, and therefore how many instances the same traffic needs. That's the number a technical decision-maker can act on, and the one that justifies the migration effort.

Most Rails applications carry two or three of these bags. Pagination is the one we find most often, because it's chosen early, never revisited, and sits on exactly the pages that get the traffic.

blue arrow to the left
Imaginary Cloud logo

Getting started with Pagy 43 (current API)

This is where older guides will lead you astray: the Pagy::Backend / Pagy::Frontend includes, the items: keyword and the pagy_nav helper are all pre-v43. Here's the current setup, which follows the official quick-start guide.

1. Add the gem, pinned to the minor version (v43 uses a leap version number, so a minor-version pin protects you from breaking changes):

# Gemfile
gem 'pagy', '~> 43.6'

Pagy 43 requires Ruby 3.3 or newer.

2. Include the single entry-point module in your application controller:

# app/controllers/application_controller.rb
include Pagy::Method

That one include replaces the old two-module (Backend plus Frontend) setup. Methods are autoloaded only when used.

3. Paginate in the controller action. Pick a paginator: :offset is the classic behaviour, :keyset is the fastest for large ordered collections:

# Offset pagination (the familiar page-number behaviour)
@pagy, @records = pagy(:offset, Product.all, limit: 20)

# Keyset pagination (fastest for large, ordered datasets)
@pagy, @records = pagy(:keyset, Product.order(:id).all, limit: 20)

Note limit:, which replaced the old items: keyword.

4. Render the navigation in the view. The helpers are now methods on the @pagy object:

<%# Server-side navigation bar %>
<%== @pagy.series_nav %>

<%# Or a client-side, responsive variant, styled for Bootstrap or Bulma %>
<%== @pagy.series_nav_js(:bootstrap) %>
<%== @pagy.input_nav_js(:bulma) %>

<%# A "showing 476-500 of 1000" style summary %>
<%== @pagy.info_tag %>

That's the whole quick start. If you want to match your app's theme interactively, or ask Pagy's built-in AI a question from inside your own app, a single line in your layout head enables the dev tools:

<%== Pagy.dev_tools %>
blue arrow to the left
Imaginary Cloud logo

Migrating from will_paginate or Kaminari to Pagy

The move is contained in the controller and the view (the file that handles the request and the template that renders the page). The official migration guide breaks it into a few mechanical steps, and in practice it maps almost one-to-one.

Controller. Legacy calls translate directly:

# will_paginate
# @records = Product.some_scope.paginate(page: params[:page], per_page: 15)

# Kaminari
# @records = Product.some_scope.page(params[:page]).per(15)

# Pagy
@pagy, @records = pagy(:offset, Product.some_scope, limit: 15)

View. Swap the helper:

<%# will_paginate: <%= will_paginate @records %> %>
<%# Kaminari:      <%= paginate @records %> %>

<%== @pagy.series_nav %>

Global config. Search your codebase for the old gem's class name (WillPaginate, Kaminari) and remove its initialisers and any monkey-patching. A global per_page becomes Pagy::OPTIONS[:limit] in a Pagy initialiser, or a per-call limit:. If you relied on Kaminari's .page scope inside model methods, remove it there and apply the paginator in the controller instead.

Then run your tests, or click through the pages. Any leftover legacy code will raise, so remove it and retry until the app renders cleanly. If you were using Bootstrap or Bulma pagination styles, the existing CSS works with Pagy's series_nav helpers.

The trade-offs, stated plainly

Pagy is not a drop-in replacement: you can't swap the gem and leave your code untouched. The helper names differ, so every paginated view has to be visited, and the collection is no longer decorated with page methods the way Kaminari decorates an Active Record relation. Any code reaching for those methods has to change. Pagy's helpers return raw HTML strings, which is exactly what keeps them fast.

On an application with a handful of index pages, this is an afternoon. On one with fifty, plan it as a scoped piece of work, and treat it the way you would any change to code that runs on every request: behind a review, with the pages checked. If you'd rather hand it off, this is the sort of scoped work our Ruby on Rails development services cover.

blue arrow to the left
Imaginary Cloud logo

When Kaminari (or will_paginate) is still the right call

An honest recommendation needs a "when not to."

Stay on Kaminari when the application is already deeply built around it (you rely on its generated view templates and per-model configuration), or when the paginated pages carry low enough traffic that the memory difference will never show up on your bill. Kaminari is mature, widely documented, and still the most-installed of the three. The argument for Pagy is a resource argument, and it's strongest where pagination sits on a hot path.

will_paginate is harder to recommend for anything new, given its maintainer has closed it to new features. But if you have a stable app already using it and no performance pressure, "in maintenance mode" is not the same as "broken." There's no urgency to move for its own sake.

The resource case for Pagy is real, but it's a case, not a commandment. Match it to your traffic.

What choosing gems without measuring costs a stack

I started on will_paginate, because it was the best tool at the time. I moved to Kaminari a few months after it launched, and stayed there until a couple of weeks after Pagy appeared.

What surprised me later was that I'd moved from will_paginate to Kaminari without taking performance into account at all, and on the published benchmark, Kaminari is the heavier of the two. No benchmark on my part. Just momentum.

That raises a fair question about the rest of the stack: how many gems are we carrying that quietly hurt the applications we design and deliver, chosen the same way and never measured since? It's the question behind the audit above, and in our own delivery work it's now a standing item rather than an afterthought, because the cost of asking it late is an infrastructure bill nobody can explain. The logic travels well beyond pagination, and well beyond Rails. The framework you pick sets a floor. The libraries you bolt onto it set the ceiling.

As a disclaimer: the performance figures above come from the benchmark suite published by Pagy's author (ddnexus/pagination-comparison), run on the legacy versions of each gem.

Frequently asked questions

Do people still use Pagy in 2026?

Yes, widely. It's gone from newcomer to one of the mainstream Rails pagination choices, with tens of millions of downloads, thousands of GitHub stars and regular releases through 2026. The active development in this space is happening on Pagy; Kaminari has the larger installed base but hasn't shipped a release since 2021.

Is Pagy faster than Kaminari?

On the author's benchmark, yes, and by a wide margin on resource use: Pagy allocates around 184 objects against Kaminari's ~6,400, and uses far less memory in total. The user-visible difference on a single page load is small. The difference shows up in how many concurrent requests an instance can serve.

How do I migrate from will_paginate or Kaminari to Pagy?

Add gem 'pagy', '~> 43.6', add include Pagy::Method to your ApplicationController, replace each .paginate(...) / .page(...).per(...) call with @pagy, @records = pagy(:offset, scope, limit: N), and replace the view helper with <%== @pagy.series_nav %>. It's not a straight swap, and every paginated view has to be visited, but the change is contained and easy to reverse.

Does Pagy work well with large datasets?

Yes. Its calculations run on integers rather than Ruby objects, so its cost doesn't grow with the size of the collection. For very large, ordered tables, Pagy's :keyset paginator avoids the expensive deep-OFFSET scan entirely. The limit you eventually hit is the database count query, not the pagination gem, and that's true of any of the three.

Which frameworks and ORMs does Pagy support?

Pagy is framework-agnostic. It works with Rails and with lighter frameworks such as Sinatra and Padrino, and paginates Active Record relations as well as plain arrays and other collections. An ORM (object-relational mapper) is the layer that turns database rows into objects; Active Record is the one built into Rails, and Pagy works alongside it rather than attaching to your models.

What changed in Pagy 43?

It was a complete redesign. The setup collapsed to a single include Pagy::Method, configuration was cut dramatically, and the helpers moved onto the @pagy object (@pagy.series_nav rather than the old pagy_nav(@pagy)). It also added new paginators (countish, keyset, keynav, calendar and search), client-side rendering, and interactive dev tools. If you're following an older tutorial, expect the method names to differ.

At Imaginary Cloud we work across a wide tech stack, including Ruby on Rails. If pagination is on a hot path in your application, or you suspect other gems are costing you more than they should, we can run the gem resource audit described above against your stack and tell you where the resource cost actually sits. Get in touch and we'll look at it together.

UX Audit advertisement listing benefits to improve user experience and engagement, with 3D mobile app interface graphics.
Tiago Franco
Tiago Franco

CEO @ Imaginary Cloud and co-author of the Product Design Process book. I enjoy food, wine, and Krav Maga (not necessarily in this order).

LinkedIn

Read more posts by this author

People who read this post, also found these interesting:

Dropdown caret icon