The Elm Architecture (TEA)

The Elm Architecture (TEA)

Synthesis. The pattern, the lineage, the "how to think about it" — pulling together Elm (the language), The Elm Architecture, Pure Functions and Immutable State, Side Effects via Cmd, Reducers, React's Evolution Toward Elm, and MVU in Other Ecosystems into one place.

The 30-second pitch

Your entire app's state is one immutable value (Model). Things that happen are Msgs. A pure function update(state, msg) -> (new_state, cmd) is the only place state changes. A pure function view(state) -> ui renders. Side effects are returned as Cmd values; the runtime executes them. Cycle: state → view → user does something → msg → update → new state → view.

That's it. The discipline is everything.

Why it works

Three guarantees fall out of the constraints:

  1. Predictable. Same state always renders the same view. Same state + same msg always produces the same new state. No race conditions in user code.
  2. Testable. update is (input, input) -> output. No mocks. Pass values, assert values.
  3. Replayable. Recorded sequences of Msgs reproduce the entire app history. Time-travel debugging is trivial.

The constraints are simple to state and powerful in practice. That's what makes the pattern survive: it's hard to "improve" without breaking the guarantees.

The lineage

1990s   Functional UI patterns in Haskell, OCaml, Lisp
   ↓
2003    The "Functional Reactive Programming" research wave
   ↓
2012    Evan Czaplicki creates Elm — combines FRP + Haskell-like syntax + browser
   ↓
2014    Czaplicki refines: simpler runtime, "The Elm Architecture" pattern emerges
   ↓
2015    Dan Abramov releases Redux at React Europe — Elm Architecture in JS
        "Live React: Hot Reloading with Time Travel" demo
   ↓
2018    React Hooks (useState, useReducer, useEffect) bake Elm-shaped patterns into React core
   ↓
2018    Bubbletea (Go) — explicit Elm Architecture for terminal apps
   ↓
2019    tui-realm (Rust) — Elm Architecture on top of tui-rs/ratatui
   ↓
2019    JetBrains Compose — declarative UI for Kotlin, MVI patterns common
   ↓
2020+   Compose Multiplatform, SwiftUI, Flutter (BLoC/Riverpod), Vue (Pinia) — all Elm-flavoured
   ↓
2024    Lazydap adopts hand-rolled Elm Architecture in Rust because the discipline
        scales while keeping the rust+ratatui+DAP+tokio learning curve manageable.

Elm-the-language is mostly historical. Elm-the-architecture is mainstream.

The five concepts you need

Concept Atomic note One-line summary
Model (no separate note; covered in The Elm Architecture) Your entire app state, as plain immutable data
Update / Reduce Reducers Pure function (State, Msg) -> (State, Cmd); only place state changes
View The Elm Architecture Pure function State -> UI; no mutation, no I/O
Cmd Side Effects via Cmd Description of a side effect; runtime executes; result comes back as Msg
Sub Side Effects via Cmd Subscription to external events (timers, websockets, IPC)

That's the whole architecture in five things.

When TEA shines

When TEA hurts

For 90% of "I need a UI" / "I need state coordination" cases in apps the size of mxr/lazydap, TEA is the right shape.

How Lazydap uses it

crates/tui/ is hand-rolled TEA in Rust:

pub struct AppState { /* model */ }
pub enum Msg { Key(KeyEvent), DapEventEvent, Tick, /* ... */ }
pub enum Cmd { Quit, SendIpc(Request), LoadSource(PathBuf), None }

pub fn update(state: AppState, msg: Msg) -> (AppState, Cmd) {
    match msg {
        Msg::Key(KeyEvent { code: KeyCode::F(5), .. }) =>
            (state, Cmd::SendIpcContinue { .. }),
        Msg::DapEventStopped { .. } =>
            (state.with_paused(...), Cmd::None),
        _ => (state, Cmd::None),
    }
}

pub fn view(frame: &mut Frame, state: &AppState) { /* render */ }

Main loop:

loop {
    terminal.draw(|f| view(f, &state))?;
    let msg = tokio::select! {
        Some(m) = input_rx.recv() => m,
        Some(m) = ipc_rx.recv() => m,
        _ = tick.tick() => Msg::Tick,
    };
    let (new_state, cmd) = update(state, msg);
    state = new_state;
    perform(cmd).await;
}

~50 lines of boilerplate around update and view. No framework. The discipline is the framework.

Mxr's TUI uses a similar pattern in spirit, with Bubbletea-influenced specifics.

What to remember

Reading order if you want to go deeper

  1. Elm (the language) — see the language that crystallised the pattern (1 hour with the official guide is the fastest internalisation).
  2. The Elm Architecture — the canonical shape.
  3. Pure Functions and Immutable State — the foundations.
  4. Side Effects via Cmd — the bit that surprises people.
  5. Reducers — the generalised vocabulary.
  6. React's Evolution Toward Elm — if you're a React person, this gives you the lineage.
  7. MVU in Other Ecosystems — once you spot the pattern, you spot it everywhere.

Other syntheses (sibling MOCs in the vault)

The other "How X actually works" synthesis notes — each is the entry point to its own cluster:

Further reading