Vocabulary for this course

The terms the chapters assume, in plain words. You can open this from any chapter.

checkpoint
A saved copy of the state after a step, stored under a thread so a run can resume or rewind.
compile
The step that checks a graph's connections and turns it into something that can be run.
conditional edge
A rule that reads the state and names which node runs next.
edge
A rule saying which node runs after the current one finishes.
entry point
The declared first node of a graph, marked by an edge from the start.
graph
A workflow drawn as nodes joined by edges, where the path can branch and loop.
interrupt
A planned pause before a node runs, so a person can approve, reject or edit first.
node
A function that reads the state and returns only the keys it changed.
reducer
A function that decides how an update is combined with the old value of one state key.
state
The shared record passed between nodes, holding every value the workflow needs to continue.
subgraph
A graph mounted inside another as a single node, with its own slice of state.
supervisor
A node that reads the state and chooses which specialist worker runs next, rather than answering itself.
thread
A named run whose checkpoints are stored together, so it can be listed, resumed or branched.

LangGraph from the ground up

State and reducers

A node does not replace the state, it returns an update. Whether that update overwrites or appends is decided by a reducer — and that choice explains most graph bugs.

Part 2 of 10

Beginner15 min readBack to Courses

Updates, not replacements

A node returns only the keys it changed. The runtime merges that partial update into the existing state, so a node that reads three keys and writes one does not need to repeat the other two.

That is convenient and it is also where the first surprise lives. If two nodes both write answer, the last one wins. If a node returns a list, the question is whether it replaces the old list or is appended to it.

A worked merge: the state holds a question and a draft. A node returns only {"answer": "..."}. After the merge the question and draft are still there, and the answer is new. That is the behaviour people expect, and it is why a node can be written against the keys it cares about rather than the whole schema.

The merge order matters when two nodes run in the same step. With parallel branches, both updates arrive together. A key with a reducer combines them; a key without one raises rather than picking a winner. That is why a shared key needs a reducer once two nodes can write it.

The reducer decides

Each key has a rule for combining the old value with the update. The default is replacement: the new value wins. For a conversation, replacement is wrong — every node would erase the history it did not return.

A reducer changes the rule for one key. add_messages appends new messages and updates existing ones by id. A counter reducer adds numbers. A set reducer unions. The node stays the same; the meaning of its return value changes.

A worked comparison: a counter reducer with old 2 and new 1 gives 3; a replacement reducer gives 1; an append reducer on a list gives the old list plus the new item. The node returns the same thing in all three cases. Only the declaration changes the meaning, which is why the reducer is part of the state schema rather than the node.

Declaring a reducer

from typing import Annotated, TypedDict
from langgraph.graph.message import add_messages

class State(TypedDict):
    messages: Annotated[list, add_messages]   # append, do not replace
    attempts: int                              # replace

def node(state: State):
    return {"messages": [("ai", "hello")]}

Two reducers, one node

Run the node with each reducer. With replacement the history disappears; with append it grows. Then add a node that writes the same key twice and see which reducer keeps both.

Output
Click "Run" to execute your code

Keep state small enough to read

The state is passed to every node, so everything in it is available everywhere and nothing in it is private. A state that accumulates full documents, tool payloads and debug output becomes the hardest part of the system to reason about.

Store references rather than payloads where you can: a document id, not the document. Keep scratch values local to the node that produces them, and write to the state only what a later node needs.

State size is easy to underestimate. Twenty chunks at eight hundred tokens each is sixteen thousand tokens, written on every step. Store the ids and fetch the text in the node that needs it; the checkpoint stays small.

Mutating the state object in place breaks checkpointing and time travel. Return a new value; a graph that edits the old one cannot be replayed.

A reducer is a small function

A reducer takes the old value and the update and returns the combined value. That is the whole contract, which means you can write one in three lines and test it without a graph. A merge that keeps the newest version of each item, a union of tags, a sum of counters — all are ordinary functions.

Write the test first: given an old value and an update, assert the result. A wrong reducer makes every node that uses the key wrong in the same way.

Messages need ids

An append reducer on a list of messages grows without bound and duplicates on a retry. add_messages solves both by keying on the message id: a message with an existing id replaces the old one, a new id appends.

That is what makes a retried node safe. The node can emit the same message again and the state does not grow a second copy.

Ids have to be stable across a retry. A provider message id is ideal; a uuid generated inside the node is not, because the retry generates a new one and appends a duplicate. If the node has no natural id, derive one from the content and the attempt number rather than from the clock.

An append that does not duplicate

The reducer keys on message id, so re-emitting an existing id replaces it. Run the node twice and confirm the history does not double.

Output
Click "Run" to execute your code

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

What does a node return?