Immediate Mode GUI vs Retained Mode: How Dear ImGui Rebuilds UI Every Frame

Dear ImGui implements an immediate mode GUI where widgets are recreated and processed every frame through direct function calls, eliminating persistent widget objects and storing state in application variables rather than UI controls.

Dear ImGui (ocornut/imgui) revolutionizes interface design by implementing an immediate mode GUI paradigm that fundamentally differs from traditional UI frameworks. Instead of maintaining a tree of persistent widget objects, the library reconstructs the entire interface each frame through a sequence of procedural calls. This approach eliminates complex state synchronization between the UI and application logic, making it ideal for game development and real-time tools.

Core Principles of Immediate Mode GUI

The immediate mode GUI architecture in Dear ImGui operates on three fundamental principles that distinguish it from retained mode alternatives.

Stateless Widget Rendering

Each call such as ImGui::Button("OK") does not create a persistent widget object. Instead, the function queries the current input state, determines whether the widget is hovered or clicked, updates minimal internal state, and immediately returns a boolean result. The widget exists only for the duration of that function call, leaving no residual UI objects in memory after the frame completes.

Application-Owned State Storage

All persistent UI data must be stored by the application in its own variables and passed back to ImGui on the next frame. When calling ImGui::SliderFloat("Value", &value, 0.0f, 1.0f), the library reads from and writes directly to the external value variable. This inversion of control ensures that the UI never becomes out of sync with the underlying data model, as there are no hidden widget properties to synchronize.

Frame-Based Execution Model

The UI hierarchy is implicitly defined by the order of ImGui calls within each frame's execution. In imgui.cpp, the core implementation processes these calls sequentially, building draw lists that persist only until ImGui::Render() emits the final command buffers. This contrasts sharply with retained mode systems where a scene graph of objects persists across frames and receives asynchronous events.

Immediate Mode GUI vs Retained Mode Comparison

Understanding the architectural differences between these paradigms reveals why Dear ImGui achieves its characteristic simplicity and performance.

Widget Lifecycle

  • Immediate Mode: Widgets are created, processed, and destroyed within a single frame iteration. No long-lived objects remain in memory.
  • Retained Mode: Widgets are instantiated once during initialization and persist until explicitly destroyed, requiring memory management and lifecycle tracking.

Event Handling

  • Immediate Mode: Events are polled directly inside the widget call. Functions like ImGui::Button() return interaction flags immediately, allowing conditional logic to execute in-line.
  • Retained Mode: Events are dispatched to existing widget objects via callbacks, listeners, or signal-slot mechanisms, creating asynchronous complexity.

State Management

  • Immediate Mode: Application variables store all persistent state. ImGui maintains only minimal internal state for navigation and focus in structures managed within imgui.cpp.
  • Retained Mode: Widget objects own their internal state (text content, selection ranges, scroll positions), requiring explicit data binding to application logic.

Rendering Architecture

  • Immediate Mode: Despite rebuilding the UI each frame, Dear ImGui does not issue a draw call per widget. As implemented in imgui_draw.cpp, the library batches geometry into vertex buffers and emits a small, efficient list of draw commands consumed by backend renderers like backends/imgui_impl_opengl3.cpp.
  • Retained Mode: Engines typically traverse a persistent scene graph each frame, which may involve cache invalidation and complex batching strategies.

Implementation in Dear ImGui Source Code

The immediate mode paradigm is realized through specific architectural decisions visible in the repository's source files.

In imgui.h, the public API exposes functions like ImGui::Begin(), ImGui::End(), and ImGui::SliderFloat() that operate without object instantiation. The core state machine in imgui.cpp manages frame contexts through ImGui::NewFrame() and ImGui::Render(), processing input and building draw lists that live only for the current iteration.

The imgui_draw.cpp file handles the vertex buffer construction, aggregating geometry from all widget calls into efficient draw lists. This batching mechanism avoids the "hammering the GPU" pitfall that some associate with immediate mode rendering, as clarified in docs/README.md.

Practical Code Example

The following pattern demonstrates the immediate mode GUI workflow inside a typical application loop:

// Application state stored in regular variables
bool show_demo = true;
float value = 0.0f;

while (running) {
    // 1. Start new ImGui frame
    ImGui_ImplXXX_NewFrame();
    ImGui::NewFrame();

    // 2. Build UI (immediate mode pass)
    ImGui::Begin("Demo Window", &show_demo);
    
    if (ImGui::Button("Press Me")) {
        // Handle click immediately
        value = 0.0f;
    }
    
    ImGui::SliderFloat("Value", &value, 0.0f, 1.0f);
    ImGui::End();

    // 3. Render: ImGui builds draw list, backend draws it
    ImGui::Render();
    ImGui_ImplXXX_RenderDrawData(ImGui::GetDrawData());
}

In this example, ImGui::Button() returns true only during the frame where the click occurs, and ImGui::SliderFloat() modifies the external value variable directly. No window or button objects persist between iterations of the while loop.

Summary

Dear ImGui's immediate mode GUI architecture provides distinct advantages for real-time applications:

  • Zero Widget Objects: UI elements exist only as function calls, eliminating memory allocation and object lifecycle management overhead.
  • Synchronous Event Handling: Input processing occurs inline with UI description code, removing the need for callback registration or event queues.
  • Automatic Synchronization: Application variables serve as the single source of truth, preventing state desynchronization between the UI and business logic.
  • Efficient Rendering: Geometry batching in imgui_draw.cpp ensures optimal GPU utilization despite the per-frame reconstruction paradigm.

Frequently Asked Questions

Does immediate mode GUI mean issuing a draw call for every widget every frame?

No. While Dear ImGui reconstructs the UI logic each frame, it does not issue GPU draw calls per widget. Instead, imgui_draw.cpp batches all geometry into vertex buffers, and ImGui::Render() produces a compact draw list containing only a few commands. Backend implementations like imgui_impl_opengl3.cpp then render these batches efficiently, making the approach suitable for high-performance applications.

Where is UI state stored if widgets don't persist between frames?

Persistent state resides entirely in your application's variables. When you call ImGui::SliderFloat() or ImGui::InputText(), you pass pointers to your own data. ImGui maintains only transient internal state for navigation, focus management, and animation, stored in structures like ImGuiContext within imgui.cpp. This design ensures the UI reflects your data model without synchronization code.

How does immediate mode GUI handle complex layouts without a widget hierarchy?

The UI hierarchy emerges implicitly from the call stack and order of operations. ImGui::Begin() and ImGui::End() create scoped containers, while functions like ImGui::SameLine() or ImGui::Indent() modify the current cursor position in the window's layout state. The library tracks this transient hierarchy in frame-specific stacks during ImGui::NewFrame(), eliminating the need for persistent parent-child object relationships.

Is Dear ImGui suitable for applications requiring complex form validation?

Yes, though the pattern differs from retained mode frameworks. Validation logic executes immediately after the widget call returns a value. For example, after ImGui::InputFloat() modifies your variable, you can clamp or validate the value before the next frame begins. This immediate feedback loop often results in simpler validation code compared to reactive systems that must propagate changes through observer patterns.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →