What changes when a workflow stops being a single function and becomes a graph? You gain explicit nodes, routes, and shared state. The useful test is whether you can explain exactly why a request followed one route instead of another.
Before you begin: Read Workflows. Know Python functions and basic type hints.
Read the graph as a program
A node performs work and returns updates. An edge selects where execution goes next. A state schema names the values the graph carries. LangGraph supports model-based agents, but a graph does not need a model to be useful.
Our graph receives a notice. A validation node checks whether text exists. A conditional route sends valid input to a drafting node and empty input to the end. We use a deterministic draft so that routing errors remain separate from model behavior.
Build a graph you can predict
This complete integration example requires the langgraph Python package. Its APIs were reviewed against official documentation on September 10, 2026. It makes no provider call. Package execution is separate from the standard-library examples checked by this course.
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
class State(TypedDict, total=False):
text: str
valid: bool
draft: str
def validate(state: State):
return {"valid": bool(state["text"].strip())}
def route(state: State):
return "draft" if state["valid"] else "stop"
def draft(state: State):
return {"draft": "Source N1 says: " + state["text"].strip()}
builder = StateGraph(State)
builder.add_node("validate", validate)
builder.add_node("draft", draft)
builder.add_edge(START, "validate")
builder.add_conditional_edges(
"validate", route, {"draft": "draft", "stop": END}
)
builder.add_edge("draft", END)
graph = builder.compile()
assert "draft" not in graph.invoke({"text": " "})
print(graph.invoke({"text": "W1 starts at 15:00."}))
Predict which keys appear for each invocation before running it. The invalid case retains input and validation state but never creates a draft. That makes “no draft” explainable without consulting generated prose.
Understand updates before parallel work
Nodes return updates rather than needing to construct the entire state from scratch. When multiple nodes update the same field, you need a deliberate merge rule, called a reducer. Appending messages and replacing a scalar are different operations. Adding parallel branches without defining how their results combine can create ambiguous state or runtime errors.
In this example, each field has one sequential writer. That keeps the merge behavior simple while you learn the graph API.
Add persistence with a clear promise
A checkpointer saves graph state for a thread. A thread ID identifies a continuing workflow; your application must still check which signed-in user owns it. An in-memory checkpointer is useful for local experiments but does not survive process loss.
Human-review interrupts can pause a graph. On resume, the interrupted node starts again from its beginning under the documented behavior. Code before the interrupt may therefore execute again. Put non-repeatable side effects after the approved decision, or make them idempotent and reconcile their outcomes. A checkpoint is not a transaction spanning every external system.
Predict a resumed run
A node sends an email and then pauses for approval. After approval it resumes and sends the email again. Where is the design error?
Trace the repeated operation
The side effect occurs before the interrupt in a node that can restart. Approval also arrives after the action it was supposed to control. Separate preparation, review, and execution. Bind approval to the exact proposed action and use a stable operation ID for execution recovery.
For practice, add a review state to the graph on paper. Specify what should happen when the user rejects the draft, edits it, or returns after the server restarts. Then choose persistent storage that can support that promise.
Sources
Graph API, persistence, and interrupts explain the APIs and recovery semantics.