skip to content

How do LangGraph's stream_mode values and updates differ in what they yield?

level: middleimportance: must knowfreq 72%

answer

  1. one is a snapshot, one is a delta
  2. node name appears in only one of them
  3. payload size scales differently
  4. last chunk equals invoke's result
  5. list of modes changes the yielded shape

basics

~20 s

values yields the entire graph state after every superstep. updates yields only the delta each node returned, keyed by node name. Use updates for cheap progress events and values when the consumer genuinely needs the whole state.

solid answer

~40 s

With `stream_mode="values"` each chunk is the full state object after the step's updates have been applied — the same shape `invoke()` would eventually return, so the last chunk equals the final result. With `stream_mode="updates"` each chunk is a dict of `{node_name: what_that_node_returned}`, containing only the keys that node produced; if two nodes ran in parallel in the same superstep you see both entries. The practical difference is payload size and attribution. If state holds a 200-page document plus a short status field, `"values"` re-sends the document on every step while `"updates"` sends a few bytes, and only `"updates"` tells you *which* node caused a change. You can request both — `stream_mode=["values", "updates"]` — and each yielded item becomes a `(mode, chunk)` tuple.

code

python · 9 lines
python
for mode, chunk in graph.stream(
    {"topic": "cats"},
    stream_mode=["updates", "values"],
):
    if mode == "updates":
        for node, delta in chunk.items():
            print("step by", node, "->", delta)
    else:
        print("state now:", chunk)

go deeper

for a junior

Remember the one-line contrast: values gives the full state after each step, updates gives just what each node returned under that node's name.

for a middle

Explain the delta-versus-snapshot tradeoff concretely — payload growth with state size, node attribution for progress UIs, and why the last values chunk equals the final result.

for a senior

Bring the production angle: bandwidth blowups when heavy channels enter state, client-side accumulation drifting from server reducers, and projecting state before it crosses a trust boundary.

for a principal

Own the wire contract. Decide whether clients consume node-shaped deltas at all, since that couples your public event stream to the graph's internal node names and makes refactors breaking changes.

## The mental model LangGraph advances in supersteps. In each one it runs the eligible nodes, collects the partial dicts they return, and applies them to the state channels through each channel's reducer. `"values"` and `"updates"` are two views of that same moment: the state *after*, versus the deltas *that produced it*. ## stream_mode="values" Each chunk is the whole state after the step. For a message-based agent state with an appending reducer, chunk 1 might hold one message, chunk 2 three messages, chunk 3 five — each chunk repeating everything that came before. Properties worth remembering: - The final chunk is exactly what `invoke()` returns. Code that consumes the stream and keeps the last chunk reproduces `invoke()` semantics. - The consumer needs no accumulation logic: render the latest chunk and you are current. That makes it the easy mode for a dashboard or a state inspector. - Cost grows with state size times step count. A retrieval agent that parks a dozen retrieved documents in state will push those documents over the wire on every hop. ## stream_mode="updates" Each chunk is `{node_name: partial_update}` — literally the dict the node function returned, before reducers merged it in. Properties: - You get attribution for free. `{"retriever": {"docs": [...]}}` says which node ran and what it contributed, which is exactly what a "Searching…", "Reading…", "Writing…" progress UI wants. - Parallel fan-out shows up as multiple keys in one chunk when several nodes complete in the same superstep. - The consumer must accumulate if it wants current state, and must know the reducers to accumulate correctly: a channel whose reducer appends behaves differently from one that overwrites. Re-implementing reducer semantics on the client is a classic source of drift bugs. - A node that returns `None` or an empty dict contributes nothing visible, so "no chunk" does not mean "node did not run". ## Which to pick Rules of thumb that hold up in review: - **Progress UI, chat transcripts, activity logs** → `"updates"`. Small payloads, node names included, natural mapping to events. - **State inspector, debugging notebook, admin view** → `"values"`. Simplest correct thing; size does not matter locally. - **Large state, remote client, per-message billing on a socket** → `"updates"`, or `"values"` with a projection step that strips heavy channels before serialising. Neither mode is the right tool for token-by-token output; that is the message-token mode's job, and the two are typically combined. ## Combining modes Passing a list changes the yielded shape. Single mode yields bare chunks; a list yields `(mode, chunk)` two-tuples. Consumers written for the single-mode shape break the moment someone adds a second mode, so branch on the mode name explicitly rather than positionally unpacking whatever arrives. Streaming with `subgraphs=True` adds another wrapping level: chunks arrive as `(namespace, chunk)`, where the namespace tuple identifies which nested graph produced it — and with multiple modes, `(namespace, mode, chunk)`. Without that flag, work happening inside a nested graph is reported as a single update from its parent node rather than step by step. ## Failure modes to name in an interview 1. **Silent bandwidth blowup.** Someone adds a `raw_html` or `documents` channel to state; the `"values"` stream quietly grows by megabytes per run. Nothing errors — the bill and the latency move. 2. **Client-side reducer drift.** A consumer of `"updates"` concatenates message lists by hand, then a channel's reducer changes to de-duplicate, and the client's view diverges from the server's state. 3. **Leaking internals.** Both modes happily serialise scratchpad channels, tool arguments and system-prompt fragments. Project state onto a public shape before it leaves the process. 4. **Assuming order across parallel nodes.** In a fan-out superstep the keys within one `"updates"` chunk carry no execution-order meaning. 5. **Treating absence as failure.** A node that returns nothing produces no visible delta; use the run's trace or a checkpoint history to prove what executed.

  • If a consumer only ever keeps the last chunk of a values stream, what has it built?
    The equivalent of invoke(). With stream_mode="values" the final chunk is the completed state, so consuming the stream and discarding all but the last item reproduces the blocking call exactly — at the cost of having transferred every intermediate snapshot to get there.
  • Two nodes run in parallel in the same superstep. What does an updates chunk look like?
    One dict with both node names as keys, each mapped to that node's returned partial update. The key order carries no meaning about which finished first, and the merged result you would see under the snapshot mode depends on each channel's reducer resolving the concurrent writes.
  • How do you get step-by-step visibility into a nested graph rather than one lump from its parent node?
    Pass subgraphs=True to stream()/astream(). Chunks then arrive prefixed with a namespace tuple identifying the nested graph that produced them, so a nested run reports its own supersteps instead of surfacing as a single update from the parent node once it completes.
  • What breaks when someone adds a second stream_mode to an existing endpoint?
    The yielded shape changes from a bare chunk to a (mode, chunk) tuple, so any consumer that destructures the old shape starts misreading payloads or raising unpack errors. The safe pattern is to branch on the mode name from the start, even when only one mode is configured.

saying these in an interview costs you the question

  • Says updates yields the whole state each step
  • Thinks values only sends changed keys
  • Reimplements reducer logic client-side and calls it equivalent
  • Assumes a list of modes still yields bare chunks
  • Believes no update chunk proves a node did not run

context