# How to Use ImGuiListClipper for Efficient Virtual Scrolling Lists

> Learn to use ImGuiListClipper for efficient virtual scrolling lists in your ImGui applications. Drastically reduce ImGui calls from millions to dozens per frame by rendering only visible items.

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

---

**Use `ImGuiListClipper` to render massive lists by calculating only the visible index range and iterating through `DisplayStart` to `DisplayEnd`, reducing ImGui calls from millions to dozens per frame.**

Dear ImGui’s `ImGuiListClipper` is a helper class designed for high-performance rendering of large datasets in the `ocornut/imgui` library. It implements **virtual scrolling** by clipping items outside the current viewport, allowing you to display thousands or millions of entries without performance degradation. This article explains the exact implementation pattern used in the official source code.

## Why Use ImGuiListClipper?

Traditional list rendering calls ImGui functions for every item, creating CPU bottlenecks when handling large datasets. The clipper solves this through three core mechanisms:

- **Rendering efficiency** – Only processes items intersecting the current clipping rectangle, typically reducing draw calls from millions to a few dozen per frame.
- **Memory agility** – Keeps your data in any container (vector, array, or database) while accessing only the visible subset during the render loop.
- **Seamless integration** – Works with standard ImGui widgets like `Text` and `Selectable`, automatically managing cursor position restoration between steps.

## How ImGuiListClipper Works

The clipper uses an iterator-style interface that calculates visible ranges based on the current scroll position and window dimensions. The implementation spans [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp) (lines 3327-3610) and relies on internal structures defined in [`imgui_internal.h`](https://github.com/ocornut/imgui/blob/main/imgui_internal.h) (lines 1689-1704).

### Step 1: Initialize the Clipper

Create an instance of `ImGuiListClipper` on the stack. The destructor automatically calls `End()` to clean up temporary buffers and restore cursor state, as implemented in [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp) lines 3327-3335.

```cpp
ImGuiListClipper clipper;

```

### Step 2: Begin the Clipping Session

Call `Begin()` to initialize the clipping calculation. In [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp) (lines 3377-3390), this method sets up the clipper data and calculates initial positioning.

```cpp
clipper.Begin(item_count, item_height);

```

- **`item_count`** – Total number of items in your dataset. Pass `INT_MAX` if the count is unknown upfront.
- **`item_height`** – Height of each row in pixels. Use `-1.0f` to let the clipper infer height from the first visible row.

### Step 3: Iterate with Step()

The `Step()` method (implemented in [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp) lines 3573-3610) updates `DisplayStart` and `DisplayEnd` indices for the current frame. It returns `true` while items remain to process.

```cpp
while (clipper.Step())
{
    // Render loop here
}

```

This method handles height inference, range calculation, and clipping rectangle intersection tests automatically.

### Step 4: Render Visible Items

Inside the `Step()` loop, render only items between `clipper.DisplayStart` and `clipper.DisplayEnd`:

```cpp
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
{
    ImGui::Text("%04d: %s", i, items[i].c_str());
}

```

## Complete Usage Example

This pattern from [`imgui_demo.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_demo.cpp) demonstrates the standard implementation for fixed-height lists:

```cpp
// items is a std::vector<std::string> or similar container
ImGuiListClipper clipper;
clipper.Begin(items.size());  // item_height = -1.0f (auto-detect)

while (clipper.Step())
{
    for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
    {
        ImGui::Text("%04d: %s", i, items[i].c_str());
    }
}
// clipper.End() called automatically by destructor

```

## Advanced Techniques

### Handling Variable-Height Rows

For lists where row heights vary, pass a known average height to `Begin()` or use `IncludeItemsByIndex()` to specify exact ranges. The `IncludeItemsByIndex()` method (defined in the [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp) implementation block) allows you to force specific indices into the visible set when height prediction is inaccurate.

### Using with ImGui Tables

When integrating with tables, add the `ImGuiListClipperFlags_NoSetTableRowCounters` flag to prevent conflicts between the clipper's row tracking and table row advancement. This flag is processed in the clipper's step logic (referenced around [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp) lines 3178-3182).

```cpp
clipper.Begin(row_count, row_height, ImGuiListClipperFlags_NoSetTableRowCounters);

```

### Indeterminate Item Counts

If your data source is streaming or paginated, initialize with `INT_MAX` and later call `SeekCursorForItem()` after the loop to position the cursor for subsequent UI elements:

```cpp
clipper.Begin(INT_MAX);
while (clipper.Step())
{
    // Render known items
}
clipper.SeekCursorForItem(known_item_count);  // Position for footer

```

## Implementation Details

The `ImGuiListClipper` class is declared in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h) (lines 196-210) and relies on `ImGuiListClipperData` and `ImGuiListClipperRange` structures defined in [`imgui_internal.h`](https://github.com/ocornut/imgui/blob/main/imgui_internal.h) (lines 1689-1704). The `Step()` method performs binary searches to locate visible ranges efficiently, while `Begin()` handles initialization of temporary draw data stored in the window's clipper stack.

Key methods in [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp):
- **`Begin()`** – Lines 3377-3390: Validates inputs, stores data in window storage, calculates initial offsets.
- **`Step()`** – Lines 3573-3610: Main logic for range calculation, height measurement, and iteration state management.
- **`End()`** – Lines 3327-3335: Cleanup and cursor restoration (called by destructor).

## Summary

- **ImGuiListClipper** virtualizes list rendering by calculating only visible index ranges via `Step()` iterations.
- Initialize with `Begin(count, height)`, iterate while `Step()` returns true, and render items between `DisplayStart` and `DisplayEnd`.
- Pass `-1.0f` for `item_height` to enable automatic height detection from the first visible row.
- Use `ImGuiListClipperFlags_NoSetTableRowCounters` when clipping inside table contexts to avoid row counter conflicts.
- The destructor automatically calls `End()`, though manual cleanup is available if needed.

## Frequently Asked Questions

### What is the performance benefit of using ImGuiListClipper?

The clipper reduces ImGui drawing calls from **O(n)** to **O(visible items)**, typically processing only 20-50 items regardless of whether your dataset contains 1,000 or 1,000,000 entries. According to the `ocornut/imgui` source code, this prevents CPU bottlenecks when calling functions like `Text` or `Selectable` by skipping invisible items entirely.

### How do I handle items with different heights in a clipped list?

Provide an average height to `Begin()` that approximates your variable row sizes. For precise control, use `IncludeItemsByIndex(begin, end)` to manually specify ranges that must be evaluated, or measure heights during the first `Step()` iteration and store them for subsequent frames. The clipper's height inference system (lines 3573-3610 in [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp)) handles minor variations automatically.

### Can I use ImGuiListClipper with tables?

Yes, but you must pass the `ImGuiListClipperFlags_NoSetTableRowCounters` flag to `Begin()`. Without this flag, the clipper conflicts with ImGui's internal table row advancement logic. The flag tells the clipper to skip row counter management, allowing the table to control row state while the clipper handles vertical positioning.

### Do I need to manually call End() on the clipper?

No. The destructor automatically invokes `End()` (implemented in [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp) lines 3327-3335), which restores the cursor position and cleans up temporary data stored in the window's internal clipper stack. Manual calls to `End()` are only necessary if you need to terminate the clipping session early before the object goes out of scope.