When does LangGraph `create_react_agent` beat a hand written StateGraph?
create_react_agent wins on the standard tool-calling loop with a baked-in messages state; reach for a hand-written StateGraph when routing, state, or interrupts get custom.
Imagine ordering coffee. The default menu has espresso, latte, and cappuccino. Three preset orders the barista makes in seconds. If that is what you want, you say 'latte' and you are done. If you want oat milk, half the foam, an extra shot, and a sprinkle of cinnamon on top, you have to spell out each step. The preset menu does not have a button for it. LangGraph's create_react_agent is the preset menu. A standard tool-calling loop with one well-known shape. The hand-written StateGraph is the custom order. More typing, but you can spell out every step the way you want.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
LangGraph ships two surfaces for building agents: create_react_agent (a prebuilt that encapsulates the most common loop) and StateGraph (the lower-level primitive that lets you draw any graph). Choosing between them is one of the first decisions in a LangGraph project, and the wrong choice shows up either as boilerplate you did not need or as a prebuilt you cannot extend.
This dive walks through the loop the prebuilt encodes, the state schema it locks you into, the four reasons to graduate to a hand-rolled StateGraph, and the migration pattern that preserves checkpoints when you do.
The ReAct loop the prebuilt encodes
create_react_agent is a single function that assembles a two-node StateGraph: a model node that calls the LLM, a tools node that executes any tool calls the model emitted, and a conditional edge that loops back to the model after tools or ends if there were no tool calls.
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model='gpt-5.5')
agent = create_react_agent(model, tools=[search_tool, calc_tool], checkpointer=memory)
result = agent.invoke(
{'messages': [HumanMessage('What is the current temperature in Paris in F?')]},
config={'configurable': {'thread_id': 'thread-1'}},
)
Under the hood the state is MessagesState, a TypedDict with a single key messages whose reducer appends to the list. The agent loop reads the latest message, decides if it is a tool call, runs the tools, appends the results, and re-invokes the model. The loop terminates when the model emits a message with no tool calls. The checkpointer persists the state at every super-step so the run is resumable.
This covers the canonical 'tool-using assistant' shape that ~70% of agent apps need. The prebuilt's value is not novelty. It is the well-tested boilerplate, the streaming defaults, the LangSmith / Langfuse integration that lands without configuration, and the consistent interrupt API.
Situations where this technique stops working.
2–4 min · Everything important, quickly.
| Axis | create_react_agent | Hand-written StateGraph |
|---|---|---|
| State schema | Messages-only (fixed) | Any TypedDict you define |
| Topology | model → tools → model | Any nodes and edges you need |
| Custom routing | No | Conditional edges, per-output dispatch |
| Interrupts | interrupt_before_tools toggle | interrupt_before / after any node |
| Parallel branches | No | Yes (super-step + reducers) |
| Best for | Tool-using agent with no custom flow | Multi-agent, plans, structured state, HITL |
Real products, models, and research that use this idea.
- LangChain's own agent templates default to create_react_agent for tool-using bots, swapping to StateGraph for the supervisor and swarm templates.
- LangGraph Studio shows create_react_agent runs as a fixed two-node graph; custom StateGraphs render with whatever topology the developer drew.
What an interviewer would ask next. Try answering before peeking at the approach.
QHow would you migrate from create_react_agent to a hand-written StateGraph without losing checkpoint compatibility?
Mirror the prebuilt's MessagesState in your TypedDict, attach the same checkpointer, and preserve the messages key reducer. Then add your custom fields and routing. Existing checkpoints replay because the messages slice is byte-compatible.
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Calling create_react_agent deprecated and writing a StateGraph for a vanilla tool-calling loop. 40 lines of boilerplate the prebuilt would have given you for free.
60 second bullets to scan on the way to the call.
The ReAct loop shape (LLM, tool, LLM, done)
What state schema create_react_agent locks you into
Primary sources. Browse if you want the original framing.
Same topic, related formats. Practice these next.