TL;DR / Time slicing breaks rendering work into small chunks that yield control back to the browser's main thread between each chunk, preventing UI jank during expensive updates.
How It Works
Main Thread Timeline:
0ms 5ms 10ms 15ms 20ms
┌────────┐ ┌───────┐ ┌────────┐ ┌───────┐ ┌────────┐
│ React │ │ │ │ React │ │ │ │ React │
│ work │↑│ Paint │↑│ work │↑│ Paint │↑│ work │
└────────┘ └───────┘ └────────┘ └───────┘ └────────┘
Each slice yields back to browser via
MessageChannel, keeping frames at 60fps.
Without time slicing:
┌──────────────────────────────────────────────────┐
│ React work (long) │ jank!
└──────────────────────────────────────────────────┘
The browser's main thread handles everything: JavaScript execution, DOM updates, layout calculations, painting, garbage collection, and input event processing. When JavaScript monopolizes the thread for more than ~16ms (one frame at 60fps), the browser cannot paint or respond to input. The result is visible jank — dropped frames, unresponsive buttons, choppy animations.
Time slicing is the technique of breaking a large synchronous task into smaller chunks, yielding control back to the browser between each chunk. This allows the browser to handle paint, layout, and input events between chunks of work, maintaining visual smoothness even during expensive operations.
React's Implementation
React's concurrent renderer implements time slicing internally through its work loop. When processing a fiber tree, React does not traverse the entire tree in one synchronous pass. Instead, it processes fibers one at a time, checking after each unit of work whether it should yield.
The decision to yield is based on a time deadline. React uses MessageChannel to schedule work. At the start of each work cycle, React sets a deadline (typically 5ms from the current time). After processing each fiber, React checks if the deadline has passed. If it has, React yields — it posts a new message to the MessageChannel to schedule the next chunk, and returns control to the browser.
The browser then has an opportunity to process its task queue: repaint the screen, handle pending input events, run requestAnimationFrame callbacks, and execute any other scheduled microtasks or macrotasks. Once the browser finishes, the MessageChannel callback fires and React resumes where it left off.
Why Not requestIdleCallback?
Early React Fiber prototypes used requestIdleCallback (rIC) for scheduling. The idea was appealing: the browser tells you when it is idle, and you do work during those idle periods. In practice, rIC proved unreliable. Browsers may not fire rIC callbacks for hundreds of milliseconds under load. Safari did not support it for years. The callback frequency varied wildly across browsers and devices.
React switched to MessageChannel because it provides consistent, predictable scheduling. A message posted to a MessageChannel fires as a macrotask — after the current execution context, after microtasks, and after paint. This gives the browser a reliable opportunity to paint between React work chunks without depending on the browser's idle detection heuristics.
The 5ms Time Slice
React's default time slice is approximately 5ms. This was chosen based on the constraint of 60fps rendering: each frame has a 16.67ms budget. By limiting React work to 5ms per chunk, roughly 11ms remains for browser layout, paint, and other tasks. In practice, the browser needs 2-4ms for painting a typical frame, leaving comfortable margin.
The 5ms target is not a hard guarantee. If a single fiber takes 20ms to render (a deeply recursive component, perhaps), React cannot interrupt mid-fiber. Time slicing operates at fiber boundaries — React completes the current fiber before checking the deadline. This means individual slow components can still cause frame drops. Time slicing mitigates the problem at the tree level but cannot solve it at the component level.
Time Slicing and Priority
Time slicing enables, but is distinct from, priority-based scheduling. Time slicing provides the mechanism to pause and resume work. The priority system (lanes) determines which work to resume after yielding. When React yields and then resumes, it checks if higher-priority work has arrived. If a user typed a key during the yield, the keystroke handler fires, creates a high-priority update, and React switches to processing that update instead of resuming the interrupted low-priority work.
Without time slicing, priority-based scheduling would be meaningless. If React cannot pause, it cannot respond to priority changes. Time slicing is the foundational mechanism that makes concurrent rendering possible.
Measuring Time Slice Effectiveness
Chrome DevTools Performance panel visualizes time slicing clearly. Long synchronous tasks appear as single tall bars in the flame chart. Time-sliced work appears as many short bars with gaps between them — those gaps are where the browser paints and handles input. If you see long tasks (>50ms) in your React application, components within those tasks may be too expensive for a single fiber yield cycle.
Gotchas
- Time slicing does not help synchronous (urgent) updates. Discrete events like clicks trigger synchronous rendering that is not time-sliced. Only transition-priority and lower updates are eligible for time slicing.
- Individual slow components defeat time slicing. If one component takes 50ms to render, React cannot interrupt it mid-render. The entire component must complete before React can yield. Profile and optimize expensive components directly.
- Time slicing adds overhead. The scheduling machinery (posting messages, checking deadlines, maintaining work-in-progress state) has a cost. For very fast renders (<5ms total), time slicing adds unnecessary overhead. React's scheduler is designed to minimize this, but it is not zero.
- Effects are not time-sliced.
useEffectanduseLayoutEffectcallbacks run synchronously after commit. A heavy effect blocks the main thread just like any other synchronous code. Time slicing only applies to the render phase. - DevTools can mislead. React DevTools Profiler may show total render time without revealing how it was distributed across time slices. Use the browser's Performance panel for accurate frame-by-frame analysis.