DEV Community

Cover image for We Hit the Browser's Scroll Limit at 550K Rows. Here's How We Reached 1 Million.
JANG KIYOUNG
JANG KIYOUNG

Posted on

We Hit the Browser's Scroll Limit at 550K Rows. Here's How We Reached 1 Million.

We Hit the Browser's Scroll Limit at 550K Rows. Here's How We Reached 1 Million.

A few days ago, we released BGrid (BeautifulGrid), an open-source
React + TypeScript data grid under the Apache-2.0 license.

One of the things we've been obsessing over is large datasets.

Our first public demo handled:

550,000 rows

Honestly, 550K rows already felt like plenty.

But as developers, we all know what happens when something works.

We immediately ask:

"Okay... but how far can we push it?"

So we tried going further.

600K.

700K.

1 million.

And then something weird happened.

The grid didn't just get slower.

The layout started breaking.

React wasn't crashing.

The browser wasn't out of memory.

We weren't rendering 700,000 DOM nodes.

So what was going on?

Turns out, we had run into a completely different kind of limit.


The problem wasn't React

BGrid uses virtual scrolling.

That means if you have 550,000 rows, we obviously don't create 550,000
<div> elements.

Only the rows currently visible in the viewport are actually rendered.

Dataset
─────────────────────
Row 1
Row 2
Row 3
...
Row 549,998
Row 549,999
Row 550,000
─────────────────────

        ↓

Actual DOM

Row 549,989
Row 549,990
Row 549,991
...
Row 550,000
Enter fullscreen mode Exit fullscreen mode

So the number of rendered DOM nodes stays small.

Great.

Problem solved, right?

Well...

Not quite.


Our invisible giant <div>

Although we weren't rendering every row, the scrollbar still needed to
represent the entire dataset.

Our rows were approximately 29px high.

So for 550,000 rows:

550,000 × 29px = 15,950,000px
Enter fullscreen mode Exit fullscreen mode

Yes.

Almost 16 million pixels.

Basically, we had created an invisible skyscraper inside the browser.

And the browser was politely telling us:

"No."

😅

At around this range in our target browsers, we started hitting
practical CSS/layout limits for extremely large element dimensions.

The physical scroll area could no longer grow reliably.

That's when we realized something important:

Our virtualized rows weren't the problem.\
Our virtualized scrollbar was.


Why 550K rows?

This also explains why our original demo stopped at the strangely
specific number of 550,000 rows.

It wasn't because BGrid suddenly became too slow at row 550,001.

It was basically this:

Row height: 29px

550,000 × 29px ≈ 15.95 million px
Enter fullscreen mode Exit fullscreen mode

We were approaching the practical physical height limit we were seeing
in the browser.

So we initially capped the demo around there.

And for a while, that seemed reasonable.

After all...

Who needs more than 550,000 rows in a browser?

Then the developer brain kicked in:

"But what if we didn't need a 16-million-pixel div at all?"

And that changed everything.


Stop scrolling physically

The old model was simple.

The number of rows directly determined the physical scroll height.

OLD MODEL

550,000 rows
      ↓
× 29px
      ↓
15,950,000px
      ↓
Physical scroll area
      ↓
Browser says: that's enough
Enter fullscreen mode Exit fullscreen mode

This works beautifully...

until it doesn't.

So in BGrid v1.0.3, we changed the architecture.

Instead of treating the browser's physical scroll position as the actual
dataset position, we separated them.

We moved from:

Physical scrolling

to:

Logical scrolling


Physical scroll ≠ Logical scroll

The new idea is surprisingly simple.

The browser only needs to scroll through a safe physical range.

BGrid then maps that physical scroll position into the much larger
logical dataset range.

Conceptually:

NEW MODEL

1,000,000+ rows
        ↓
Logical scroll range
        ↓
Mapped to a bounded physical scroll range
        ↓
Calculate logical row position
        ↓
Render only visible rows
Enter fullscreen mode Exit fullscreen mode

So instead of saying:

1 row = 29 physical pixels
Enter fullscreen mode Exit fullscreen mode

for the entire dataset, we can think in terms of:

Physical scrollbar position
        ↓
Logical position
        ↓
Target row index
        ↓
Visible rows
Enter fullscreen mode Exit fullscreen mode

The browser no longer needs to know how physically tall one million rows
would be.

And that's a big difference.


Hello, 1,000,000 rows 👋

Once we removed the dependency on the full physical scroll height, the
old 550K ceiling disappeared.

So naturally, we tried:

1,000,000 rows
Enter fullscreen mode Exit fullscreen mode

And it worked.

That is now the large-data example in BGrid v1.0.3.

The important part isn't just that the grid can display one million
rows.

It's that we no longer need this:

1,000,000 × 29px = 29,000,000px
Enter fullscreen mode Exit fullscreen mode

as a physical DOM height.

Instead:

1,000,000 rows

        ↓

logical scroll space

        ↓

bounded physical scrollbar

        ↓

virtualized viewport

        ↓

only visible rows rendered
Enter fullscreen mode Exit fullscreen mode

The dataset can grow without requiring the DOM element height to grow at
the same rate.


So... what about 10 million?

You know exactly what happened next.

1 million worked.

So obviously we typed another zero.

😂

10,000,000 rows
Enter fullscreen mode Exit fullscreen mode

And yes.

BGrid was able to display it.

Now, before someone opens Hacker News and starts sharpening their
keyboard:

No, we're not suggesting you should load 10 million database records
into browser memory.

Please don't. 😄

At that scale, server-side processing, pagination, incremental loading,
or other data strategies will usually make much more sense.

The 10M experiment wasn't about saying:

"Everyone should put 10 million rows in React!"

It was about answering a different question:

Is the grid itself still fundamentally limited by the browser's
maximum physical scroll height?

After switching to logical scrolling, the answer is:

No.

And that's what we really wanted to find out.


There are still limits, of course

Logical scrolling doesn't magically give browsers infinite resources.

At very large dataset sizes, you still have to think about:

  • Memory usage
  • Data generation
  • Sorting
  • Filtering
  • Searching
  • Serialization
  • Network transfer
  • Application state management

If you load 10 million giant JavaScript objects into memory and your
laptop starts sounding like a drone...

That's probably not the grid's fault. 😅

What logical scrolling solves is one specific architectural limitation:

The maximum dataset size is no longer directly tied to the maximum
physical height of a DOM element.

And that gives us a much better foundation for large datasets.


BGrid isn't just a 1M-row demo

Performance is fun to talk about because...

well...

1,000,000 ROWS looks cool in a headline. 😎

But BGrid isn't intended to be a benchmark toy.

We're building it for actual business applications.

It currently includes features such as:

  • Virtual scrolling
  • Cell editing
  • Cell merging
  • Sorting
  • Filtering
  • Summary
  • Pivot
  • Frozen rows
  • Frozen columns
  • Custom editors
  • Keyboard navigation

It's built with React + TypeScript and released under the
Apache-2.0 license.

No commercial license is required for the open-source features.


Why build another React data grid?

Fair question.

There are already some excellent data grids in the React ecosystem.

But we've been building business applications --- and grids --- for a long time.

We wanted something that was:

  • Open source
  • Business-application focused
  • Strongly typed
  • React-friendly
  • Easy for developers to customize
  • Easy for AI coding agents to understand and use

That last point has become increasingly interesting to us.

In a world where developers are writing more software together with AI,
good types, predictable APIs, examples, and documentation aren't just
developer experience anymore.

They're also AI developer experience.

That's something we're actively experimenting with in BGrid.


Try to break it

The public demo currently uses 1,000,000 rows.

And honestly, we'd love for people to try to break it.

👉 Demo: https://bgrid.axisj.com/

👉 GitHub: https://github.com/axisj/beautiful-grid

If you're building React applications, give it a try.

If something breaks, please open an issue.

If you think the architecture is wrong, tell us.

If you know a better way to solve the scrolling problem, we'd genuinely
love to hear it.

And if you like the project, a GitHub ⭐ would help a young open-source
project a lot.


One question for other frontend developers:

What's the largest dataset you've ever had to display in a
browser?

And more importantly...

what broke first? 😄

Top comments (0)