n4nAI

LangGraph vs LangChain agents: when to use each

A practical comparison of LangGraph and LangChain agents across architecture, control flow, debugging, and production readiness — with a clear verdict for each use case.

n4n Team6 min read1,344 words

Audio narration

Coming soon — every post will get a voice note here.

LangGraph and LangChain agents solve different problems. LangChain agents are a thin abstraction over ReAct-style prompting loops — good for simple tool use, brittle when you need cycles, branching, or human-in-the-loop. LangGraph is a stateful graph orchestrator built for multi-step, multi-agent workflows with explicit control flow, checkpointing, and streaming. If you’re building a chatbot that calls a few tools, LangChain agents are fine. If you’re building a coding agent, a research pipeline, or anything with loops and conditional logic, you want LangGraph.

Architecture and mental model

LangChain agents follow the classic ReAct pattern: the LLM reasons, picks a tool, observes the result, repeats. The agent executor handles the loop. You configure tools, a prompt, and maybe a memory buffer. The control flow is implicit — the LLM decides what happens next at each step.

# LangChain agent (simplified)
from langchain.agents import create_react_agent, AgentExecutor
from langchain.tools import tool

@tool
def search_web(query: str) -> str:
    ...

agent = create_react_agent(llm, tools=[search_web], prompt=prompt)
executor = AgentExecutor(agent=agent, tools=[search_web], verbose=True)
result = executor.invoke({"input": "What's the weather in Tokyo?"})

LangGraph models workflows as graphs: nodes are functions (LLM calls, tools, custom logic), edges define transitions. State moves through the graph explicitly. You can have cycles, parallel branches, conditional edges, and subgraphs. The LLM doesn’t drive control flow — your graph does.

# LangGraph (simplified)
from langgraph.graph import StateGraph, END
from typing import TypedDict, Annotated
import operator

class AgentState(TypedDict):
    messages: Annotated[list, operator.add]
    iterations: int

def call_model(state: AgentState):
    response = llm.invoke(state["messages"])
    return {"messages": [response], "iterations": state["iterations"] + 1}

def should_continue(state: AgentState):
    return "tools" if state["iterations"] < 3 else END

graph = StateGraph(AgentState)
graph.add_node("agent", call_model)
graph.add_node("tools", tool_node)
graph.add_edge("agent", "tools")
graph.add_conditional_edges("tools", should_continue)
graph.set_entry_point("agent")
app = graph.compile()
result = app.invoke({"messages": [HumanMessage("Research quantum computing")], "iterations": 0})

The difference matters. In LangChain, a loop means the LLM keeps calling tools until it decides to stop. In LangGraph, you define the loop explicitly — max iterations, exit conditions, branching logic. You can inspect and modify state at any node. You can pause, resume, or rewind.

Control flow and cycles

LangChain agents support basic loops via max_iterations and early_stopping_method. But you can’t express “run these three tools in parallel, then fan-in, then conditionally branch based on results” without fighting the framework. The agent executor is a single loop. Nested agents are possible but awkward — you’re composing executors, not graphs.

LangGraph makes parallel execution, fan-out/fan-in, and conditional branching first-class:

# Parallel tool execution, then fan-in
graph.add_node("search", search_tool)
graph.add_node("analyze", analyze_tool)
graph.add_node("summarize", summarize_node)

graph.add_edge("agent", "search")
graph.add_edge("agent", "analyze")  # both run in parallel
graph.add_edge(["search", "analyze"], "summarize")  # fan-in

Conditional edges let you route based on state:

def route(state: AgentState) -> str:
    if state["error_count"] > 2:
        return "escalate"
    if state["confidence"] < 0.7:
        return "human_review"
    return "continue"

graph.add_conditional_edges("agent", route, {
    "escalate": "escalation_node",
    "human_review": "review_node",
    "continue": "next_step"
})

This is not syntactic sugar — it changes what you can build. A coding agent that writes tests, runs them, analyzes failures, and retries with a different approach needs exactly this kind of control flow.

State management and checkpointing

LangChain agents use ConversationBufferMemory or ConversationSummaryMemory. State is a message list. You can’t easily attach arbitrary metadata (token counts, tool latencies, user preferences) without subclassing. Checkpointing is manual — you serialize the memory yourself.

LangGraph state is a typed dict. You define the schema. Checkpointing is built in via MemorySaver, SqliteSaver, or PostgresSaver. Every graph invocation produces a checkpoint. You can resume from any checkpoint, fork from a previous state, or replay with modified inputs.

from langgraph.checkpoint.sqlite import SqliteSaver

with SqliteSaver.from_conn_string("checkpoints.db") as saver:
    app = graph.compile(checkpointer=saver)
    
    # First run
    config = {"configurable": {"thread_id": "user-123"}}
    result = app.invoke({"messages": [HumanMessage("Plan a trip to Japan")]}, config)
    
    # Resume later — state is restored automatically
    result = app.invoke({"messages": [HumanMessage("Actually, make it cheaper")]}, config)
    
    # Fork from an earlier checkpoint
    checkpoints = list(saver.list(config))
    earlier_config = {"configurable": {"thread_id": "user-123", "checkpoint_id": checkpoints[0].config["configurable"]["checkpoint_id"]}}
    forked = app.invoke({"messages": [HumanMessage("Try a different approach")]}, earlier_config)

This enables human-in-the-loop workflows that are painful in LangChain: pause for approval, edit state, resume. Time-travel debugging — rewind to a checkpoint, change a parameter, replay — is trivial.

Streaming and observability

LangChain agents stream tokens via astream or astream_events. You get token-level streaming from the LLM, but tool calls and intermediate steps are opaque until complete. verbose=True prints to stdout — not production-friendly.

LangGraph streams at the graph level. astream yields state updates after each node. astream_events yields granular events: node start/end, tool calls, LLM tokens, conditional edge decisions. You can build a real-time UI showing exactly where the graph is.

async for event in app.astream_events({"messages": [HumanMessage("...")]}, config, version="v2"):
    if event["event"] == "on_chain_start":
        print(f"Node started: {event['name']}")
    elif event["event"] == "on_tool_start":
        print(f"Tool called: {event['name']} with {event['data']['input']}")
    elif event["event"] == "on_chat_model_stream":
        print(event["data"]["chunk"].content, end="")

This matters for production. You can log every transition, measure per-node latency, alert on stuck nodes, and show users a live trace. LangChain’s LangChainTracer helps but operates at a coarser granularity.

Debugging and testing

LangChain agents are hard to test deterministically. The LLM drives control flow, so the same input can produce different tool sequences. You can mock tools, but you can’t easily assert “the agent should have called search, then analyze, then summarize” because the agent might skip steps or loop unexpectedly.

LangGraph graphs are testable as pure functions. Feed a state, assert the output state. Test individual nodes in isolation. Test the full graph with controlled inputs. Because control flow is explicit, you can write integration tests that verify the exact path taken.

def test_research_graph_happy_path():
    # Mock the LLM to return predictable tool calls
    mock_llm = MockLLM(responses=[
        AIMessage(content="", tool_calls=[{"name": "search", "args": {"query": "quantum computing"}}]),
        AIMessage(content="", tool_calls=[{"name": "analyze", "args": {"text": "search results"}}]),
        AIMessage(content="Final summary"),
    ])
    
    graph = build_graph(mock_llm)
    result = graph.invoke({"messages": [HumanMessage("Research quantum computing")], "iterations": 0})
    
    assert result["iterations"] == 3
    assert len(result["messages"]) == 4  # human + 3 AI messages
    # Verify exact node execution order via checkpoints

You can also use graph.get_graph().draw_mermaid() to visualize the workflow — useful for documentation and architecture reviews.

Multi-agent patterns

LangChain supports multi-agent via initialize_agent with AgentType.OPENAI_MULTI_FUNCTIONS or by chaining executors. But there’s no built-in pattern for agent handoff, shared state, or supervisor/worker topologies. You build it yourself.

LangGraph has first-class multi-agent patterns. The create_react_agent function (yes, LangGraph has its own) produces a subgraph you can compose. Supervisor pattern:

from langgraph.prebuilt import create_react_agent

researcher = create_react_agent(research_llm, [search_tool, analyze_tool])
coder = create_react_agent(coder_llm, [write_code_tool, test_tool])
reviewer = create_react_agent(reviewer_llm, [review_tool])

def supervisor(state: AgentState) -> str:
    last_message = state["messages"][-1]
    if "research" in last_message.content.lower():
        return "researcher"
    if "code" in last_message.content.lower():
        return "coder"
    return "reviewer"

graph = StateGraph(AgentState)
graph.add_node("supervisor", supervisor_node)
graph.add_node("researcher", researcher)
graph.add_node("coder", coder)
graph.add_node("reviewer", reviewer)
graph.add_conditional_edges("supervisor", supervisor, {
    "researcher": "researcher",
    "coder": "coder",
    "reviewer": "reviewer"
})
graph.add_edge("researcher", "supervisor")
graph.add_edge("coder", "supervisor")
graph.add_edge("reviewer", END)

Each agent is a self-contained graph with its own state, tools, and checkpointer. They share the parent graph’s state via the message list. This composability is why LangGraph wins for multi-agent systems.

Ecosystem and integrations

LangChain has a massive ecosystem: 100+ tool integrations, 50+ vector stores, 30+ chat models, document loaders for everything. If you need a niche integration (Salesforce, Notion, obscure vector DB), LangChain probably has it. LangGraph uses LangChain’s integrations under the hood — create_react_agent accepts LangChain tools. You don’t lose the ecosystem.

But LangGraph adds its own prebuilt components: ToolNode for parallel tool execution, InjectedState for passing graph state to tools, ValidationNode for output validation. The langgraph-prebuilt package includes common patterns (ReAct agent, supervisor, planner-executor) as reusable graphs.

Performance and latency

Both frameworks add minimal overhead — they’re orchestration layers, not model servers. The latency difference comes from architecture:

  • LangChain agent: sequential by default. Each tool call waits for the previous. Parallel tool calling requires OpenAIFunctionsAgent with parallel_tool_calls=True (OpenAI only).
  • LangGraph: parallel execution is explicit. Fan-out/fan-in reduces wall-clock time for independent operations. Checkpointing adds ~5-15ms per node (SQLite) — negligible for most workloads but measurable at high throughput.

For high-throughput serving, both can run stateless (no checkpointer) to minimize latency. LangGraph’s compiled graph has slightly lower per-step overhead because there’s no agent executor loop parsing LLM output — the graph just routes.

Deployment and production concerns

LangChain agents deploy as standard Python services. You manage memory, sessions, and scaling yourself. LangChain’s Runnable interface helps with batching and async, but you’re building the server.

LangGraph adds LangGraph Platform (commercial) for managed deployment: horizontal scaling, built-in checkpointing, horizontal scaling, cron jobs, and a studio for debugging. The open-source version runs anywhere — FastAPI, AWS Lambda, Kubernetes. The checkpointing backends (PostgreSQL, Redis) are production-grade.

If you’re deploying to n4n.ai or similar inference gateways, both frameworks work — they just need an OpenAI-compatible endpoint. LangGraph’s explicit control flow makes it easier to implement routing directives (e.g., “use cheaper model for classification, expensive model for generation”) because you control which node calls which model.

When LangChain agents are the right choice

  • Simple ReAct loops: one to three tools, linear flow, no branching
  • Prototyping: you want working code in 10 minutes, not a graph definition
  • Existing LangChain codebase: migrating to LangGraph isn’t free
  • Single-turn tool use: “call this API, return result” — no conversation, no loops
  • Teams unfamiliar with graph concepts: lower learning curve
# This is fine in LangChain. Don't overthink it.
executor = AgentExecutor(agent=agent, tools=[calculator, unit_converter], max_iterations=3)

When LangGraph is the right choice

  • Multi-step workflows with conditional logic
  • Cycles, retries, or iterative refinement (coding agents, research agents)
  • Human-in-the-loop: approval gates, editing state mid-run
  • Multi-agent systems: supervisor/worker, planner/executor, swarm
  • Long-running conversations needing checkpoint/resume
  • Production observability: per-node tracing, latency budgets, alerting
  • Testing requirements: deterministic graph execution, node-level unit tests

Verdict: which to choose

Use case Recommendation Reason
Chatbot with 2-3 tools LangChain agents Simpler, faster to build, sufficient
Coding agent (write → test → fix) LangGraph Needs cycles, conditional retry, state inspection
Research pipeline (search → analyze → synthesize) LangGraph Parallel fan-out, fan-in, explicit stages
Multi-agent supervisor/worker LangGraph Native composition, shared state, handoff patterns
Human approval workflows LangGraph Checkpointing, pause/resume, state editing
High-throughput stateless tool calling Either Both work; LangGraph slightly lower overhead
Team new to LLM orchestration LangChain agents Gentler learning curve, more tutorials
Production system needing observability LangGraph Event streaming, per-node metrics, traceability
Niche integration (legacy CRM, custom DB) LangChain agents Larger integration ecosystem
Time-travel debugging / replay LangGraph Built-in checkpointing, fork from any state

Start with LangChain agents if your workflow is linear and shallow. Switch to LangGraph the moment you feel the agent executor fighting you — when you need a loop with a custom exit condition, when you want to run two tools in parallel, when you need to pause for human input, or when you’re adding a second agent. The migration path is incremental: wrap your LangChain agent as a LangGraph node, then expand the graph from there.

Tagslanggraphlangchain-agentscomparisonorchestration

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All langgraph multi-agent workflows posts →