# How the ImGui ID Stack System Works: PushID, PopID, and the ## Operator

> Understand the ImGui ID stack system with PushID, PopID, and the ## operator. Learn how ImGui generates unique widget IDs efficiently without strict naming.

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

---

**TLDR:** ImGui uses a 32-bit ID stack where `PushID()` layers identifiers and `PopID()` removes them, combining hashes with the `##` label suffix to generate unique widget IDs without restrictive naming conventions.

The Dear ImGui library (ocornut/imgui) identifies every interactive element through a sophisticated **ID stack system** that resolves label collisions deterministically. Understanding `PushID`, `PopID`, and the `##` operator is essential for building complex UIs with duplicate labels or dynamically generated content.

## What Is the ImGui ID Stack?

Every widget (buttons, sliders, trees) requires a unique 32-bit identifier. Rather than forcing globally unique labels, ImGui maintains a per-window stack in `ImGuiWindow::IDStack` that combines multiple hash layers. When a widget requests an ID via `ImGui::GetID()`, the system walks the current stack, mixes the hashes together, and produces a final unique identifier. The stack implementation resides in [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp) around line 9334.

## PushID and PopID: Core API

The stack manipulation functions provide four distinct ways to push identifiers onto the stack before removal with `PopID()`.

### The Four PushID Overloads

| Overload | Parameter | Purpose |
|----------|-----------|---------|
| `PushID(const char* str_id)` | C-string | Stable string identifier (e.g., `"MyButton"`) |
| `PushID(const char* str_id_begin, const char* str_id_end)` | Pointer range | Substring or custom-terminated names |
| `PushID(const void* ptr_id)` | Pointer | Memory address as unique key (e.g., object pointer) |
| `PushID(int int_id)` | Integer | Numeric index (e.g., loop counter) |

All overloads resolve to an internal `PushID(id_hash)` call that pushes the 32-bit hash onto the stack, as implemented in [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp) lines 9342–9358.

## The ## Operator and Label Hashing

The `##` token acts as a label delimiter recognized by `GetID()`. Text before `##` renders visibly; text after provides additional hashing material but remains hidden from the user.

```cpp
if (ImGui::Button("Save##file1")) { 
    // Visible: "Save", ID: hash("Save##file1")
}

```

### Combining PushID with Empty Labels

For dynamic content, combine `PushID` with an empty label to avoid visible suffixes entirely:

```cpp
ImGui::PushID(item_id);  // item_id = unique integer or pointer
ImGui::Button("##");     // Uses only the stack's hash for ID
ImGui::PopID();

```

This pattern appears throughout [`imgui_widgets.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_widgets.cpp) (lines 3832–3834).

## Practical Usage Patterns

### Disambiguating Identical Labels

When multiple "OK" buttons exist, wrap each in a scoped ID to prevent them from sharing the same identifier:

```cpp
for (int i = 0; i < 5; i++) {
    ImGui::PushID(i);
    if (ImGui::Button("OK")) { /* handles i-th button */ }
    ImGui::PopID();
}

```

### Stable IDs for Dynamic Content

Use immutable pointers (e.g., object addresses) rather than array indices to ensure focus and activation state persist through reordering or deletion. This prevents input focus from jumping to the wrong item when the list changes.

### Nested Scopes

The stack supports arbitrary nesting for hierarchical IDs:

```cpp
ImGui::PushID("Parent");
    ImGui::PushID(child_index);
        ImGui::Button("Action"); 
        // ID = hash("Parent") ^ hash(child_index) ^ hash("Action")
    ImGui::PopID();
ImGui::PopID();

```

## Safety and Debugging

ImGui asserts balanced push/pop operations at runtime. A mismatch triggers `IM_ASSERT_USER_ERROR` in [`imgui_tables.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_tables.cpp) (line 1593), preventing stack corruption that would cause subsequent widgets to receive incorrect IDs.

## Summary

- The **ID stack** in `ImGuiWindow::IDStack` combines 32-bit hashes to create unique widget identifiers.
- **`PushID`** offers four overloads (string, range, pointer, int) to layer identifiers, while **`PopID`** removes them.
- The **`##`** operator hides text from the UI while using it for ID generation.
- **Empty labels** (`"##"`) combined with `PushID` provide clean visible text with unique underlying IDs.
- The system enforces **balanced push/pop** through assertions to maintain stack integrity.

## Frequently Asked Questions

### What happens if I forget to call PopID?

If pushes and pops are unbalanced, ImGui triggers an assertion via `IM_ASSERT_USER_ERROR` (see [`imgui_tables.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_tables.cpp) line 1593). The stack corruption would cause subsequent widgets to receive incorrect IDs, potentially breaking input handling and focus management.

### Can I use the same label for multiple buttons?

Yes, but only if you scope each with `PushID` or use the `##` suffix to differentiate them. Without disambiguation, widgets with identical labels share the same ID and behave as a single logical entity.

### Why use PushID instead of ## suffixes?

`PushID` keeps visible labels clean and readable while providing uniqueness programmatically. It is essential for dynamic lists where generating unique string suffixes would be cumbersome, and it allows using stable pointers or integers that survive reordering better than generated strings.

### How does ImGui combine stack hashes?

Each `PushID` computes a 32-bit hash of its argument and pushes it onto the stack. When `GetID` processes a label, it mixes the current stack hash with the label's hash (or an empty string if the label is `""`) to produce the final identifier.