Lesson 3 of 8 · 30 min

LangGraph Tutorial: Building Stateful Agent Graphs

Model an agent as a graph of nodes that share state, then add checkpoints and a human pause with LangGraph.

In this lesson1. What LangGraph is2. State, nodes and edges3. Durable execution and human in the loop4. When LangGraph is a good fit

What LangGraph is

LangGraph describes itself on PyPI as a low level orchestration framework for building stateful agents. It is published under the MIT licence, the Python package requires Python 3.10 or later, and a JavaScript version is available as the @langchain/langgraph package. LangChain's documentation notes that LangGraph does not require LangChain, so you can use it with any model client you like.

The idea is simple. Instead of hiding the agent loop inside one function, you draw it as a graph. Each node is a Python function that reads the shared state and returns an update. Edges decide which node runs next. Because the control flow is explicit, you can see and test every path, which matters when a loan pre check agent for a Bengaluru fintech must always run a compliance step before replying.

State, nodes and edges

A graph starts with a state schema. LangGraph ships a ready made MessagesState for chat style agents, and you can define your own with a TypedDict. Nodes are functions that receive the state and return the keys they want to change. Edges connect nodes, with START and END marking the entry and exit. Conditional edges choose the next node from the state, which is how you build branches and loops, such as returning to the model node after a tool node runs.

The example below is the minimal graph from the LangGraph overview page. It uses a mock function instead of a real model so you can run it with no API key. The node is registered under its function name, which is why the edges refer to the string mock_llm. Once you understand it, replace mock_llm with a node that calls your model and add a tool node with a conditional edge back to it.

python · illustrative
# Illustrative, check the official docs for the current API
# From the LangGraph overview: docs.langchain.com/oss/python/langgraph
from langgraph.graph import StateGraph, MessagesState, START, END


def mock_llm(state: MessagesState):
    return {"messages": [{"role": "ai", "content": "hello world"}]}


graph = StateGraph(MessagesState)
graph.add_node(mock_llm)
graph.add_edge(START, "mock_llm")
graph.add_edge("mock_llm", END)
graph = graph.compile()

graph.invoke({"messages": [{"role": "user", "content": "hi"}]})

Durable execution and human in the loop

LangGraph's documentation lists durable execution as a core benefit: state is checkpointed so a run can resume after a failure or a pause. You enable this by compiling the graph with a checkpointer and passing a thread_id in the config. The same mechanism powers human in the loop. Calling interrupt() inside a node pauses the run and returns a payload to your application. You resume later by invoking the graph with Command(resume=value) and the same thread_id.

The interrupts documentation lists rules worth memorising before you build an approval flow. A checkpointer is required, and InMemorySaver is for development only, because it loses everything when the process stops. On resume, the node runs again from the beginning, so code before interrupt() must be safe to repeat; never send an email there. Payloads should be JSON serialisable. Do not wrap interrupt() in a bare try and except block, because it would catch the pause signal.

python · illustrative
# Illustrative, check the official docs for the current API
from typing_extensions import TypedDict
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import END, START, StateGraph
from langgraph.types import Command, interrupt


class State(TypedDict):
    refund_inr: int
    approved: str


def approval_node(state: State):
    answer = interrupt(f"Approve refund of {state['refund_inr']} INR?")
    return {"approved": answer}


builder = StateGraph(State)
builder.add_node("approval", approval_node)
builder.add_edge(START, "approval")
builder.add_edge("approval", END)
graph = builder.compile(checkpointer=InMemorySaver())  # use a durable store in production

config = {"configurable": {"thread_id": "refund-1042"}}
result = graph.invoke({"refund_inr": 2500, "approved": ""}, config)
print(result["__interrupt__"])  # shown to the reviewer
result = graph.invoke(Command(resume="yes"), config)

When LangGraph is a good fit

Choose LangGraph when you need control over the path more than you need speed of setup. It suits long running processes that must survive restarts, flows that pause for approval, and systems where auditors want to see exactly which steps can happen in which order. The documentation also points to LangSmith for tracing and evaluation and to LangSmith Deployment for hosting, though the open source library runs anywhere Python or Node.js runs.

The trade off is that you write more code than with higher level tools. A simple FAQ bot does not need a graph. A good habit is to sketch your graph on paper first, with every node and edge, before writing code. If the sketch is a single box with a loop, a simpler SDK may serve you better. XDQ Labs engineers often use that paper sketch in client workshops because non technical stakeholders can read it.

  • Good fit: long running, auditable processes with approval steps.
  • Good fit: agents whose control flow you want to test node by node.
  • Weaker fit: a single prompt with one or two tools and no pauses.
Key takeaways
  • LangGraph models an agent as nodes that update shared state, joined by edges.
  • It is MIT licensed, available for Python and JavaScript, and does not require LangChain.
  • Checkpointers plus a thread_id give durable, resumable runs.
  • interrupt() and Command(resume=...) implement human approval pauses.
  • Code before interrupt() reruns on resume, so keep it idempotent.
Try it

Extend the approval graph with a second node that only runs when the reviewer answers yes, using a conditional edge. Restart your Python process between the pause and the resume using a persistent checkpointer from the docs, and confirm the run continues from the saved state.

Check your understanding

1. What must you provide to resume a paused LangGraph run?

2. What happens to the code before interrupt() when a node resumes?

3. Which statement about LangGraph and LangChain is correct?

← PreviousModel Context Protocol (MCP) Explained, With a Minimal Python ServerNext lesson →CrewAI Tutorial: Multi-Agent Teams With Crews and Flows
XDQ xAgents · AI lab for your company
While you sleep, your AI agents keep working.

From USD 49 or INR 2,999 a month. A person approves every step.

Start free, 14 days →See every agent300 credits · no card needed · prices include taxes🚀 Startup? Free landing page for 30 days →Need custom software or a security review? Send a brief →