# How Dear ImGui's ID Stack System Works: A Complete Technical Guide

> Understand Dear ImGui's ID stack system. Learn how unique widget identifiers are generated, enabling multiple identical labels without state collisions.

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

---

**TLDR:** Dear ImGui generates unique widget identifiers by hashing a stack of contextual values stored in `ImGuiContext`, allowing multiple identical labels to coexist without state collisions.

Dear ImGui's immediate mode architecture requires a robust mechanism to track widget state—such as focus, hover, and activation—across frames. The **ID stack system** solves this by computing 32-bit hashes on-the-fly from a hierarchy of values rather than storing explicit identifiers. Understanding how `PushID` and `PopID` manipulate this stack is essential for debugging collisions and managing complex layouts in the ocornut/imgui codebase.

## Why Unique IDs Matter

Every interactive widget requires a **unique identifier** to persist state between frames. Rather than expecting users to manually generate UUIDs, the library computes these IDs deterministically by hashing the current contents of an internal stack maintained within `ImGuiContext`. This approach allows sibling widgets to share identical labels—such as multiple "Delete" buttons in a list—without conflicting state.

## The ID Stack Architecture

The ID stack lives inside the `ImGuiContext` structure and serves as a hierarchy of scope identifiers. As described in the **[ID STACK section](https://github.com/ocornut/imgui/blob/master/imgui.cpp#L83)** of [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp), the system automatically pushes context when entering new scopes and hashes all stack elements to produce the final widget ID.

### Automatic Scope Management

Container functions like `Begin()` and `TreeNode()` implicitly call `PushID()` when entering a scope and `PopID()` when exiting. This automatic management ensures that windows, tree nodes, and other containers create distinct namespaces for their children without manual intervention. The stack is cleared incrementally, with each `PopID()` removing the most recently pushed value.

## Pushing Values Onto the Stack

Users can manually push identifiers using three distinct overloads declared in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h) and implemented in [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp) around lines 9355‑9379. Each overload adds a specific data type to the stack, which participates in the final hash calculation.

### String-Based Identifiers

Use `ImGui::PushID(const char* str_id)` or the ranged variant `PushID(const char* str_begin, const char* str_end)` to push a string segment onto the stack. This is the most common approach for distinguishing between sibling elements.

### Pointer-Based Identifiers

For objects with stable memory addresses, `ImGui::PushID(const void* ptr_id)` pushes a pointer value directly. This is ideal when iterating over actual object instances where the pointer serves as a unique key.

### Integer-Based Identifiers

When iterating loops or handling indexed data, `ImGui::PushID(int int_id)` pushes an integer value. This overload provides a convenient way to disambiguate array elements without string conversions.

## Label Syntax and Implicit Hashing

The final ID computation incorporates the widget's label string, which supports special syntax for advanced use cases:

- **`##` hidden suffix:** Text following `##` participates in the hash but is not rendered, allowing identical visible labels with distinct IDs.
- **`###` rename operator:** Text before `###` is excluded from the hash, while text after becomes the ID base. This allows dynamic visible text—like changing counters—while maintaining stable identifiers.

According to the **[FAQ documentation](https://github.com/ocornut/imgui/blob/master/docs/FAQ.md#q-about-the-id-stack-system)** in [`docs/FAQ.md`](https://github.com/ocornut/imgui/blob/main/docs/FAQ.md), these operators provide fine-grained control over what contributes to the hash without affecting the user interface.

## How the Final ID Is Computed

Internally, Dear ImGui calls `GetID()` (see `ImGui::GetID` in the *ID STACK* section of [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp)) to generate the final 32-bit hash. This function iterates over every element currently on the stack—strings, pointers, and integers—mixing each into a cumulative hash using the same algorithm as string hashing. The resulting value is stored in the widget's `ID` field and used for all state look-ups in `ImGuiContext`.

## Debugging ID Collisions

When widgets exhibit unexpected focus behavior or assertion failures indicate duplicate IDs, use the **ID Stack Tool** available in [`imgui_demo.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_demo.cpp). Call `ImGui::ShowIDStackToolWindow()` or navigate to *Demo > Tools > ID Stack Tool* in the demo window. This tool displays every level of the current stack with intermediate hash values, making it trivial to identify where collisions occur.

## Code Examples

The following examples demonstrate the three `PushID` overloads and label syntax tricks as implemented in the ocornut/imgui source:

```cpp
// Example 1: Integer-based PushID in a loop
ImGui::Begin("MyWindow");
for (int i = 0; i < 3; ++i) {
    ImGui::PushID(i);                // Stack: ["MyWindow", i]
    ImGui::Button("Click");          // ID = hash("MyWindow", i, "Click")
    ImGui::PopID();
}
ImGui::End();

// Example 2: Pointer-based PushID for object stability
MyObject* obj = GetSelectedObject();
ImGui::PushID(obj);                  // Stack: ["MyWindow", obj]
ImGui::Checkbox("Enabled", &obj->enabled);
ImGui::PopID();

// Example 3: Hidden suffix with ##
ImGui::Button("Play##song42");       // Stack: ["MyWindow"]; ID = hash("MyWindow", "Play##song42")

// Example 4: Dynamic label with constant ID using ###
float fps = ImGui::GetIO().Framerate;
char buf[64];
sprintf(buf, "FPS: %.1f###FPSLabel", fps);
ImGui::Text(buf);                    // Visible text changes, ID stays hash("MyWindow", "FPSLabel")

```

## Summary

- The ID stack lives in `ImGuiContext` and is manipulated via `PushID()` and `PopID()` defined in [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp) (lines 9355‑9379) and declared in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h).
- Container functions automatically manage stack scope, pushing at entry and popping at exit.
- Three overloads accept **strings**, **pointers**, and **integers** to disambiguate sibling widgets.
- The final ID is a 32-bit hash computed by `GetID()` that combines all stack elements with the widget label using the same algorithm as string hashing.
- Label syntax using `##` and `###` allows hidden hash components or stable IDs with changing visible text.
- Use `ShowIDStackToolWindow()` from [`imgui_demo.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_demo.cpp) to visualize the current stack and debug collisions.

## Frequently Asked Questions

### What causes ID collisions in Dear ImGui?

ID collisions occur when two interactive widgets generate identical 32-bit hashes, causing Dear ImGui to treat them as the same widget. This happens when siblings share the same label without distinct stack context, or when manual `PushID` calls are mismatched with `PopID`. The assertion error will typically reference the conflicting label and can be diagnosed using the ID Stack Tool.

### When should I use PushID with a pointer versus an integer?

Use `PushID(const void* ptr_id)` when iterating over stable object addresses where the pointer itself serves as a unique entity identifier—common when rendering lists of C++ objects. Use `PushID(int int_id)` for indexed loops or array positions where the integer index is the distinguishing factor, as it avoids pointer indirection and is slightly more efficient according to the implementations in [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp).

### How do I debug duplicate ID errors in ImGui?

Navigate to *Demo > Tools > ID Stack Tool* in the demo interface or programmatically call `ImGui::ShowIDStackToolWindow()` as implemented in [`imgui_demo.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_demo.cpp). This window displays every level of the current ID stack with the resulting hash values at each scope. By inspecting the stack while the duplicate ID warning is active, you can identify which parent scope is missing a `PushID` or where two widgets inadvertently share the same hash components.

### What is the difference between ## and ### in ImGui labels?

The `##` operator marks a hidden suffix—text after `##` is not rendered but participates in the ID hash, allowing buttons with identical visible text to have unique IDs. The `###` operator excludes everything before it from the hash and uses only the text after as the identifier base, enabling you to change the visible label dynamically while keeping the underlying ID stable for state persistence, as documented in the Dear ImGui FAQ.