Your hand-written agent loop works. Then production asks four questions it can't answer: Can it pause for a manager's approval before refunding? If the server restarts mid-run, does it resume? Can I see what it knew at step 4? Can two different paths run from the same state? A while-loop says no to all four. LangGraph exists for exactly those four questions — which is why it shows up in production agent stacks and, increasingly, in interviews.
The loop is a graph — drawn instead of buried
In a hand-written loop, the control flow lives inside if statements. LangGraph turns it inside out: steps become nodes, the routing between them becomes edges, and everything they share becomes explicit state.
Look at the cycle: agent → tools → agent. That's the ReAct loop from lesson 3, drawn rather than implied. And notice the branch that stops at human approval — impossible to express cleanly in a while-loop inside a request handler, trivial as an edge.
State — the agent's working memory, made explicit
State is a typed object passed between nodes. Each node reads it and returns updates. Because every contribution is declared, you can inspect exactly what the agent knew at any step.
from typing import Annotated, TypedDict
import operator
class AgentState(TypedDict):
messages: Annotated[list, operator.add] # APPENDED across nodes
order: dict | None # replaced by whoever sets it
step_count: intThe detail that catches everyone: the reducer. Annotated[list, operator.add] means updates are appended; a plain field is replaced. Get it backwards and either your conversation history vanishes each step, or your single draft becomes a growing list of drafts. Fields that accumulate need a reducer; fields that are owned by one node don't.
Nodes and edges
from langgraph.graph import StateGraph, START, END
def agent_node(state: AgentState):
reply = model.bind_tools(TOOLS).invoke(state["messages"])
return {"messages": [reply], "step_count": state["step_count"] + 1}
def tools_node(state: AgentState):
results = run_requested_tools(state["messages"][-1])
return {"messages": results}
graph = StateGraph(AgentState)
graph.add_node("agent", agent_node)
graph.add_node("tools", tools_node)
graph.add_edge(START, "agent")
graph.add_edge("tools", "agent") # the cycleA node is just a function: state in, partial state out. Nothing mysterious — the framework's job is running them in the right order and merging their updates.
Conditional edges — where the decision lives
def route(state: AgentState):
last = state["messages"][-1]
if state["step_count"] >= MAX_STEPS:
return "end" # always bound the loop
if getattr(last, "tool_calls", None):
return "tools"
return "end"
graph.add_conditional_edges("agent", route, {"tools": "tools", "end": END})
app = graph.compile()Result
agent → tools → agent → END
That's a complete ReAct agent — with the step limit enforced by the graph rather than hidden in a loop counter.
The routing function is plain Python, which matters: your safety rules live in code you can unit-test, not in a prompt you hope the model respects.
Wait — what happens if the server restarts mid-run?
In a while-loop: the run is gone. With a checkpointer, LangGraph persists the state after every step, so a run can be paused, resumed, recovered after a crash, and continued across sessions.
from langgraph.checkpoint.postgres import PostgresSaver
app = graph.compile(checkpointer=PostgresSaver(conn))
config = {"configurable": {"thread_id": "ticket-8123"}}
app.invoke({"messages": [user_msg], "step_count": 0}, config)
# ... hours later, same thread_id resumes exactly where it stoppedThe thread_id turns an in-memory loop into a durable process. That's what makes long-running agents and multi-session conversations practical — and it's the foundation of the next feature.
Human-in-the-loop — approval before, not after
The pattern production actually needs: the agent proposes a refund, the graph stops before executing it, a human sees the proposal, and the run resumes on approval.
app = graph.compile(checkpointer=saver, interrupt_before=["refund"])
app.invoke(initial_state, config) # runs, then STOPS at refund
pending = app.get_state(config) # show the human what's proposed
if human_approves(pending):
app.invoke(None, config) # resume from the checkpoint
else:
app.update_state(config, {"messages": [rejection_note]})
app.invoke(None, config) # agent responds to the rejectionTwo properties make this trustworthy: the sensitive action has not happened yet when the human sees it — approval, not notification — and the rejection path feeds back as an observation, so the agent can explain the outcome instead of dead-ending.
When it's worth it (and when it isn't)
Worth it when you need: persistence and resumability, human approval gates, branching or parallel paths, streaming of intermediate steps, or a control flow complex enough that you'd rather look at a diagram than a nest of conditionals.
Not worth it for a two-tool bounded loop in a weekend project — the raw version is thirty lines and teaches you more. And remember what it does not fix: hallucination, tool design, prompt quality, evaluation. Frameworks are operational leverage, not correctness guarantees.
Selection-round radar: LangGraph questions cluster on four terms — state (typed, shared, with reducers), nodes and edges, conditional edges (routing = control flow in code), and checkpoints (persistence enabling human-in-the-loop and recovery). If you can explain the agent→tools cycle plus interrupt-before-approval, you've covered most of what gets asked.
Common mistakes
- Wrong reducers — history disappearing, or single values piling up as lists.
- No step-limit check in the routing function, so the cycle can spin forever.
- Using
interrupt_afterwhere you meantinterrupt_before— the action already happened. - Stuffing business logic into nodes instead of keeping tools as plain, testable functions.
- Adding LangGraph to a trivial agent for the vocabulary rather than the features.
- Expecting the framework to improve answer quality.
Quick recap
| Concept | One-liner |
|---|---|
| Graph | nodes do work, edges route, state is shared and typed |
| Reducer | accumulating fields append; owned fields replace |
| Conditional edge | routing in plain Python — where step limits and gates live |
| The cycle | agent → tools → agent IS the ReAct loop, drawn |
| Checkpointer | persists state per step → resumable, crash-safe, multi-session |
| interrupt_before | pause before a sensitive node; resume on approval |
| What it doesn't fix | hallucination, tool design, prompts, evaluation |
Practice Zone — PYQs from real selection rounds
Six MCQs and two design tasks — draw a support agent's graph with an approval interrupt, then design a research agent's state (including the reducer trap).
LangGraph models an agent as:
Asked in

What is the state in a LangGraph agent?
Asked in

A conditional edge in LangGraph is used to:
Asked in

What do checkpoints enable?
Asked in

Why might a team prefer LangGraph over a hand-written agent loop?
Asked in

Human-in-the-loop in LangGraph is typically implemented by:
Asked in

Hands-on tasks:
Sketch the LangGraph structure for a support agent with tools and a human-approval step before refunds. Name the nodes, the edges (including conditional ones), and where the interrupt goes.
Asked in

Design the state object for a research agent that searches the web, reads pages, and writes a brief. What fields, and which one is easiest to get wrong?
Asked in

FAQ
Is LangGraph a replacement for LangChain?
No — different layers, commonly used together: LangGraph for control flow and state, LangChain components (models, tools, retrievers, parsers) inside the nodes. Chains are linear; graphs express cycles, branches and pauses.
Can I use LangGraph without LangChain?
Yes. Nodes are plain functions — they can call any SDK directly. Many teams do exactly that to keep the dependency surface small while still getting state, checkpoints and interrupts.
Where should checkpoints be stored in production?
A durable store — Postgres or similar — not memory, or you lose exactly the resumability you added it for. Treat checkpoint data like any other user data: it contains conversation content, so apply the same access control, PII handling and retention rules.
Next lesson: what the agent remembers, and what it should forget — Lesson 6: Agent Memory & Context Engineering →


