# How to Optimize ImGui Performance for Large Lists Using ImGuiListClipper

> Optimize ImGui performance for large lists with ImGuiListClipper. Render thousands of rows efficiently and reduce CPU overhead by only processing visible items.

- Repository: [omar/imgui](https://github.com/ocornut/imgui)
- Tags: performance
- Published: 2026-07-15

---

**ImGuiListClipper is a helper class that reduces CPU overhead by computing only the visible range of items in long lists, letting you render thousands of rows without per-frame iteration costs.**

Dear ImGui (ocornut/imgui) provides the `ImGuiListClipper` utility to solve performance bottlenecks when displaying massive datasets. Instead of submitting every list item to the rendering pipeline each frame, this class calculates the visible viewport and skips invisible elements entirely. This optimization is essential for log viewers, file browsers, and large tables that would otherwise cause frame drops.

## How ImGuiListClipper Works Internally

### Range Calculation via Begin()

In [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h) (lines 2876-2892), the `ImGuiListClipper` class defines the `Begin()` method that initiates the clipping calculation. When you call `clipper.Begin(items_count, item_height)`, the clipper queries the current window’s clipping rectangle and calculates the indices for `DisplayStart` and `DisplayEnd`—the first and last items that can possibly be visible within the scroll region.

### Step-Wise Iteration

The clipper exposes a `while (clipper.Step())` loop that advances an internal step counter, as implemented in [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp) (lines 1295-1319). Each call to `Step()` updates the display range for the current batch, enabling the clipper to handle variable-height items or tables using `ImGuiListClipperFlags_NoSetTableRowCounters`.

### Automatic Cursor Positioning

After drawing the visible subset, the clipper internally advances the window cursor using `ImGui::Dummy()` to account for skipped items. This ensures scrollbars report the correct total height, as seen in [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp) (lines 1320-1332) within the cleanup logic.

### Table Integration

When used inside `BeginTable/EndTable` blocks, the clipper respects the `ImGuiListClipperFlags_NoSetTableRowCounters` flag defined in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h) (lines 2852-2854). This prevents corruption of table row indices during custom row handling, making it safe for complex table layouts.

## When to Use ImGuiListClipper

Use the clipper when you need to **optimize ImGui performance for large lists** in these scenarios:

- **Lists with thousands of entries** (e.g., log windows, file browsers) to avoid iterating over every element each frame
- **Tables with many rows** to reduce the number of `TableNextRow` calls and draw commands for off-screen rows
- **Dynamic data where the total count is known** so you can pass the exact `items_count` to `Begin()` and let the clipper handle unknown heights efficiently

## Implementation Best Practices

- **Provide a stable item height**: Pass an explicit `item_height` to `Begin()` if all rows share the same height. This lets the clipper compute ranges without scanning the first item.
- **Handle variable heights carefully**: Set `ImGuiListClipperFlags_NoSetTableRowCounters` and manually handle per-item offsets when mixing clipping with variable-height items.
- **Maintain consistent spacing**: Combine with `ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ...)` to keep item heights predictable when changing spacing.

## Code Examples

### Basic List with Selectable Items

```cpp
void ShowLargeList(const std::vector<std::string>& items)
{
    ImGuiListClipper clipper;
    clipper.Begin(static_cast<int>(items.size()));
    while (clipper.Step())
    {
        for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; ++i)
        {
            ImGui::Selectable(items[i].c_str());
        }
    }
    // No need to call clipper.End(); destructor does it automatically
}

```

### High-Performance Table with Fixed Row Height

```cpp
void ShowHugeTable()
{
    const int rows = 50000;
    ImGui::BeginTable("BigTable", 3, ImGuiTableFlags_Borders);
    ImGuiListClipper clipper;
    clipper.Begin(rows, 20.0f);          // explicit item height
    while (clipper.Step())
    {
        for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row)
        {
            ImGui::TableNextRow();
            ImGui::TableSetColumnIndex(0); ImGui::Text("Row %d – Col 0", row);
            ImGui::TableSetColumnIndex(1); ImGui::Text("Col 1");
            ImGui::TableSetColumnIndex(2); ImGui::Text("Col 2");
        }
    }
    ImGui::EndTable();
}

```

### Variable Height Tables with Custom Flags

```cpp
void ShowVariableHeightTable()
{
    const int rows = 2000;
    ImGui::BeginTable("VarHeight", 2);
    ImGuiListClipper clipper;
    clipper.Begin(rows, -1.0f);                     // let ImGui infer height from first row
    clipper.Flags |= ImGuiListClipperFlags_NoSetTableRowCounters;

    while (clipper.Step())
    {
        for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row)
        {
            ImGui::TableNextRow( (row % 10) ? 0.0f : 30.0f ); // every 10th row taller
            ImGui::TableSetColumnIndex(0); ImGui::Text("Row %d", row);
            ImGui::TableSetColumnIndex(1); ImGui::Text("Data");
        }
    }
    ImGui::EndTable();
}

```

## Summary

- **ImGuiListClipper** eliminates CPU bottlenecks by calculating visible ranges via `Begin()` and iterating only over visible items using `Step()`.
- The clipper automatically handles scrollbar positioning and cursor advancement for skipped items.
- Pass explicit `item_height` values when possible to avoid inference overhead, and use `ImGuiListClipperFlags_NoSetTableRowCounters` for variable-height tables.
- Reference implementations are available in [`imgui_demo.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_demo.cpp) (lines 1870-1900) and internal data structures in [`imgui_internal.h`](https://github.com/ocornut/imgui/blob/main/imgui_internal.h) (lines 1688-1710).

## Frequently Asked Questions

### Do I need to call End() on ImGuiListClipper?

No. The `ImGuiListClipper` destructor automatically handles cleanup, as implemented in [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp). The `End()` method is called internally when the clipper goes out of scope or when `Step()` completes the final iteration.

### Can ImGuiListClipper handle items with different heights?

Yes, but with limitations. If items have variable heights, omit the `item_height` parameter (or pass `-1.0f`) to let the clipper infer height from the first item. For tables with variable row heights, you must set `ImGuiListClipperFlags_NoSetTableRowCounters` to prevent the clipper from corrupting table row counters.

### Why is my scrollbar size incorrect when using the clipper?

This typically occurs when the total item count passed to `Begin()` does not match your actual data size, or when item heights vary without proper flag configuration. The clipper uses `ImGui::Dummy()` in [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp) (lines 1320-1332) to advance the cursor for skipped items, ensuring the scrollbar reflects the total virtual height.

### How do I integrate ImGuiListClipper with ImGui::BeginTable?

Place the clipper inside your `BeginTable/EndTable` block. Call `clipper.Begin(rows, row_height)` after `BeginTable()`, then iterate `TableNextRow()` only for indices between `clipper.DisplayStart` and `clipper.DisplayEnd`. For tables with custom row handling, set `clipper.Flags |= ImGuiListClipperFlags_NoSetTableRowCounters` as defined in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h) (lines 2852-2854).