Using ImGuiListClipper for Efficient Rendering of Large Lists in Dear ImGui

ImGuiListClipper is a virtualization helper that renders only the visible subset of large lists, preventing expensive CPU overhead when displaying thousands of items.

The ocornut/imgui repository provides this specialized class in imgui.cpp to solve performance bottlenecks in long scrollable regions. By calculating which rows intersect the current clipping rectangle, your application skips processing for off-screen items entirely.

How ImGuiListClipper Works Internally

Understanding the internal mechanics helps you debug edge cases and optimize integration with custom widgets.

State Allocation and Temporary Buffers

When you call Begin(), the clipper obtains transient storage from ImGuiContext::ClipperTempData at lines 3264‑3267. This buffer holds the clipping ranges, step counters, and seek offsets needed for the current frame. The system automatically returns this memory when the clipper goes out of scope, avoiding heap allocations during the render loop.

Height Measurement and Range Calculation

If you omit the itemHeight parameter in Begin(), the clipper forces measurement of the first item at lines 3328‑3336 to determine DisplayStart and DisplayEnd. Once the height is known, ImGuiListClipper_SortAndFuseRanges (lines 3269‑3285) merges overlapping visible blocks into coherent ImGuiListClipperRange structures defined in imgui_internal.h at lines 1889‑1897. Each range stores Min and Max indices representing contiguous visible segments.

Cursor Seeking and Iterative Stepping

Between visible blocks, ImGuiListClipper_SeekCursorAndSetupPrevLine (lines 3288‑3302) advances the ImGui cursor to the next visible region while preserving window state for scrolling and navigation. The public Step() method at lines 3562‑3566 wraps the internal ImGuiListClipper_StepInternal function, returning true while additional ranges remain. Finally, End() (lines 3545‑3550) positions the cursor after the last logical item to ensure subsequent UI elements layout correctly.

When to Use ImGuiListClipper

Implement the clipper when rendering lists that exceed the practical limits of immediate-mode submission:

  • Large static datasets – Lists containing 10,000+ entries with uniform row heights
  • Dynamic collections – Data streams where you can provide a constant itemHeight to Begin(itemsCount, itemHeight)
  • Complex containers – Tables (see the FIXME‑TABLE comment at line 3256) and tree views that require scrolling performance
  • Multi-select interfaces – Combining clipper iteration with ImGuiMultiSelectIO to maintain selection state across virtualization boundaries

Implementation Examples

Basic List Virtualization

This minimal example demonstrates the standard pattern for virtualizing a 20,000-item list:

int ItemsCount = 20000;
ImGuiListClipper clipper;
clipper.Begin(ItemsCount);  // Optional: clipper.Begin(ItemsCount, itemHeight);

while (clipper.Step())
{
    for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; ++i)
    {
        // Only visible items are submitted to ImGui
        ImGui::Text("Item %d", i);
    }
}

Integration with Multi-Select APIs

When combining with BeginMultiSelect, use IncludeItemByIndex() to ensure selection anchors remain visible:

int ItemsCount = 10000;
ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(ImGuiMultiSelectFlags_ClearOnEscape, 0, ItemsCount);

ImGuiListClipper clipper;
clipper.Begin(ItemsCount);

if (ms_io->RangeSrcItem != -1)
    clipper.IncludeItemByIndex((int)ms_io->RangeSrcItem);

while (clipper.Step())
{
    for (int n = clipper.DisplayStart; n < clipper.DisplayEnd; ++n)
    {
        char label[64];
        sprintf(label, "Object %05d", n);
        bool selected = /* your selection state */;
        
        ImGui::SetNextItemSelectionUserData(n);
        ImGui::Selectable(label, selected);
    }
}
ImGui::EndMultiSelect();

Clipping Inside Tables

The clipper functions correctly within table structures, as shown in imgui_demo.cpp at lines 3110‑3128:

if (ImGui::BeginTable("tbl", 2, ImGuiTableFlags_ScrollY))
{
    ImGuiListClipper clipper;
    clipper.Begin(ItemsCount);
    
    while (clipper.Step())
    {
        for (int n = clipper.DisplayStart; n < clipper.DisplayEnd; ++n)
        {
            ImGui::TableNextRow();
            ImGui::TableNextColumn();
            ImGui::Text("Row %d", n);
            ImGui::TableNextColumn();
            ImGui::Text("Data %d", n);
        }
    }
    ImGui::EndTable();
}

Summary

  • ImGuiListClipper virtualizes large lists by calculating visible ranges in ImGuiContext::ClipperTempData and only submitting those items to the draw pipeline.
  • Height measurement occurs automatically on the first step if you omit the itemHeight parameter in Begin().
  • Range fusion via ImGuiListClipper_SortAndFuseRanges handles fragmented visibility areas, while ImGuiListClipper_SeekCursorAndSetupPrevLine maintains correct cursor positioning.
  • Integration patterns include standalone lists, table rows, and multi-select operations using IncludeItemByIndex() for anchor preservation.
  • Source references are located in imgui.cpp (implementation), imgui_internal.h (range structures), and imgui_demo.cpp (usage examples).

Frequently Asked Questions

What is the minimum number of items to justify using ImGuiListClipper?

While the clipper functions correctly with any list size, it provides measurable performance benefits once you exceed 1,000 to 2,000 items depending on widget complexity. For fewer than 500 simple text rows, the overhead of range calculation may outweigh the savings, though the clipper remains safe to use.

Can ImGuiListClipper handle variable row heights?

No. The current implementation assumes uniform item heights to compute visibility ranges mathematically without measuring every row. If you pass a known height to Begin(), that value applies to all items. Variable heights require custom virtualization logic or manual calculation of DisplayStart/DisplayEnd outside the standard clipper API.

How does ImGuiListClipper interact with keyboard navigation?

The clipper preserves navigation state through ImGuiListClipper_SeekCursorAndSetupPrevLine, which updates window scrolling and focus regions. When using multi-select, call IncludeItemByIndex() on the RangeSrcItem to ensure the anchor item is submitted even if it falls outside the visible clipping rectangle, maintaining selection continuity.

Is ImGuiListClipper compatible with ImGui::BeginTable?

Yes. The clipper works inside tables using ImGuiTableFlags_ScrollY, as demonstrated in imgui_demo.cpp. The clipper iterates over row indices, and you call TableNextRow() within the Step() loop. A FIXME‑TABLE comment at line 3256 indicates ongoing refinements for complex table features, but the core virtualization is fully supported.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →