# Optimizing Dear ImGui Performance with Large UI Hierarchies: Best Practices Guide

> Discover best practices for optimizing Dear ImGui performance with large UI hierarchies. Learn to use ImGuiListClipper, virtualize trees, and reduce CPU costs for smoother UIs.

- Repository: [omar/imgui](https://github.com/ocornut/imgui)
- Tags: best-practices
- Published: 2026-07-25

---

**Use `ImGuiListClipper` to cull invisible items, combine with table-specific clipping rectangles, and virtualize tree structures so CPU costs remain proportional to visible pixels rather than total item count.**

Dear ImGui (ocornut/imgui) is designed for high-performance immediate-mode UI, but submitting thousands of widgets per frame—common in long lists, deep trees, or massive tables—can dominate your CPU budget. When optimizing Dear ImGui performance with large UI hierarchies, the architectural solution is **early culling**: leveraging built-in clipping mechanisms that discard off-screen elements before they reach the draw list.

## Coarse-Grained Clipping with ImGuiListClipper

The `ImGuiListClipper` class is the primary tool for scaling homogeneous lists. It calculates which rows intersect the current window clip rectangle and yields only those indices, reducing widget creation from O(N) to O(V) where V is the visible row count.

### Implementation in imgui.cpp

The full implementation resides in **[`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp)** (lines 3327–3577), where the clipper estimates item heights and steps through visible ranges. The public API in **[`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h)** (lines 2867–2901) provides `Begin()`, `Step()`, and `End()` methods, while internal structures like `ImGuiListClipperRange` are defined in **[`imgui_internal.h`](https://github.com/ocornut/imgui/blob/main/imgui_internal.h)** (lines 1689–1711).

### Basic Usage Pattern

Create a clipper each frame, initialize it with the total item count, and loop while `Step()` returns `true`. Only emit widgets for indices between `DisplayStart` and `DisplayEnd`:

```cpp
ImGuiListClipper clipper;
clipper.Begin(static_cast<int>(items.size()));
while (clipper.Step())
{
    for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
        ImGui::Text("%s", items[i].c_str());
}

```

This pattern eliminates the cost of creating invisible widgets while preserving random access to your underlying data container.

## Optimize Large Tables with Built-in Clipping

ImGui tables maintain two clipping rectangles in **[`imgui_tables.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_tables.cpp)** (lines 159–172): `HostClipRect` (the outer window bounds) and `InnerClipRect` (the region after scrolling and frozen columns). The table system automatically discards rows outside these rectangles, but you should combine this with `ImGuiListClipper` for row submission:

```cpp
ImGui::BeginTable("HugeTable", 5, ImGuiTableFlags_ScrollY);
ImGuiListClipper clipper;
clipper.Begin(row_count);
while (clipper.Step())
{
    for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; row++)
    {
        ImGui::TableNextRow();
        for (int col = 0; col < 5; col++)
        {
            ImGui::TableSetColumnIndex(col);
            ImGui::Text("R%dC%d", row, col);
        }
    }
}
ImGui::EndTable();

```

This dual-layer approach ensures column decorations (borders, backgrounds) draw only for visible areas while the clipper skips off-screen row widgets entirely.

## Virtualize Deep Tree Hierarchies

Deep tree structures (file explorers, scene graphs) require flattening visible nodes into a linear array. Maintain a separate data structure for parent-child relationships, then rebuild a flat "visible only" list when expansion states change. Feed this list to `ImGuiListClipper` each frame.

The **[`imgui_demo.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_demo.cpp)** file demonstrates this pattern around lines 4268–4295 under the "Tree nodes with large numbers" section. The key is separating tree logic from UI submission:

```cpp
// Flatten visible nodes (called only when state changes)
void FlattenTree(const TreeNode& node, int depth, std::vector<std::pair<const TreeNode*,int>>& out)
{
    out.emplace_back(&node, depth);
    if (node.opened)
        for (const auto& child : node.children)
            FlattenTree(child, depth+1, out);
}

// UI submission using clipper
void ShowVirtualTree(const TreeNode& root)
{
    static std::vector<std::pair<const TreeNode*,int>> visible;
    visible.clear();
    FlattenTree(root, 0, visible);

    ImGuiListClipper clipper;
    clipper.Begin(static_cast<int>(visible.size()));
    while (clipper.Step())
    {
        for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
        {
            const TreeNode* n = visible[i].first;
            int depth = visible[i].second;
            ImGui::Indent(depth * ImGui::GetStyle().IndentSpacing);
            ImGui::TreeNodeEx(n->label.c_str(), 
                            n->children.empty() ? ImGuiTreeNodeFlags_Leaf : 0);
            ImGui::Unindent(depth * ImGui::GetStyle().IndentSpacing);
        }
    }
}

```

## Minimize Per-Item Work

Even with clipping, visible items incur overhead. Follow these guidelines from the source architecture:

- **Cache expensive calculations** outside the UI loop (e.g., pre-format strings, store texture IDs) to avoid repeated layout calculations.
- **Prefer simple widgets** (`Text`, `Bullet`, `Selectable`) for bulk rows; avoid heavyweight controls like `InputText` unless necessary.
- **Avoid nested child windows** when possible; each child adds its own clipping stack and draw-list command buffer.
- **Batch draw calls** by keeping texture switches minimal—ImGui automatically merges consecutive commands using the same texture.

## Constrain Window Sizes and Use Child Regions

Limit maximum window dimensions with `SetNextWindowSizeConstraints` to prevent unnecessarily large scrollable areas:

```cpp
ImGui::SetNextWindowSizeConstraints(ImVec2(0,0), ImVec2(800,600));
ImGui::Begin("ConstrainedWindow");

```

For scrollable sub-regions, use `BeginChild` to create dedicated `ImGuiWindow` instances with their own clip rectangles, allowing the clipper to operate on smaller viewports:

```cpp
ImGui::BeginChild("ScrollingRegion", ImVec2(0,0), true);
    // Large list with ImGuiListClipper here
ImGui::EndChild();

```

## Validate with Built-in Profiling

Verify your optimizations using the Metrics/Debugger window implemented in **[`imgui_demo.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_demo.cpp)** (lines 6460–6475). This tool displays draw command counts, clipped item statistics, and frame timing.

When manually adjusting `ClipRect` (for custom overlays), follow the save/restore pattern shown in **[`imgui_widgets.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_widgets.cpp)** (lines 6964–6970) to ensure the clip rectangle remains valid for subsequent items.

## Summary

- **Use `ImGuiListClipper`** in [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp) for any homogeneous list to reduce widget creation to visible items only.
- **Combine table clipping** (`HostClipRect`/`InnerClipRect` in [`imgui_tables.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_tables.cpp)) with the clipper for massive tables.
- **Virtualize trees** by flattening visible nodes and feeding the array to a clipper, as demonstrated in [`imgui_demo.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_demo.cpp).
- **Cache calculations** and use simple widgets to minimize per-item CPU work.
- **Profile** with the built-in metrics window to confirm draw-list reductions and validate your clipping strategy.

## Frequently Asked Questions

### What is ImGuiListClipper and when should I use it?

`ImGuiListClipper` is a helper class in **[`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp)** that optimizes rendering of large, uniformly-sized lists by calculating which items are visible within the current scroll region. Use it whenever displaying more than a few hundred rows of data (lists, tables, console logs) to keep frame costs proportional to screen size rather than data size.

### How do I optimize large tables in Dear ImGui?

Combine `ImGuiListClipper` with the table API. The table system in **[`imgui_tables.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_tables.cpp)** automatically manages `InnerClipRect` for column clipping, while the clipper skips row submission for off-screen areas. Always specify `ImGuiTableFlags_ScrollY` for vertical scrolling and wrap row generation inside the clipper's `Step()` loop.

### Can ImGui handle thousands of tree nodes efficiently?

Yes, by virtualizing the hierarchy. Maintain your tree structure separately from the UI, flatten only the visible (expanded) nodes into a linear array, and pass that array to `ImGuiListClipper`. The **[`imgui_demo.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_demo.cpp)** file contains a reference implementation showing how to handle large trees without traversing collapsed branches.

### How do I verify that clipping optimizations are working?

Open the Metrics/Debugger window from the demo to inspect draw command counts and clipper statistics. Additionally, check **[`imgui_widgets.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_widgets.cpp)** (lines 6964–6970) for the `ClipRect` save/restore pattern when implementing custom clipping, ensuring you do not leave the draw list in an invalid state that could disable culling.