In This Guide
Key Takeaways
- LangGraph is a graph runtime for LLM apps — nodes update a shared state, edges decide what runs next, cycles are first-class
- It is the right tool when your control flow has branches, loops, retries, or human-in-the-loop — which describes nearly every real agent
- StateGraph + TypedDict + checkpointer is the production-grade triplet — once you internalize it, the framework gets out of the way
- The honest learning curve is one to two weeks; the payoff is debuggable, resumable, observable agents instead of black-box prompt loops
- For straight-through RAG or single-tool calls, a plain LangChain runnable or hand-coded function is often simpler and cheaper
- By 2026 LangGraph powers production agents at LinkedIn, Klarna, Uber, Replit, and Elastic — it is no longer experimental
What Is LangGraph?
LangGraph is an open-source Python (and JavaScript) library from LangChain Inc. for building stateful, multi-actor LLM applications as directed graphs. You define a typed shared state, write nodes as plain functions that read and update that state, and wire them together with edges that can be conditional, cyclic, or interruptible. The runtime executes the graph, persists state at every step, and gives you streaming, retries, time-travel debugging, and human-in-the-loop control out of the box.
The framing matters. LangChain gives you primitives — chat models, prompts, retrievers, output parsers, tool wrappers — and a Runnable composition layer (LCEL) for chaining them linearly. That works beautifully for "prompt → model → parser" pipelines. It breaks down the moment you need a cycle, a conditional branch, or a pause for human approval. LangGraph fills exactly that gap: it is the agent runtime that sits on top of LangChain primitives.
The mental shift is from "chain" to "graph." A chain is a linear pipeline; data flows in one direction, each step runs once. A graph is a state machine; nodes can run multiple times, route conditionally, fan out and back in, and pause indefinitely waiting for an external event. Almost every real agent you have ever built — a ReAct loop, a planner-executor, a self-reflective RAG pipeline, a multi-agent supervisor — is more naturally a graph than a chain.
Why LangGraph in 2026
The 2023–2024 wave of LLM apps was mostly chains. The 2025–2026 wave is mostly agents — and agents need a runtime, not a pipeline.
Three forces pushed LangGraph from "interesting" in 2024 to "default" in 2026. First, the workflows that customers actually want — research assistants, code generators, customer support deflectors, document processors — almost all require cycles. The agent calls a tool, looks at the result, decides whether to call another tool, eventually answers. Vanilla LangChain chains cannot express this without ugly recursion or hand-rolled while-loops. LangGraph makes the loop a first-class citizen.
Second, production agents need persistence. When a user starts a conversation on Monday and resumes it on Wednesday, the agent has to pick up exactly where it left off — same memory, same in-flight tool call, same scratch state. LangGraph's checkpointer abstraction makes that a one-line change rather than a multi-week engineering project. Pair it with PostgresSaver and you have durable, multi-tenant agent state without writing custom session management.
Third, real agents need humans in the loop. The financial trade needs an analyst's approval. The customer-facing email needs a manager to glance at it. The infrastructure change needs a maintainer to confirm. LangGraph's interrupt() primitive lets a node pause the graph, surface a question to a human, and resume on their reply — all with the rest of the state preserved automatically. This is the feature that made LangGraph go from "we use it for prototypes" to "we ship it to customers."
Pro Tip: Type Your State With TypedDict (or Pydantic)
The single highest-leverage habit in LangGraph is defining your state as a typed schema — a TypedDict or Pydantic model — from day one. This gives you autocomplete in your IDE, runtime validation when you wire in a Pydantic model, and clear contracts between nodes. Untyped dict state works for a 30-line demo and becomes unmaintainable at 300 lines. Treat the state schema like an API contract: every field documented, every reducer (the Annotated[..., add_messages] pattern) intentional. Future-you debugging a stuck agent at 2 a.m. will thank present-you.
Core Concepts: State, Nodes, Edges
LangGraph has exactly four primitives you must internalize: State, Nodes, Edges, and the compiled Graph. Everything else — checkpointers, interrupts, subgraphs, streaming — is a feature layered on top.
State
State is a dictionary-like object that flows through the graph. You declare its schema with a TypedDict (or Pydantic model). Every node receives the current state and returns a partial dict of updates. By default, returned fields overwrite the previous value, but for accumulating fields like a message history you use a reducer — a function that says "merge new values into old values rather than replace them." The standard reducer is add_messages for message lists; you can write your own for any field.
Nodes
A node is just a Python function (or any callable) that takes the current state and returns a dict of updates. That is the entire contract. Nodes can call LLMs, hit databases, run tools, do nothing — LangGraph does not care. The framework's job is to schedule them, persist their outputs, and route to the next one.
Edges
Edges connect nodes. There are three flavors. Static edges say "after node A always run node B." Conditional edges attach a routing function to a node: after the node runs, the function inspects the state and returns the name of the next node (or END). This is how you express "if there is a tool call, route to the tools node; otherwise end." The special nodes START and END mark entry and exit points.
The Compiled Graph
You build a StateGraph, add nodes and edges, then call .compile(). The compiled graph is a runnable object — call .invoke(input) for a single run, .stream(input) for token-and-step-level streaming, or .astream(input) for the async variant. The compiled graph is also where you attach a checkpointer for persistence. Once compiled, the graph is immutable; rebuild it if you need to change topology.
Installation and First Graph
Installation is one pip command. The first graph is fewer than 30 lines and demonstrates every core primitive.
# Install LangGraph and a model provider pip install langgraph langchain-anthropic # Set your API key (or use any provider — OpenAI, Google, etc.) export ANTHROPIC_API_KEY="your-key-here"
Now the smallest possible interesting graph: a three-node pipeline where the first node sets a topic, the second node generates a draft, and the third node polishes it. The example shows state, nodes, and edges in their simplest form.
from typing import TypedDict from langgraph.graph import StateGraph, START, END from langchain_anthropic import ChatAnthropic # 1. Define the shared state schema class DraftState(TypedDict): topic: str draft: str final: str llm = ChatAnthropic(model="claude-sonnet-4-6") # 2. Define nodes — each takes state, returns a dict of updates def choose_topic(state: DraftState) -> dict: return {"topic": state["topic"] or "the dignity of small work"} def write_draft(state: DraftState) -> dict: msg = llm.invoke(f"Write a 3-sentence essay on {state['topic']}.") return {"draft": msg.content} def polish(state: DraftState) -> dict: msg = llm.invoke(f"Polish this:\n\n{state['draft']}") return {"final": msg.content} # 3. Wire the graph graph = StateGraph(DraftState) graph.add_node("choose_topic", choose_topic) graph.add_node("write_draft", write_draft) graph.add_node("polish", polish) graph.add_edge(START, "choose_topic") graph.add_edge("choose_topic", "write_draft") graph.add_edge("write_draft", "polish") graph.add_edge("polish", END) app = graph.compile() result = app.invoke({"topic": "", "draft": "", "final": ""}) print(result["final"])
Run it. You have a working LangGraph application. Notice what is not in the code: no manual state-passing, no explicit "now run polish" call, no error handling boilerplate. The runtime handles all of that. This same shape — typed state, function-shaped nodes, explicit edges, compile-then-invoke — scales from 30 lines to 3,000 without ever changing form.
Building a ReAct Agent with LangGraph
The ReAct (Reason + Act) pattern is the canonical agent loop: the model reasons, decides whether to call a tool, calls it, looks at the result, and repeats until it can answer. LangGraph expresses this in two nodes and one conditional edge.
The architecture is simple. There is an agent node that calls an LLM bound to a set of tools — when the model wants to use a tool, it returns a message with tool_calls. There is a tools node (LangGraph ships ToolNode as a built-in) that executes those tool calls and appends results to state. There is a conditional edge after the agent that asks: "did the last message have tool calls?" If yes, route to tools; if no, route to END. After the tools node runs, an unconditional edge sends control back to the agent. That is the full loop.
from typing import Annotated, TypedDict from langgraph.graph import StateGraph, START, END from langgraph.graph.message import add_messages from langgraph.prebuilt import ToolNode from langchain_core.tools import tool from langchain_anthropic import ChatAnthropic # State accumulates messages across the loop class AgentState(TypedDict): messages: Annotated[list, add_messages] # Define a tool — anything decorated with @tool is callable by the agent @tool def get_weather(city: str) -> str: """Return the current weather in a city.""" # In production this hits a weather API return f"It is 72F and sunny in {city}." tools = [get_weather] llm = ChatAnthropic(model="claude-sonnet-4-6").bind_tools(tools) # Agent node: call the LLM with the running message history def agent(state: AgentState) -> dict: response = llm.invoke(state["messages"]) return {"messages": [response]} # Conditional router: if the last message has tool_calls, run tools; else end def should_continue(state: AgentState) -> str: last = state["messages"][-1] return "tools" if getattr(last, "tool_calls", None) else END # Wire the graph — the cycle agent -> tools -> agent is the ReAct loop graph = StateGraph(AgentState) graph.add_node("agent", agent) graph.add_node("tools", ToolNode(tools)) graph.add_edge(START, "agent") graph.add_conditional_edges("agent", should_continue, ["tools", END]) graph.add_edge("tools", "agent") app = graph.compile() result = app.invoke({"messages": [("user", "What is the weather in Denver?")]}) print(result["messages"][-1].content)
Two things to notice. The Annotated[list, add_messages] pattern is how LangGraph knows to append messages to state instead of overwriting them — this is the reducer mechanism in action. And the cycle agent → tools → agent is what makes this a graph rather than a chain. A LangChain runnable cannot express this without manual loop management; LangGraph treats it as the natural shape of an agent.
For most applications, you do not even need to write this scaffolding yourself. langgraph.prebuilt.create_react_agent(model, tools) ships a production-grade ReAct agent in one call. Use the manual version when you need to customize the loop — adding pre-flight validation, retry logic, custom routing — and use the prebuilt when you just need a working agent.
Persistence and Human-in-the-Loop
Persistence is what separates a demo agent from a production agent. Human-in-the-loop is what separates a production agent from a deployable one. LangGraph treats both as configuration, not architecture.
Checkpointers: SqliteSaver, PostgresSaver, RedisSaver, MemorySaver
A checkpointer persists state at every step of graph execution. Pass one to .compile(checkpointer=...) and your graph is now resumable. Each invocation specifies a thread_id in the config; subsequent calls with the same thread_id pick up exactly where the previous run left off, including in-flight tool calls and pending interrupts. LangGraph ships MemorySaver for tests, SqliteSaver for single-process apps, and PostgresSaver / RedisSaver for multi-tenant production. The checkpointer is also what powers time travel — you can replay from any prior step or fork a new branch from history.
from langgraph.checkpoint.sqlite import SqliteSaver # Persist every step to a local SQLite database checkpointer = SqliteSaver.from_conn_string("agent_state.db") app = graph.compile(checkpointer=checkpointer) # Each conversation is a thread — same thread_id resumes prior state config = {"configurable": {"thread_id": "user-42-conversation-7"}} # First turn app.invoke({"messages": [("user", "My name is Bo.")]}, config) # A week later, in a different process — agent remembers result = app.invoke({"messages": [("user", "What is my name?")]}, config) print(result["messages"][-1].content) # "Your name is Bo."
Human-in-the-Loop with interrupt()
The interrupt() function pauses graph execution and surfaces a value to the caller. The caller inspects it, decides what to do, and resumes by sending a Command(resume=...) back. Combined with a checkpointer, this means a graph can be paused for milliseconds, hours, or weeks while a human reviews, approves, edits, or rejects — and resume cleanly when the human responds. The pattern works for approval gates ("ok to send this email?"), edit-then-continue flows ("here is the draft, fix anything"), and full intervention ("override the agent's plan").
from langgraph.types import interrupt, Command def approve_action(state: AgentState) -> dict: proposed = state["messages"][-1].content # Pause the graph; surface the proposed action to the operator decision = interrupt({"proposed_action": proposed}) if decision["approved"]: return {"messages": [("system", "Approved by operator.")]} return {"messages": [("system", f"Rejected: {decision['reason']}")]} graph.add_node("approve", approve_action) # ... wire the rest ... app = graph.compile(checkpointer=checkpointer) # First call: graph runs until interrupt(), then returns app.invoke({"messages": [("user", "Send a refund email.")]}, config) # Operator reviews, then resumes with a Command app.invoke(Command(resume={"approved"": True}), config)
The combination — durable checkpointer plus interrupts — is the unlock. You can build an agent that emails an analyst, sleeps until the analyst clicks "approve" in your dashboard the next morning, and resumes the workflow without losing a single piece of state. That is the kind of system that ships to enterprise customers.
LangGraph vs LangChain vs CrewAI vs AutoGen
LangGraph is not the only agent framework in 2026 — but the alternatives optimize for different things. Understanding the trade-offs saves you weeks of mid-project regret.
| Capability | LangGraph | LangChain (LCEL) | CrewAI | AutoGen | smolagents |
|---|---|---|---|---|---|
| Control flow model | Explicit graph | Linear chain | Role-based crews | Multi-agent chat | Code-as-action loop |
| Cycles / loops | First-class | Manual | Implicit | Implicit | First-class |
| Typed shared state | Yes (TypedDict/Pydantic) | Partial (Runnable I/O) | Limited | Limited | Limited |
| Human-in-the-loop | Native (interrupt) | Manual | Limited | Manual | Manual |
| Persistence / checkpoints | Native (Sqlite/Postgres/Redis) | No | Limited | Limited | No |
| Streaming (token + step) | Yes | Yes (token only) | Limited | Limited | Limited |
| Observability | LangSmith integrated | LangSmith integrated | External | External | External |
| Learning curve | 1–2 weeks | Days | Days | 1 week | Hours |
| Production readiness (2026) | Mature (1.0+) | Mature | Maturing | Research-leaning | Lightweight |
| Best for | Stateful, controllable agents | Linear pipelines, RAG | Role-played multi-agent demos | Conversational multi-agent | Single-agent code execution |
The honest decision tree: if your control flow is a straight line — prompt, model, parser, return — use plain LangChain. If you need cycles, branches, persistence, or HITL, use LangGraph. If you want a quick role-playing multi-agent demo with minimal code, CrewAI is faster to prototype but harder to control in production. AutoGen excels at conversational research agents but has a different mental model. smolagents is excellent for lightweight code-execution loops where the agent's output is Python.
The teams I have seen migrate away from CrewAI and AutoGen and toward LangGraph almost always cite the same reason: when something goes wrong in production, they need to know exactly which node ran, what state it received, and what state it produced. The explicit graph makes that trivial. The other frameworks abstract too much.
Real Production Patterns
Five patterns cover roughly 90% of the LangGraph applications shipping in 2026. Internalize these and you will recognize the shape of almost any agent you need to build.
1. Multi-Agent Supervisor
One supervisor LLM looks at the request and routes to one of several specialist agents — a researcher, a writer, a coder, a reviewer — each implemented as its own subgraph or node. After the specialist runs, control returns to the supervisor, which decides whether to dispatch another specialist or finish. This is the right pattern when the work decomposes into discrete skills and you want the supervisor to make the orchestration decision dynamically rather than hard-coding the order. LangGraph's Command primitive (which lets a node both update state and specify the next node) makes supervisor patterns clean.
2. Hierarchical Teams
A supervisor of supervisors. The top-level supervisor routes to "research team" or "engineering team," each of which is its own LangGraph graph with its own internal supervisor and specialists. This pattern is overkill for most use cases but invaluable when you have genuinely distinct domains — e.g., a customer support agent that has a billing team, a technical team, and a sales team, each with their own tools and policies.
3. Plan-and-Execute
A planner node generates a multi-step plan upfront, an executor node runs each step in sequence (often as tool calls), and a replanner node optionally revises the plan when reality diverges from expectation. This pattern shines for tasks where the steps are knowable in advance — research reports, code generation across multiple files, structured data extraction pipelines — and produces dramatically better results than letting a single ReAct loop reason its way through everything.
4. RAG With Reflection
Retrieve documents, generate an answer, then route to a critic node that grades the answer ("is this grounded? is this complete?"). If the critic finds problems, route back to retrieval with a refined query, or to generation with corrective context. The cycle continues until the critic approves or a hop limit is reached. This pattern (often called Self-RAG or Corrective RAG in the literature) materially improves answer quality on long-tail questions and is one of the most-shipped LangGraph patterns in production.
5. Code Generation Loops
The agent writes code, executes it in a sandbox, observes the error or output, and revises. This is the loop powering everything from data-analysis agents to autonomous coding assistants. LangGraph's cycle support and persistence make this practical: you can resume a stuck code-gen agent days later, time-travel back to before a regression, and stream tokens to the UI while the loop runs.
Best Practices, Pitfalls, and When NOT to Use It
The biggest LangGraph lessons are not in the docs. They are in the patterns that fail in production and the patterns that survive.
Best Practices
- Type your state from day one. TypedDict or Pydantic. Never raw
dict. - Make nodes pure when possible. Take state, return updates. Side effects belong in tool nodes, not in your routing logic.
- Use the prebuilt ReAct agent until you can't.
create_react_agentcovers most use cases. Roll your own only when you need behavior the prebuilt does not give you. - Always attach a checkpointer in production. Even if you do not need persistence today, you will need it in three months. Adding it later means rewriting routing assumptions.
- Use LangSmith for tracing. Debugging an agent without traces is debugging blind. The free tier covers individual development.
- Think about token cost per node. Every node that calls an LLM adds latency and dollars. Profile early; collapse nodes that don't earn their cost.
- Set hop limits on cycles. Use a counter in state and a conditional edge that exits if the loop runs more than N times. Runaway cycles are the most common LangGraph production incident.
Common Pitfalls
State mutation bugs. Returning a mutated copy of state instead of just the changed fields can silently overwrite data other nodes set. Always return only the keys you intend to change.
Forgetting reducers. If a node returns {"messages": [new_msg]} without an add_messages reducer on that field, you wipe the message history. The error is silent until you wonder why your agent has amnesia.
Over-graphifying. Not every problem is a graph. If your control flow is genuinely linear, LangGraph adds ceremony without value.
Conditional-edge complexity creep. Once you have five conditional edges branching to seven nodes, the graph becomes harder to reason about than the if-else it replaced. Refactor into subgraphs.
When NOT to Use LangGraph
Single-step LLM calls. If you have a function that takes input, calls a model, returns output — write the function. LangGraph is overkill.
Pure RAG without reflection. Retrieve → format → generate → return is a chain. LangChain LCEL is simpler.
Hand-coded loops where the structure is fixed and small. A 20-line Python while-loop is easier to read, debug, and modify than a 4-node graph that does the same thing. Reach for LangGraph when the loop has branches, persistence, or HITL — not just because "agents are graphs."
Latency-critical real-time inference. The graph runtime adds modest overhead per step. For sub-100ms response budgets, the abstraction tax may matter; benchmark before committing.
Sources: LangGraph Documentation, LangChain Blog, LangChain Case Studies. API surface and pricing reflect public docs as of April 2026 and may change; always check the official docs before pinning a version in production.
Explore More Guides
LangGraph wins where control matters more than convenience — and that is most production agents.
When LangGraph wins: any application where you need to know exactly which step ran, what state it had, and how to resume after a failure. That is every customer-facing agent we have shipped. The explicit graph topology is genuinely a feature, not ceremony — when an agent does something unexpected at 3 a.m., you trace through nodes and edges, not through opaque "agent thoughts." The checkpointer + interrupt combination is the single biggest reason teams pick LangGraph over CrewAI or AutoGen for production: a graph that can be paused for a week and resumed cleanly is the kind of thing enterprise buyers can put in front of regulators.
When LangGraph is overkill: a single LLM call with a prompt template, a straight-through RAG pipeline, a one-shot extraction job. Reaching for LangGraph here is the equivalent of using Kubernetes to run a static blog. The framework rewards you for the complexity it absorbs; if you have no complexity, the abstraction is pure tax. Plain LangChain LCEL or even a hand-written function will be faster to write, faster to read, and faster to run.
The learning curve reality: budget two weeks of fumbling before the mental model clicks, and another month before your code stops looking like "chains rewritten as graphs." The first few graphs you build will overuse conditional edges, under-use subgraphs, and tangle state in ways that make debugging painful. That is normal. The cure is reading the official examples carefully, building three or four real graphs end-to-end, and resisting the urge to skip TypedDict because "I'll add types later." LangGraph rewards discipline upfront and punishes shortcuts. Once the discipline becomes habit, it is the most productive agent framework available.