Graphs, not chains
A chain runs once from left to right. A graph has a state, named steps, and the ability to go back — which is what real workflows need.
Where a chain runs out
A chain is a straight line: every step runs exactly once, in a fixed order. That covers most prompt pipelines and none of the workflows people actually ask for. Retry until the answer passes a check. Ask a question, wait for an approval, then continue. Send the work to one of three specialists depending on what it is.
All three need the same three things a chain lacks: a named current position, a decision about where to go next, and a state that survives the trip.
The retry case is the clearest. A chain that drafts an answer has nowhere to put “draft again” — the next step is fixed, and running the chain twice starts from the beginning. A graph adds one edge from the check back to the draft, and the second pass starts with the first pass’s text still in the state. The difference is the ability to say “go back” without rewriting the pipeline.
The state is the program
In a graph, every node reads the same shared state and returns an update to it. That state is the only thing passed between steps. A node is a function of the state, and an edge is a rule about which node runs next.
Keeping everything in one state object is what makes the graph resumable and inspectable. At any moment you can look at the state and know exactly where the work is, which is very hard to do with a chain of nested closures.
The state is usually a dictionary with a few named keys: the question, the passages, the draft, the attempt count. Each node reads what it needs and returns only the keys it changed. That partial update is what the runtime merges, and it is why a node can be written without knowing the whole state.
The same idea in LangGraph
from langgraph.graph import StateGraph, START, END
from typing import TypedDict
class State(TypedDict):
question: str
answer: str
builder = StateGraph(State)
builder.add_node("answer", answer_node)
builder.add_node("check", check_node)
builder.add_edge(START, "answer")
builder.add_edge("answer", "check")
builder.add_edge("check", END)
graph = builder.compile()
print(graph.invoke({"question": "What is RAG?"}))A graph in forty lines
Run it, then add a third node between `answer` and `check` that trims whitespace. Notice that the state carries the data and the edges carry the order.
Nodes are ordinary functions
A node does not need to call a model. It can query a database, validate a schema, call an API or simply reformat the state. The graph does not care what is inside a node, only that it reads the state and returns an update.
That is what makes a graph testable. Each node is a pure function of a dictionary, so it can be called directly in a unit test without building the graph at all.
A node that calls a tool is still a function. It reads a query from the state, calls the tool, and returns the result under a key. The tool call can be mocked in a test, because the node’s contract is the state, not the network. That is the practical benefit: the graph is not needed to test a node.
Start with the smallest graph that has the behaviour you need. A two-node graph with one conditional edge is easier to debug than a five-node graph with three, and most workflows fit in three nodes.
A graph is not a workflow engine
It does not schedule work, poll a queue or survive a process restart on its own. It runs nodes in one process, in the order the edges say. Durability comes from a checkpointer, and scheduling comes from whatever calls the graph.
That matters when the workflow waits days for a person. The graph can pause and resume, but something has to store the thread and wake it; without that, it is a description of a workflow that still needs an operator.
When a chain is still right
If every step runs once, in a fixed order, and the only thing that varies is the data, a chain is simpler and should stay a chain. Graphs earn their complexity when there is a loop, a branch that depends on a result, or a pause for a person.
The question to ask is not “which is more powerful” but “does this workflow need to decide where to go next while it is running”. If the answer is no, a graph is overhead.
The cost is indirection. A graph spreads a workflow across nodes, edges and a state definition, and a reader has to hold all three to follow it. For a five-step pipeline, that is more than the workflow is worth. Add the graph when a loop or branch appears, not before.
The same work, both shapes
Run the chain and the graph, then add a second pass to the graph only. That is the moment the graph starts earning its complexity.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What can a chain not express?
