Pause with `interrupt_before` at compile time or `interrupt()` inside a node; persist with a checkpointer so the thread can resume after restart.
Imagine a board game where you sometimes have to wait for another player before you can take your turn. You need two things: a rule that says 'stop here and ask the other player,' and a notebook where you write down the state of the board so the game can be paused for a week and resumed on the same square. Without the rule you cannot pause; without the notebook you cannot resume after a coffee break. LangGraph's interrupts are the rule, and the checkpointer is the notebook.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
Human in the loop sounds like a UX feature and turns out to be a distributed-systems feature. Pausing a workflow while a human decides means surviving process restarts, multi-instance deployments, network partitions, and the simple reality that humans take minutes to days to respond. LangGraph's answer is to treat HITL as durable workflow state: a pause primitive that yields execution and a persistence primitive that survives anything short of database loss.
The two primitives have to be configured together. Either alone is a footgun: a pause without persistence works only within one process lifetime; persistence without a pause hook is a stateful machine with no way to wait for external input.
This deep dive walks the two pause variants, the four checkpointer options and their operational trade-offs, the resumption mechanics with update_state and as_node, the latency budget of checkpointing, and the bonus side effect that the same mechanism enables time-travel debugging.
The two pause primitives: static and dynamic
Static pause: interrupt_before and interrupt_after. Declared at compile time on the graph object: graph.compile(checkpointer=cp, interrupt_before=['approval_node']). The runtime checks before (or after) the named nodes and yields to the caller via an interrupt event on the stream. This is the right pick when the pause point is a fixed node that always requires external input. Example: an approval_node that always asks a human before sending an outbound email.
Dynamic pause: interrupt(value). Called from inside a node body. The function pauses execution immediately and emits the value through the stream's interrupt event. Resumption with Command(resume=human_input) lets the human's response become the function's return value. This is the right pick when the pause condition depends on state: 'pause only when confidence is below 0.7,' 'pause only when the tool call is to a dangerous endpoint,' 'pause only when the cumulative cost crosses a threshold.'
Mixing both in one graph is supported. A typical pattern is interrupt_before=['high_stakes_node'] for guaranteed pauses plus dynamic interrupt() calls inside other nodes for state-conditional pauses.
The event shapes are slightly different. Static interrupts surface as __interrupt__ events with the pending node name; dynamic interrupts surface as interrupt events with the value. The application code that consumes the stream needs to handle both shapes if both are used.
Situations where this technique stops working.
2–4 min · Everything important, quickly.
Real products, models, and research that use this idea.
- Linear's AI features use LangGraph with Postgres checkpointing for the issue-triage flow that occasionally needs human approval before posting a comment.
- Devin (Cognition AI) reportedly uses a LangGraph-style state machine with durable checkpointing for long-horizon coding tasks.
What an interviewer would ask next. Try answering before peeking at the approach.
QWalk through the exact sequence of calls for a HITL approval flow.
- Configure graph with
interrupt_before=['approval_node']and a PostgresSaver checkpointer. 2) Callgraph.stream(initial_input, {'configurable': {'thread_id': tid}}). 3) The stream yields events until the approval node; an interrupt event is emitted. 4) Application surfaces the pending state to a human. 5) Human decides; application callsgraph.update_state(thread_config, {'approved': True}, as_node='approval_node'). 6) Application callsgraph.stream(None, thread_config)to resume from the next step.
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Configuring `interrupt_before` without setting a checkpointer. The graph will pause but cannot survive a process restart, so the 'pause' only works within one process lifetime.
60 second bullets to scan on the way to the call.
The two pause variants: interrupt_before / interrupt_after and the dynamic interrupt() function
The four checkpointer options: PostgresSaver, SqliteSaver, RedisSaver, MemorySaver
Primary sources. Browse if you want the original framing.
Same topic, related formats. Practice these next.