AnyLearn
All lessons
AIintermediate

LangGraph: stateful, graph-based LLM workflows

What LangGraph adds over plain LangChain, how its nodes-and-edges model maps to real agent patterns (loops, branches, human approvals), and when to reach for it versus a simpler control flow.

Not signed in โ€” your progress and quiz score won't be saved.
Lesson progress1 / 8

Why LangGraph exists

Plain LangChain chains are linear: input flows through a sequence of steps, each producing the next input. That works for simple RAG, but breaks for anything with branches, loops, or human intervention.

LangGraph is LangChain's answer: a small graph library where nodes are functions (often LLM calls or tools) and edges are routing rules between them. You define the graph; LangGraph runs it. It handles state, checkpointing, branching, cycles, and human-in-the-loop interruptions out of the box.

If LangChain is "chain together LLM calls", LangGraph is "build a state machine where the transitions are decided by LLMs".

Full lesson text

All 8 steps on one page โ€” for reading, reference, and search.

Show

1. Why LangGraph exists

Plain LangChain chains are linear: input flows through a sequence of steps, each producing the next input. That works for simple RAG, but breaks for anything with branches, loops, or human intervention.

LangGraph is LangChain's answer: a small graph library where nodes are functions (often LLM calls or tools) and edges are routing rules between them. You define the graph; LangGraph runs it. It handles state, checkpointing, branching, cycles, and human-in-the-loop interruptions out of the box.

If LangChain is "chain together LLM calls", LangGraph is "build a state machine where the transitions are decided by LLMs".

2. The three core primitives

Almost everything in LangGraph is one of:

  1. State โ€” a single Python dict (typically a TypedDict) that flows through the graph. Each node receives state and returns updates to it.
  2. Nodes โ€” Python functions or LangChain Runnables. They read state, do work, return new state pieces.
  3. Edges โ€” connections between nodes. Three flavours: normal edges (always go from A โ†’ B), conditional edges (a function chooses the next node based on state), and the special END node (terminates the run).

That's the whole API. Everything else is a recipe on top.

3. Minimal example

A two-node graph that asks an LLM, then conditionally calls a tool:

from langgraph.graph import StateGraph, END
from typing import TypedDict

class State(TypedDict):
    messages: list
    needs_search: bool

def think(state: State) -> dict:
    reply = llm.invoke(state["messages"])
    return {"messages": state["messages"] + [reply], "needs_search": "search:" in reply.content}

def search(state: State) -> dict:
    results = web_search(state["messages"][-1].content)
    return {"messages": state["messages"] + [results]}

g = StateGraph(State)
g.add_node("think", think)
g.add_node("search", search)
g.set_entry_point("think")
g.add_conditional_edges("think", lambda s: "search" if s["needs_search"] else END)
g.add_edge("search", "think")
app = g.compile()

think decides whether to search. If yes, the graph cycles back through search and into think again. If no, it terminates.

4. The graph from the example

Two nodes, one cycle, one terminal edge.

flowchart LR
  Start["Start"] --> Think["think"]
  Think --> Decide["Conditional"]
  Decide --> Search["search"]
  Search --> Think
  Decide --> EndNode["END"]

5. State updates are reducers, not assignments

A subtle but important detail: nodes don't replace state, they update it. When a node returns {"messages": [...]}, LangGraph applies a reducer to merge the update with existing state.

The default reducer is overwrite. For lists, you typically want concatenation, which you declare with the add_messages reducer:

from langgraph.graph.message import add_messages
from typing import Annotated

class State(TypedDict):
    messages: Annotated[list, add_messages]

Now every node that returns {"messages": [new_msg]} appends rather than replaces. This is the LangChain-y idiom and worth getting right early โ€” wrong reducer semantics is the #1 source of "why did my history disappear?" bugs.

6. Checkpointing and resumability

LangGraph can persist state after every step. Pass a checkpointer when you compile:

from langgraph.checkpoint.sqlite import SqliteSaver

app = g.compile(checkpointer=SqliteSaver.from_conn_string("checkpoints.db"))
result = app.invoke({"messages": [...]}, config={"configurable": {"thread_id": "abc123"}})

This is how you get:

  • Resumable conversations โ€” close the tab, come back, pick up the same thread_id.
  • Time travel โ€” inspect or replay any prior step in the run.
  • Human-in-the-loop โ€” pause at a node, wait for human input, resume.

For production, swap SqliteSaver for PostgresSaver. Same API.

7. Human-in-the-loop interrupts

LangGraph's killer feature is its built-in handling of pauses for human review. Mark a node as interrupt_before:

app = g.compile(checkpointer=..., interrupt_before=["send_email"])

When the graph reaches send_email, execution stops. State is checkpointed. Your UI shows the user what's about to happen. The user approves; you call app.invoke(None, config=...) to resume, or modify state first to change the proposal.

This pattern works for any sensitive operation โ€” approving a refund, posting to a public channel, deploying a config change. The graph guarantees the dangerous step doesn't run until you explicitly continue.

8. When LangGraph is overkill

Reach for LangGraph when you have:

  • Real branching logic the LLM decides on
  • Cycles ("keep refining until this condition")
  • A multi-step process where you need to pause for human approval
  • State that must persist across sessions
  • Multi-agent coordination with explicit handoffs

Don't reach for it when you have:

  • A linear RAG pipeline (use a plain LangChain chain or just raw API calls)
  • A single-shot prompt with structured output (use the SDK directly)
  • A short tool-using agent (the SDK's built-in agent loop is fine)

LangGraph adds dependencies, a learning curve, and a state-machine mental model. It's worth it for genuinely graph-shaped problems. For 80% of LLM apps, it's the wrong abstraction.

Check your understanding

The lesson ends with a 5-question quiz. Take it in the player above to see your score.

  1. What's the main capability LangGraph adds beyond plain LangChain?
    • Faster LLM inference
    • Branching, cycles, and stateful workflows with checkpointing
    • Built-in vector databases
    • Automatic prompt optimisation
  2. Why does the `add_messages` reducer matter when you declare your state?
    • It speeds up LLM calls
    • It makes the list serialisable
    • It tells LangGraph to append to the list rather than overwrite it on each node update
    • It enables encryption of message history
  3. You want a graph that retries an LLM call until it produces JSON that parses. How do you express "retry" in LangGraph?
    • Use a `for` loop in Python
    • Use a conditional edge from the validator node back to the LLM node
    • Set `max_retries` on the compile call
    • LangGraph doesn't support retries
  4. Which LangGraph feature is the cleanest fit for an "approve before sending email" UX?
    • A conditional edge
    • A checkpointer combined with `interrupt_before` on the send node
    • A subgraph
    • A larger state object
  5. Your app does a single retrieval, then a single LLM answer call. Should you use LangGraph?
    • Yes โ€” LangGraph is best practice for all LLM apps
    • Yes โ€” only LangGraph supports retrieval
    • No โ€” that's a linear pipeline; LangGraph's graph model is overkill
    • Only if the user count is high

Related lessons

AI
advanced

Loop engineering: verification, orchestration, and anti-patterns

Make loops trustworthy. Adversarial verification panels, sub-agent orchestration, loop-until-dry for unbounded discovery, loop-until-budget for paid depth, multi-modal sweeps with diverse prompts, eval-driven cap selection, and the five anti-patterns โ€” silent caps, infinite plans, drift, premature termination, thrashing โ€” that ship to prod more than they should.

9 stepsยท~14 min
AI
advanced

Designing a Production Agent Harness

Move beyond toy ReAct loops. Learn how to build a production-grade agent harness with a robust control loop, tool registry, schema validation, retry logic, token budgets, abort signals, and a persistent journal that survives crashes.

11 stepsยท~17 minยทaudio
AI
intermediate

Loop engineering in production: state, errors, and budgets

Once an agent loop runs past a few iterations the failure surface shifts: state bloats, tool calls fail in three distinct ways, retries multiply if you stack them wrong, and unbounded budgets rack up four-figure bills overnight. The state, errors, and budgets you need to make the loop survive contact with reality.

8 stepsยท~12 min
AI
beginner

Loop engineering basics: the agent control loop

How LLM agents actually run: the iterative prompt-action-observation loop, the ReAct shape, the smallest tool-calling loop in twelve lines, why you always stack three termination layers, what the model sees on iteration N, and when a single prompt is the better answer.

8 stepsยท~12 min