← Back to blog

LangGraph Tutorial for Beginners: Build Stateful Agents (Not Toy Examples)

Skip the toy examples. This LangGraph tutorial teaches you to build stateful agents that actually work in production — with real code, no framework fluff.

LangGraph is a framework for building agents that remember what happened three turns ago. Most LangGraph tutorials show you how to build a chatbot that talks to itself. This one shows you how to build stateful agents that route decisions, persist context, and don't hallucinate every third message. Here's the production approach.

Why LangGraph Exists (And Why LangChain Isn't Enough)

LangChain treats agents like one-off queries. You send in a prompt, you get out an answer, you're done. LangGraph adds state management and loops. Understanding this distinction prevents you from building the wrong thing entirely.

The core problem LangGraph solves: a sales agent tracking a 5-turn cold-email conversation needs to remember the prospect's name, the objection they raised in turn 2, whether they said yes or no in turn 4, and what follow-up was promised. LangChain has no native way to thread that context without manually stitching it into every prompt call. You'd write the entire conversation history into each API call, ballooning your token spend and introducing context loss on error.

LangGraph's state graphs let you model multi-turn decisions as nodes and edges, not as nested if-then-else chains buried in your application code. A state object flows through the graph, persisting across every turn. Each node reads it, updates it, and passes it forward. This isn't a minor convenience. It's the difference between stateless LangChain (which breaks on anything beyond three turns) and stateful LangGraph (which can run 20-turn negotiations without losing a fact).

Core Concept: Nodes, Edges, and State

A node is a function that takes state in and returns state out. It's not a black box. An edge is a conditional router: if X happened, go to node Y; otherwise go to node Z. State is a typed dictionary or Pydantic model that flows through the entire graph.

Let's ground this. Say you're building a prospecting agent. Your state object holds prospect_name, email_sent, objection_reason, and follow_up_count. Node 1 (research_prospect) reads prospect_name from state, looks up company info, and appends findings back to state. Node 2 (draft_email) reads those findings, drafts an email, and sets email_sent to True. Node 3 (conditional router) checks if objection_reason is 'budget'. If yes, route to the discount_handler node. If no, route to the value_reframe node.

Unlike LangChain chains that forget everything after one call, your state object persists. After all nodes finish, you inspect state and see every decision, every fact, every objection across all 20 turns. This traceability is why stateful agents work in production.

Setting Up Your First LangGraph Agent: Step-by-Step

Install only what you need: pip install langgraph langchain-core. You don't need the full langchain suite.

Define your state with a TypedDict or Pydantic model. Make it explicit, not implicit. Here's the pattern:

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

class AgentState(TypedDict): prospect_name: str email_sent: bool objection_reason: str follow_up_count: int conversation_history: list

graph = StateGraph(AgentState)

Add nodes using the @graph.node decorator or graph.add_node(name, function). Keep functions under 50 lines. Add edges with graph.add_edge() for unconditional flow or graph.add_conditional_edges() for routing logic. Finally, compile with .compile(). This validates the graph and catches missing edges before runtime.

LangGraph vs LangChain: Where State Changes Everything

LangChain chains are stateless. Each call is a fresh start. If you need multi-turn context, you stitch it manually into every prompt. LangGraph graphs are stateful. You loop through nodes, state updates in place, context persists across the entire execution.

Here's a real benchmark: a 5-turn cold-email conversation with objection handling. Using LangChain, you make 5 separate API calls, each one requiring you to manually pass the entire conversation history as a system prompt. Using LangGraph, you execute 1 graph with a single state object threading through it. Your token spend drops roughly 40% because you're not re-packaging the same history into every API call.

LangChain's SimpleAgent works for tutorials. LangGraph's CompiledGraph works for production because it has built-in persistence, error recovery, and step introspection. If you're stitching context into prompts by hand, you're using LangChain. If state just flows through your graph and you never touch it manually, you're using LangGraph correctly.

Conditional Routing: The Part That Actually Matters

Routing is where stateful agents decide what to do next. It's not magic. It's a function that reads state and returns a string telling the graph which node to visit.

Example: if conversation_turn > 3 and objection_type == 'budget', route to 'discount_handler'. Otherwise route to 'value_reframe'. Your routing function looks like this:

python def route_on_objection(state: AgentState) -> str: if state["conversation_turn"] > 3 and state["objection_type"] == "budget": return "discount_handler" return "value_reframe"

graph.add_conditional_edges("assess_objection", route_on_objection, {"discount_handler": "discount_handler", "value_reframe": "value_reframe"})

Most tutorials skip this and just chain nodes linearly. But routing is what makes an agent adaptive instead of robotic. Test your routing function in isolation before wiring it into the graph.

Persistence and Recovery: Why Your Agent Won't Break

Use Supabase or Postgres for checkpointing. Save state after every node execution. LangGraph's built-in checkpointing means you can pause, resume, or replay any conversation. Not possible with stateless chains.

Real scenario: your agent is halfway through a 20-turn negotiation when Claude times out. With checkpointing, you resume from turn 11, not turn 1. Your customer sees no interruption. The prospect never knows anything broke.

Configure with one line: graph.compile(checkpointer=SqliteSaver(database='agent_state.db')). That's it. Checkpoints also let you debug: inspect what state looked like at turn 5, what decision was made, and why the agent chose it.

Common Mistakes That Kill Stateful Agents

Mistake 1: mutating state inside a node without returning it. The graph won't see the update. Mistake 2: using LangChain memory classes instead of explicit state. You lose traceability and testability. Mistake 3: forgetting to type your state. Python ducks into None, and your agent halts silently.

Mistake 4: trying to do too much in one node. If a node function is over 100 lines, split it into two nodes. Real fix: enforce immutability, test routing functions separately, validate state after each node with assertions.

From Tutorial to Production: Connecting LangGraph to Real Tools

Most tutorials end with a chatbot prompt. Production means connecting to n8n webhooks, Supabase storage, and Claude API keys. Here's the stack:

Your LangGraph agent runs inside a Next.js API route on Vercel. It calls Claude for reasoning, logs state to Supabase, and triggers n8n workflows for CRM updates when deals close. Use environment variables for API keys, not hardcoded in your graph. Add monitoring: log every node execution with timestamps, decisions, and state deltas. Your graph isn't deployed until you can restart it, inspect state 30 steps ago, and not lose a single message.

[STAT_NEEDED: percentage of LangGraph deployments that add checkpointing vs those that don't]

FAQ

What's the difference between LangGraph stateful agents and LangChain chains?

LangChain chains are stateless. Each call forgets the previous one unless you manually pass context back in. LangGraph agents maintain state throughout execution, threading it through every node automatically. LangChain requires manual context stitching; LangGraph does it for you.

Do I need to use LangGraph if I'm building a simple chatbot?

No. If your chatbot is turn-to-turn question answering with no context, LangChain is fine. Use LangGraph when you need decisions spanning 5+ turns, objection handling, or conditional routing based on conversation history.

How do I persist LangGraph state so my agent survives crashes?

Use LangGraph's built-in checkpointing with Supabase or SQLite. After every node execution, state is saved. If your agent crashes mid-execution, resume from the last checkpoint instead of restarting from turn 1.

Ready to Build

If you want to talk through applying this to your stack, book a strategy call at cognival.co/book.


Want to apply this to your business?

30-min strategy call. No pitch, real look at your stack.

Book a strategy call →