Exclusive

An exclusive new experience โ€” React Native support is on its way to Planby!

LogoCreated with Sketch.planby
Description
Pricing๐Ÿ”ฅ
ReactPerformanceVirtualizationScheduler

Building a High-Performance React Scheduler with Virtualization

How virtual rendering and windowing make it possible to display 10,000+ events in a React timeline scheduler without sacrificing 60fps scroll performance.

Karol Kozer

ยท

February 20, 2026

ยท

8 min read

Every React timeline scheduler faces the same problem: as the number of events grows, the DOM grows too โ€” and rendering thousands of absolutely positioned elements simultaneously kills scroll performance. The standard fix is virtualization (also called windowing). Here is how it works, why it is harder to apply to a 2D timeline than to a vertical list, and how Planby implements it to maintain 60fps with 10,000+ events.


Why naive rendering fails at scale

A React timeline scheduler maps events to absolutely positioned DOM elements inside a scrollable container. Each event needs a left, width, and top value calculated from the event's start time, duration, and resource row.

With 100 events this is trivial โ€” 100 DOM nodes, each painted once. With 10,000 events the browser must:

1.

Mount 10,000 React components on initial render

2.

Run layout (reflow) on 10,000 absolutely positioned elements

3.

Repaint the visible viewport on every scroll event

4.

Keep all 10,000 nodes in memory even when they are far off-screen

The result: janky scrolling, high memory usage, and sometimes a completely unresponsive tab. This is not a React problem โ€” it is a DOM problem. The browser simply cannot efficiently manage that many painted nodes.


Virtual rendering: only mount what is visible

The solution is to render only the events that intersect the current viewport. When the user scrolls, unmount events that have left the viewport and mount the newly visible ones. From the browser's perspective, there are never more than ~50 DOM nodes on screen โ€” regardless of how many events exist in the dataset.

Libraries like react-window and react-virtualized implement this for simple vertical lists. A timeline scheduler, however, is a 2D surface โ€” it scrolls both horizontally (time axis) and vertically (resource/row axis). Naive list virtualization only solves one axis.

Key insight

A timeline scheduler needs bidirectional windowing: events are filtered by both the horizontal scroll position (visible time range) and the vertical scroll position (visible rows). Only events that are visible on both axes are mounted into the DOM.


DOM node count: virtualized vs non-virtualized

The clearest way to see the impact of virtualization is to compare DOM node counts at different dataset sizes. With a virtualized scheduler, the count stays constant because it only depends on how many events fit in the current viewport โ€” not on total dataset size.

Events in datasetNon-virtualized DOM nodesVirtualized DOM nodes (Planby)
100~100~40โ€“60
500~500~40โ€“60
1,000~1,000~40โ€“60
5,000~5,000~40โ€“60
10,000~10,000~40โ€“60

DOM node count with Planby is determined by viewport dimensions (how many events fit on screen), not by total dataset size.


How Planby implements bidirectional windowing

Planby's useEpg hook calculates three things on every scroll event:

1. Visible time range

Based on the current horizontal scroll position and the container width, Planby computes the start and end timestamps of the visible time window. Only events whose time range overlaps this window are candidates for rendering.

2. Visible row range

Based on the vertical scroll position and container height, Planby determines which resource rows are currently visible. Only events in those rows pass the second filter.

3. Intersection

Events that pass both filters are mounted. Everything else stays unmounted. This is O(n) in the total event count but the mounted count stays near constant (viewport-bound).

The hook returns a filtered epg array containing only the events that should be rendered. The Layout component maps over this array and renders the visible events. When the user scrolls, React reconciles the diff โ€” new events appear, out-of-view events disappear.


60fps scroll performance: the rendering budget

To maintain 60fps, each frame must complete in under 16.67ms. With 10,000 events mounted naively, a single scroll event triggers reflows across thousands of DOM nodes โ€” easily exceeding that budget and producing visible jank.

With virtualization, a scroll event only needs to:

1.

Recalculate the visible time window (pure JS, microseconds)

2.

Recalculate the visible row window (pure JS, microseconds)

3.

Compute the intersection (array filter, O(n) but fast for n=10k)

4.

Reconcile a ~50-node React tree (fast, bounded)

The total cost per scroll event stays well within the 16ms budget. Users see smooth, responsive scrolling regardless of how large the underlying dataset is.


Beyond events: virtualizing rows too

A scheduler with 500 resources (rows) has the same problem on the vertical axis. Planby handles this by also virtualizing the row (channel) list. Only the rows that are currently visible in the vertical viewport are rendered โ€” the sidebar labels and the event rows together.

This matters for use cases like large employee shift schedulers, hotel room booking systems, or production line planners where there may be hundreds or thousands of resource rows.


Why most React schedulers do not virtualize

Bidirectional virtualization on a 2D time-axis is significantly more complex than a simple vertical list. Most React scheduler libraries skip it for this reason โ€” FullCalendar, React Big Calendar, and DHTMLX Scheduler all render their full event datasets into the DOM. This works acceptably for small datasets (under a few hundred events) but degrades predictably as dataset size grows.

FullCalendar note

FullCalendar's resource timeline view (their closest equivalent to a horizontal scheduler) requires a $1,099/year Premium license and has no virtualization. With large event counts, its performance degrades measurably.


Summary

Virtualization is not optional for production-grade React timeline schedulers with real-world data volumes. The key points:

โœ“

Naive rendering puts all events into the DOM โ€” performance degrades linearly with event count

โœ“

Virtual rendering keeps DOM node count constant (~40โ€“60) regardless of dataset size

โœ“

A timeline scheduler needs bidirectional windowing (horizontal time axis + vertical row axis)

โœ“

Planby implements both axes simultaneously in the useEpg hook

โœ“

60fps scroll is achievable even at 10,000+ events when the DOM stays small

If you are evaluating React scheduler libraries for a data-heavy use case, virtual rendering is the single most important capability to check for. Without it, any library will hit a wall as your data grows.


Try Planby โ€” the virtualized React scheduler

Free tier available. PRO license is one-time, no annual renewal.

npm install @nessprim/planby
View pricing โ†’Large datasets page โ†’
โ† Back to BlogNext: FullCalendar Alternative โ†’

planby

The most powerful React scheduler & timeline component for building modern scheduling interfaces.

Twitter

Product

HomeDemoPricingFor FramerWhy Planby

ยฉ 2026 Nessprim Karol Kozer. All rights reserved.

Built with โค๏ธ for developers