Skip to content

Batch Updates and Batching — Complete Guide

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Batch Updates and Batching. We cover key concepts, practical examples, and best practices to help you master this topic.

Batch updates group multiple state changes into a single DOM update cycle, reducing reflows and ensuring consistent UI state without intermediate flickering.

What You'll Learn

  • What batching is and why frameworks use it
  • How synchronous and asynchronous batching differ
  • How React batches state updates
  • How to implement your own batching system

Why It Matters

Without batching, multiple state changes trigger multiple re-renders and DOM updates, causing jank, flickering, and wasted computation. Batching is essential for smooth UI performance.

flowchart LR
  A[State change 1] --> B[Batching Queue]
  C[State change 2] --> B
  D[State change 3] --> B
  B --> E[Single reconciliation]
  E --> F[Single DOM update]
  F --> G[Single paint]

The Problem Batching Solves

Multiple state changes without batching cause cascading re-renders.

// Without batching: 3 separate re-renders
class UnbatchedComponent {
    updateAll() {
        this.setState({ a: 1 });  // Triggers re-render 1
        this.setState({ b: 2 });  // Triggers re-render 2
        this.setState({ c: 3 });  // Triggers re-render 3
        // After 3 re-renders and 3 DOM updates, browser paints once
        // But the component was built 3 times unnecessarily
    }
}

// With batching: 1 re-render
class BatchedComponent {
    updateAll() {
        // All three setState calls are queued
        this.setState({ a: 1 });
        this.setState({ b: 2 });
        this.setState({ c: 3 });
        // At the end of the microtask, ONE re-render happens
        // Component built once, DOM updated once, paint once
    }
}

How React Batches Updates

React batches state updates in event handlers and lifecycle methods. Outside those, updates are not batched (before React 18).

import React, { useState } from 'react';

function BatchedExample() {
    const [count, setCount] = useState(0);
    const [text, setText] = useState('');

    function handleClick() {
        // Inside event handler: BATCHED (React 18+)
        setCount(c => c + 1);        // Queued
        setCount(c => c + 1);        // Queued
        setCount(c => c + 1);        // Queued
        setText('updated');          // Queued
        // All four updates are batched into ONE re-render
        // count goes from 0 to 3 in a single render
    }

    function handleAsyncUpdate() {
        // Before React 18, setTimeout did NOT batch
        // In React 18+, setTimeout is also batched
        setTimeout(() => {
            setCount(c => c + 1);
            setText('async update');
            // React 18+: batched into one re-render
            // React 17: two separate re-renders
        }, 100);
    }

    return (
        <button onClick={handleClick}>
            {count} - {text}
        </button>
    );
}

Implementing a Simple Batching System

You can build your own batching mechanism using microtasks.

class BatchingFramework {
    constructor() {
        this._batchQueue = new Set();
        this._isBatching = false;
        this._pendingCallback = null;
    }

    // Called by components when their state changes
    scheduleUpdate(component) {
        this._batchQueue.add(component);

        if (!this._isBatching) {
            this._isBatching = true;
            // Schedule batch processing at the end of current task
            this._pendingCallback = Promise.resolve().then(() => {
                this._flushBatch();
            });
        }
    }

    _flushBatch() {
        // Process all queued updates
        const components = Array.from(this._batchQueue);
        this._batchQueue.clear();
        this._isBatching = false;

        // Build new virtual trees for all dirty components
        const allPatches = [];
        components.forEach(component => {
            const newTree = component.render();
            const oldTree = component._vdom;
            const patches = this._diff(oldTree, newTree);
            allPatches.push({ component, patches });
            component._vdom = newTree;
        });

        // Apply all patches in a single batch
        // The browser will see all DOM changes at once
        allPatches.forEach(({ component, patches }) => {
            this._commit(component.container, patches);
        });

        // After this microtask, the browser paints
        // All DOM changes are visible in a single frame
    }

    _diff(oldTree, newTree) {
        // Simplified implementation
        if (!oldTree) return { type: 'CREATE', node: newTree };
        if (!newTree) return { type: 'DELETE' };
        if (oldTree.type !== newTree.type) return { type: 'REPLACE', node: newTree };
        return { type: 'UPDATE', props: this._diffProps(oldTree.props, newTree.props), children: this._diffChildren(oldTree.children, newTree.children) };
    }

    _commit(container, patches) {
        // Apply patches to the DOM
        // All mutations happen synchronously here
    }
}

Synchronous vs Asynchronous Batching

Different frameworks use different batching strategies.

// Synchronous batching (Vue 3):
// Updates are queued and flushed in the next microtask
// This ensures the DOM update happens asynchronously

// Vue uses a nextTick-based approach:
const queue = [];
let isFlushing = false;

function queueJob(job) {
    if (!queue.includes(job)) {
        queue.push(job);
    }
    if (!isFlushing) {
        isFlushing = true;
        nextTick(flushJobs);
    }
}

function flushJobs() {
    // Sort jobs by component depth (parent before child)
    queue.sort((a, b) => a.id - b.id);
    const copy = queue.slice();
    queue.length = 0;

    for (const job of copy) {
        job();  // Execute each job synchronously
    }
    isFlushing = false;
}

// React 18 uses automatic batching:
// All updates within the same event handler,
// timeout, promise, or native event are batched.
// This is always asynchronous (deferred to microtask).

flushSync — Opting Out of Batching

Sometimes you need synchronous DOM updates. React provides flushSync for this.

import { flushSync } from 'react-dom';

function SyncUpdates() {
    const [count, setCount] = useState(0);
    const [flag, setFlag] = useState(false);

    function handleSyncClick() {
        // Force synchronous, unbatched update
        flushSync(() => {
            setCount(c => c + 1);
        });
        // DOM is updated here. count element shows new value.
        // Browser may have already recalculated styles.

        // This second update is also synchronous
        flushSync(() => {
            setFlag(f => !f);
        });
        // DOM is updated again. Two separate renders, two paints.
    }

    return <button onClick={handleSyncClick}>{count}</button>;
}

Common Mistakes

  1. Expecting all state updates to be batched automatically outside event handlers (pre-React 18).
  2. Reading DOM state between batched updates and getting stale values.
  3. Using flushSync excessively, negating the benefits of batching.
  4. Assuming batching guarantees a single paint (the browser may still coalesce multiple paints).
  5. Mutating state before batching completes, causing inconsistent UI state.

Practice Questions

  1. What is batching in Virtual Dom? Grouping multiple state changes into a single re-render and DOM update.
  2. How does React 18 handle batching? It batches all updates automatically, including setTimeout and promises.
  3. What is the benefit of batching? Fewer re-renders, fewer DOM operations, fewer paints, better performance.
  4. How do you opt out of batching in React? Use flushSync for synchronous, unbatched updates.

Challenge

Build a batching visualizer. Create a component with three independent counters. Add buttons that update all three at once with and without batching. Show a timeline of when each re-render and DOM update occurs. Measure and display the total time difference.

FAQ

What is batching in Virtual DOM?

Batching groups multiple state changes together and processes them in a single render cycle, avoiding unnecessary intermediate DOM updates.

Does React batch updates automatically?

Yes, React 18+ batches all updates automatically. Earlier versions only batched inside event handlers.

What is flushSync in React?

flushSync forces synchronous, unbatched DOM updates. Use it sparingly when you need to read DOM state immediately after an update.

Does Vue use batching?

Yes. Vue queues updates and flushes them asynchronously in the next microtask, similar to React.

Can batching cause stale state reads?

Yes. Reading state between a queued update and the batch flush returns the old value. Use functional updates for reliable state reads.

Mini Project

Build a performance debugging tool that wraps a component and logs every state update, showing whether it was batched. Display a real-time counter of batched vs unbatched updates. Use it to profile a complex form component and identify unnecessary re-renders.

What's Next

Lesson 7: Fiber Architecture

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro