Logo
Published on

Concurrent Features

How React Concurrent Features Eliminate UI Blocking

React Concurrent Features prevent UI freezes during heavy rendering by making updates interruptible and prioritizing user interactions over low-priority work. Time-slicing breaks large updates into smaller chunks while startTransition marks updates that can be interrupted, keeping the interface responsive throughout complex operations.

TL;DR

  • Use startTransition for low-priority updates that can be interrupted
  • React yields control to browser for urgent user interactions
  • Prevents UI freezes during heavy rendering or state updates
  • Perfect for search filtering, data visualization, and large lists
const result = data.map((item) => item.value)

The UI Blocking Problem

Your React app becomes completely unresponsive during heavy state updates, with buttons frozen and scrolling stuttering while JavaScript processes large amounts of data.

// Problem: Synchronous updates block entire UI
function heavyUpdate() {
  const data = Array(10000).fill(0)
  console.log('Processing large dataset...')
  for (let i = 0; i < data.length; i++) {
    data[i] = Math.random() * 100
  }
  console.log('UI frozen during processing')
  console.log('Cannot click or scroll')
  console.log('Poor user experience')
  return data
}
const result = heavyUpdate()
console.log('Processed:', result.length)

Concurrent features keep the UI responsive by making expensive updates interruptible:

// Solution: Interruptible updates with transitions
function withTransition(callback) {
  console.log('Starting transition...')
  setTimeout(() => {
    console.log('UI stays responsive')
    callback()
  }, 0)
  return 'Processing in background'
}
const processAsync = () => {
  console.log('Heavy work in progress')
  console.log('User can still interact')
}
const status = withTransition(processAsync)
console.log('Status:', status)
console.log('UI never freezes')

Best Practises

Use concurrent features when:

  • ✅ Heavy computations block UI for more than 100ms
  • ✅ Users need immediate feedback while background work processes
  • ✅ Search, filtering, or sorting operations cause UI freezes
  • ✅ Large lists or complex visualizations block interactions

Avoid when:

  • 🚩 All state updates are fast and don't block the UI
  • 🚩 Operations must complete synchronously for correctness
  • 🚩 Simple apps with minimal state update complexity
  • 🚩 Team isn't familiar with React 18+ concurrent patterns

System Design Trade-offs

AspectConcurrent FeaturesSynchronous Updates
UI ResponsivenessExcellent - never blocksPoor - freezes during updates
User InteractionsAlways availableBlocked during heavy work
Perceived PerformanceSmooth - progressive updatesJanky - all-or-nothing
Frame RateStable 60fps maintainedDrops to 0fps during work
Priority ManagementSmart - urgent vs backgroundDumb - everything equal priority
Development ComplexityMedium - understand transitionsLow - simple state updates

More Code Examples

❌ Blocking updates
// Blocking updates freeze UI during processing
function SimulateBlockingSearch() {
  const dataset = Array(5000)
    .fill(0)
    .map((_, i) => ({
      id: i,
      title: `Item ${i}`,
      tags: [`tag${i % 10}`, `category${i % 5}`],
    }))
  function search(query) {
    console.log('Searching for:', query)
    const startTime = Date.now()
    const results = dataset.filter((item) => {
      return item.title.includes(query) || item.tags.some((tag) => tag.includes(query))
    })
    const endTime = Date.now()
    console.log(`Search took: ${endTime - startTime}ms`)
    console.log('UI frozen during search')
    console.log(`Found ${results.length} results`)
    return results
  }
  // Simulate user typing
  ;['I', 'It', 'Ite', 'Item'].forEach((query) => {
    console.log('User types:', query)
    search(query)
    console.log('Cannot type next character until done')
  })
  console.log('Problem: Laggy search experience')
  return 'Search completed'
}
const result = SimulateBlockingSearch()
console.log(result)
// Output shows UI blocking
✅ Non-blocking updates
// Non-blocking updates with transitions
function SimulateNonBlockingSearch() {
  const dataset = Array(5000)
    .fill(0)
    .map((_, i) => ({
      id: i,
      title: `Item ${i}`,
      tags: [`tag${i % 10}`],
    }))
  const searchQueue = []
  let processing = false
  function searchWithTransition(query) {
    console.log('Queueing search for:', query)
    searchQueue.push(query)
    if (!processing) {
      processing = true
      processQueue()
    }
    return 'Search queued'
  }
  function processQueue() {
    if (searchQueue.length === 0) {
      processing = false
      return
    }
    const query = searchQueue.shift()
    console.log('Processing search:', query)
    // Simulate async processing
    setTimeout(() => {
      const results = dataset.filter((item) => item.title.includes(query))
      console.log(`Found ${results.length} results`)
      console.log('UI remained responsive')
      processQueue() // Process next
    }, 10)
  }
  // Simulate rapid typing
  ;['I', 'It', 'Ite', 'Item'].forEach((query) => {
    console.log('User types:', query)
    searchWithTransition(query)
    console.log('Can type immediately')
  })
  console.log('Result: Smooth search experience')
  return 'All searches queued'
}
const result = SimulateNonBlockingSearch()
console.log(result)
// Output shows responsive UI

Technical Trivia

The Notion Editor Lag Crisis of 2022: Notion's web editor became notorious for freezing during large document edits, where typing a single character could freeze the interface for 2-3 seconds while the application recalculated document structure, updated references, and synchronized collaborative edits across all connected clients.

Why synchronous updates failed: Every keystroke triggered immediate synchronous processing of the entire document structure, blocking all user interactions including scrolling and clicking. Users couldn't continue typing, navigate away, or even cancel operations while the app processed changes, creating an unusable editing experience.

Concurrent features rescued Notion's editor: Implementing startTransition for document processing and useDeferredValue for layout calculations kept the editor responsive while background work processed. Users can now type smoothly while formatting, cross-references, and synchronization happen in the background without blocking interactions.


Master React Concurrent Features: Implementation Strategy

Use startTransition for any state updates that trigger heavy rendering, especially filtering and sorting operations. Combine with useDeferredValue for input-driven updates and useTransition's isPending flag to provide visual feedback during background work.