·9 min read·AlgoMindset Team

LangGraph Tutorial: Building Stateful, Multi-Step AI Agent Workflows (Free)

AI EngineeringLangGraphTutorialAI Agents

Why chains are not enough for real agents

A LangChain chain is fundamentally a pipeline: step one feeds step two feeds step three. That works well for a fixed sequence — retrieve, then summarize, then format — but it breaks down the moment your agent needs to loop ("keep calling tools until you have enough information"), branch ("if the answer needs a calculation, call the calculator tool; otherwise answer directly"), or pause for a human to approve a step before continuing.

LangGraph exists to model exactly that shape. Instead of a straight pipeline, you define a graph: a set of nodes (units of work) connected by edges (which can be conditional), all reading from and writing to a shared, typed state object. The graph runtime handles looping, branching, and — critically — persisting state so a long-running or paused agent can resume exactly where it left off.

The three core concepts: State, Nodes, Edges

State is a typed object — usually a TypedDict or a Pydantic model in Python — that represents everything the graph is tracking: the conversation so far, intermediate results, a counter, whatever your agent needs to remember between steps. Every node receives the current state and returns a partial update to it.

A Node is just a function: state in, partial state update out. It can call an LLM, call a tool, run a calculation, or do nothing but transform data. An Edge connects nodes and determines what runs next — a normal edge always goes to the same next node, while a conditional edge inspects the current state and picks the next node dynamically, which is how you implement branching and loops.

python
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, END
from langgraph.graph.message import add_messages

class AgentState(TypedDict):
    messages: Annotated[list, add_messages]
    tool_calls_made: int

def call_model(state: AgentState) -> dict:
    # Call your LLM here with state["messages"], return its response
    response = llm.invoke(state["messages"])
    return {"messages": [response]}

def call_tool(state: AgentState) -> dict:
    # Execute whichever tool the model asked for
    result = run_tool(state["messages"][-1])
    return {"messages": [result], "tool_calls_made": state["tool_calls_made"] + 1}

Conditional edges: how the loop actually branches

The routing function is where the "agent" behavior lives. After the model responds, you inspect its output: did it ask to call a tool, or did it produce a final answer? Based on that, you route to the tool node and loop back, or route to END and finish.

python
def should_continue(state: AgentState) -> str:
    last_message = state["messages"][-1]
    if last_message.tool_calls:
        return "call_tool"
    return "end"

graph = StateGraph(AgentState)
graph.add_node("call_model", call_model)
graph.add_node("call_tool", call_tool)
graph.set_entry_point("call_model")

graph.add_conditional_edges(
    "call_model",
    should_continue,
    {"call_tool": "call_tool", "end": END},
)
graph.add_edge("call_tool", "call_model")  # loop back after the tool runs

app = graph.compile()

This is the entire ReAct loop

That small graph — model, conditional edge, tool, edge back to model — is the ReAct pattern (Reason + Act) that underlies most tool-using agents, including the ones behind popular coding assistants. The model reasons about what to do, optionally acts by calling a tool, observes the result, and reasons again, until it decides it has enough to answer. The value LangGraph adds over hand-rolling this loop yourself is state typing, built-in cycle support, and — for anything running longer than a few seconds — checkpointing.

Checkpointing: why this matters for anything long-running

A checkpointer persists the graph's state after every node runs, to memory, a database, or a file. This unlocks two things that matter for production agents: resuming a crashed or interrupted run from its last checkpoint instead of starting over, and human-in-the-loop workflows where the graph pauses before a sensitive node (like sending an email or executing a trade) and waits for explicit approval before continuing — the state is safely persisted while it waits.

This is the detail that separates a toy agent demo from something you would actually run in production, and it is worth naming specifically if an interviewer asks how you would make a multi-step agent reliable: not "add try/catch," but "checkpoint state after every node so a failure loses at most one step, not the whole run."

When to reach for LangGraph vs. a plain chain

Use a plain chain when the steps are fixed and always run in the same order — no looping, no branching, no need to pause mid-run. Reach for LangGraph the moment any of the following is true: the number of steps is not known in advance (the agent decides when it is done), the flow needs conditional branching based on intermediate results, you need a human approval step in the middle of a run, or you need the agent to survive a restart without losing its place.

To go deeper on the coordination patterns that sit on top of a single graph like this — multiple specialized agents, shared memory, failure isolation across agents — our multi-agent orchestration system design lab walks through the full architecture, and our LangChain Agent Patterns premium lab covers additional executor and memory patterns beyond what is in this tutorial.