# How to Use ImGuiListClipper for Efficient Large List Rendering in Dear ImGui

> Learn to use ImGuiListClipper for efficient large list rendering in Dear ImGui. Reduce CPU overhead from O(N) to O(V) for massive datasets with this essential helper class.

- Repository: [omar/imgui](https://github.com/ocornut/imgui)
- Tags: how-to-guide
- Published: 2026-07-23

---

**`ImGuiListClipper` is a helper class that renders only visible items in scrolling regions, reducing CPU overhead from O(N) to O(V) when displaying thousands or millions of rows.**

The `ImGuiListClipper` struct, declared in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h), eliminates the performance bottleneck of iterating entire datasets by calculating which rows intersect the current viewport. This optimization is essential for maintaining frame rates in data-heavy applications built with the Dear ImGui library.

## Core API and Data Structures

The public interface resides in **[imgui.h](/ocornut/imgui/blob/master/imgui.h#L2894-L2921)** where `ImGuiListClipper` exposes three primary methods and output indices:

- **`Begin(int items_count, float items_height = -1.0f)`** – Initializes the clipper with the total number of items and an optional height estimate.
- **`Step()`** – Advances the internal state and populates `DisplayStart` and `DisplayEnd` with the visible range indices. Returns `true` while processing continues.
- **`End()`** – Resets internal buffers; called automatically by the destructor.

The internal bookkeeping leverages structures defined in **[imgui_internal.h](/ocornut/imgui/blob/master/imgui_internal.h#L1685-L1712)**:

- **`ImGuiListClipperRange`** (lines 1689–1699) – Stores minimum and maximum row indices for clipping ranges.
- **`ImGuiListClipperData`** (lines 1702–1712) – Maintains step counters, lossiness offsets, and temporary buffers used during the visibility calculation.

The actual algorithm implementation lives in **[imgui.cpp](/ocornut/imgui/blob/master/imgui.cpp)**, specifically within the `ImGuiListClipper::Begin`, `Step`, and `End` method definitions.

## Basic Implementation Pattern

The standard integration follows a three-phase workflow: initialization, stepped rendering, and cleanup.

```cpp
ImGuiListClipper clipper;
clipper.Begin(items_count);
while (clipper.Step())
{
    for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
    {
        // Only executed for visible rows
        ImGui::Text("Item %d", i);
    }
}
clipper.End();  // Optional; destructor handles this

```

**Key variables:**
- **`DisplayStart`** – First visible row index (inclusive).
- **`DisplayEnd`** – Last visible row index (exclusive).

This pattern ensures that expensive per-item operations—such as string formatting, texture binding, or complex widget logic—execute exclusively for rows actually drawn to the screen.

## Handling Variable Heights and Unknown Counts

`ImGuiListClipper` adapts to dynamic content through optional parameters and special constants.

**Variable item heights:**
Pass an estimated height to `Begin()` to improve initial frame accuracy. The clipper refines this estimate automatically after the first render:

```cpp
clipper.Begin(items_count, 20.0f);  // Approximate 20px per row

```

**Unknown or infinite lists:**
When the total count is unavailable (e.g., streaming data), pass `INT_MAX` and break manually when data ends:

```cpp
clipper.Begin(INT_MAX);
while (clipper.Step())
{
    for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
    {
        if (i >= actual_data_size) break;
        ImGui::Text("Stream item %d", i);
    }
}

```

## Integration with Tables

When used inside `BeginTable`, the clipper automatically synchronizes with table row counters unless you specify `ImGuiListClipperFlags_NoSetTableRowCounters`.

```cpp
if (ImGui::BeginTable("LargeTable", 3, ImGuiTableFlags_ScrollY))
{
    ImGuiListClipper clipper;
    clipper.Begin(row_count);
    while (clipper.Step())
    {
        for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; row++)
        {
            ImGui::TableNextRow();
            ImGui::TableSetColumnIndex(0);
            ImGui::Text("Row %d", row);
            ImGui::TableSetColumnIndex(1);
            ImGui::Text("%s", data[row].name);
        }
    }
    ImGui::EndTable();
}

```

The clipper calls `TableNextRow()` internally for each visible index, maintaining correct table state without manual intervention.

## Performance Characteristics

The algorithm transforms list rendering complexity from **O(N)**—where N is the total item count—to **O(V)**, where V represents only the visible rows. This distinction becomes critical when N exceeds 10,000 items, as unclipped loops waste CPU cycles on invisible geometry and draw calls.

For optimal results, wrap clipped content within `BeginChild` to isolate scrolling regions, or ensure the parent window has scrolling enabled so `ImGuiListClipper` can calculate accurate viewport bounds.

## Summary

- **`ImGuiListClipper`** lives in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h) (lines 2894–2921) and reduces rendering overhead by skipping invisible rows.
- Use **`Begin()`**, **`Step()`**, and **`End()`** to process only visible indices stored in **`DisplayStart`** and **`DisplayEnd`**.
- Pass height estimates for variable-height items, or **`INT_MAX`** for unknown/unbounded lists.
- Internal structures in [`imgui_internal.h`](https://github.com/ocornut/imgui/blob/main/imgui_internal.h) manage clipping ranges via **`ImGuiListClipperRange`** and **`ImGuiListClipperData`**.
- The clipper automatically handles table row synchronization when used with `BeginTable`.

## Frequently Asked Questions

### When should I use ImGuiListClipper instead of a standard for-loop?

Use `ImGuiListClipper` whenever your list exceeds approximately 1,000 items or causes frame rate drops. Standard loops process every item regardless of visibility, while the clipper restricts processing to the subset currently visible in the scrolling viewport, eliminating wasted CPU and GPU resources on off-screen elements.

### How does ImGuiListClipper handle rows with different heights?

The clipper accepts an optional `items_height` parameter in `Begin()`. If you provide an estimate, it uses that for initial calculations; after the first frame, it measures actual rendered heights to refine the visibility ranges for subsequent frames. If heights vary significantly, ensure you pass a reasonable average to minimize first-frame inaccuracy.

### Can I use ImGuiListClipper with Dear ImGui tables?

Yes. When placed inside a `BeginTable`/`EndTable` block, `ImGuiListClipper` automatically calls `TableNextRow()` for each visible index, keeping the table's internal row counters synchronized. You can disable this behavior by passing the `ImGuiListClipperFlags_NoSetTableRowCounters` flag to `Begin()` if you need manual control over row advancement.

### What happens if I forget to call End() on the clipper?

Nothing catastrophic. The `ImGuiListClipper` destructor automatically invokes `End()`, releasing internal memory stored in the `ImGuiListClipperData` structure. However, explicit `End()` calls improve code clarity and allow immediate reuse of the clipper instance without destruction.