# How ImGui's ID Stack System Works: PushID, PopID, and the ## Suffix

> Understand ImGui's ID stack system. Learn how PushID, PopID, and the ## suffix create unique widget identifiers for your GUI.

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

---

**ImGui generates unique 32-bit widget identifiers by maintaining a hierarchical hash stack that combines `PushID` scopes with label text, while the `##` suffix allows hidden identifier tokens in display strings.**

The Dear ImGui library (ocornut/imgui) identifies every interactive element—buttons, sliders, trees—through a sophisticated **ImGui ID stack** mechanism that prevents naming collisions while keeping UI code readable. This system relies on hierarchical hashing stored in `ImGuiWindow::IDStack` to produce stable identifiers at runtime, combining explicit scope pushes with implicit label hashing.

## Core Concept of the ID Stack

ImGui assigns every widget a **32-bit ID** derived from a stack of hash values. Because many widgets share identical visible labels (e.g., multiple "OK" buttons), the library builds a stack of identifiers that are combined through hashing to produce a unique final value for each element.

The stack implementation resides in [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp) within the `ImGuiWindow` structure, specifically the `IDStack` member. When you call `ImGui::PushID()`, the function computes a hash of the provided argument and pushes it onto this stack around line 9334. `ImGui::PopID()` simply removes the topmost entry. When a widget requests an ID via `GetID()`, ImGui walks the current stack, mixes the hashes together, and combines the result with the widget's label hash to generate the final identifier. This scope is automatically cleared when the window closes.

## PushID Overloads and Implementation

ImGui provides four distinct overloads for `PushID`, each accepting different identifier types to accommodate various programming patterns:

- **`PushID(const char* str_id)`**: Accepts a C-string for stable string-based scoping (e.g., `"MyButton"`).
- **`PushID(const char* str_id_begin, const char* str_id_end)`**: Accepts a pointer range for substrings or custom terminators.
- **`PushID(const void* ptr_id)`**: Uses a memory address (such as an object pointer) as a unique key.
- **`PushID(int int_id)`**: Accepts a numeric index, commonly used for loop counters.

All overloads are thin wrappers that ultimately call the internal `PushID(ImU32 id_hash)` function, as seen in [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp) lines 9342–9358. This internal function pushes the pre-computed 32-bit hash onto `ImGuiWindow::IDStack`, ensuring consistent hashing behavior regardless of input type.

## The ## Suffix Operator

The `##` token acts as a **label delimiter** recognized by `ImGui::GetID()`. Everything following `##` is stripped from the visible UI text but fully contributes to the hash calculation. This allows identical display text with distinct identifiers:

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

```

The mechanism processes the string once, splitting the display portion from the identifier portion before hashing. This operates independently of the ID stack but combines with it when both are present.

## Practical Patterns and Empty Labels

For dynamic content where labels must remain clean but uniqueness is required, combine `PushID` with empty labels:

```cpp
ImGui::PushID(item_ptr);             // Push pointer or unique index
ImGui::Button("##");                 // Empty label uses only the stack's hash
ImGui::PopID();

```

This pattern appears throughout the codebase, specifically in [`imgui_widgets.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_widgets.cpp) lines 3832–3834, where internal widgets require invisible identifiers. When nesting scopes, the hashes accumulate hierarchically:

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

```

Use immutable keys (like object pointers) rather than array indices when items can be reordered or deleted; this ensures that focus and activation states follow the logical item rather than its position.

## Safety Checks and Debugging

ImGui asserts that `PushID` and `PopID` calls are balanced. A mismatch triggers a runtime assertion via `IM_ASSERT_USER_ERROR`, as implemented in [`imgui_tables.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_tables.cpp) line 1593. This safety check prevents stack corruption that would otherwise cause ID collisions or memory errors. When debugging, verify that every push has a corresponding pop within the same frame and window scope.

## Summary

- **ImGui ID stack**: A per-window stack (`ImGuiWindow::IDStack`) storing 32-bit hash values that scope widget identifiers.
- **PushID/PushID**: Four overloads (string, string range, pointer, integer) that hash inputs and push onto the stack, implemented in [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp) around line 9334.
- **## suffix**: A label delimiter that hides text from display while including it in the ID hash calculation.

- **Empty labels**: Combining `PushID` with `"##"` creates clean UI elements with stable, unique identifiers derived solely from the stack.
- **Safety**: Unbalanced push/pop operations trigger assertions (`imgui_tables.cpp:1593`) to prevent stack corruption.

## Frequently Asked Questions

### What is the ImGui ID stack used for?

The **ImGui ID stack** disambiguates widgets that share identical visible labels by combining hierarchical hash scopes with label text. It allows multiple buttons labeled "OK" to exist simultaneously without ID collisions, and ensures that focus states persist correctly when UI elements are reordered or deleted.

### How does the ## suffix work in ImGui labels?

The `##` operator splits the label string at runtime: text before `##` renders visibly, while text after `##` is hidden from display but fully included in the hash calculation. This allows developers to create human-readable labels like "Save" while appending unique tokens (e.g., "##config1") to generate distinct 32-bit IDs.

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

Forgetting `PopID` creates an unbalanced stack that persists for the remainder of the window's lifetime, causing all subsequent widgets to inherit incorrect hash prefixes. ImGui detects this condition via `IM_ASSERT_USER_ERROR` in [`imgui_tables.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_tables.cpp) (line 1593), triggering a runtime assertion in debug builds to alert you of the mismatch.

### Can I use pointers as IDs in PushID?

Yes, the `PushID(const void* ptr_id)` overload accepts memory addresses as unique identifiers. This is the preferred method for stable identification of dynamic objects, as object pointers remain constant even if array indices change, ensuring that ImGui's internal state (focus, scrolling, selection) remains attached to the correct logical item.