n4nAI

LangGraph state machines explained with a code example

A precise LangGraph state machine explained: how graph-based agent orchestration works, with a runnable Python example and common pitfalls to avoid.

n4n Team4 min read809 words

Audio narration

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

A LangGraph state machine is a directed graph where each node executes a function that reads from and writes to a shared state object, while edges determine the next node based on that state. This LangGraph state machine explained guide strips away the hype and shows the mechanics you need to ship agent workflows that don’t collapse under edge cases.

What a LangGraph state machine actually is

State is the backbone

In LangGraph, state is a typed container—usually a TypedDict or a Pydantic model. Every node receives the current state, returns a partial update, and LangGraph merges that update back using a reducer. The reducer defines conflict resolution: overwrite, append, or custom merge.

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

class AgentState(TypedDict):
    query: str
    category: str | None
    results: Annotated[list[str], lambda a, b: a + b]
    answer: str | None

The Annotated list uses a concat reducer so multiple nodes can contribute results without clobbering each other.

Nodes are just callables

A node is any function (sync or async) that takes state and returns a dict. It does not manage control flow. That separation is the whole point.

def classify(state: AgentState) -> dict:
    # call an LLM or heuristic
    cat = "technical" if "error" in state["query"] else "general"
    return {"category": cat}

Edges encode transitions

Fixed edges push state from one node to the next. Conditional edges route based on a function that inspects state and returns a node name or END.

def route(state: AgentState) -> str:
    if state["category"] == "technical":
        return "deep_dive"
    return "summarize"

How it works under the hood

LangGraph compiles your StateGraph into a Pregel-like execution plan. Each step processes a set of nodes whose inputs are ready, applies reducers, and writes checkpoints to a persistence layer if configured. That checkpointing is what makes long-running agents resumable after a crash or a human approval step.

The runtime is not a black box. You can introspect the graph, dump the state at any step, and replay from a specific checkpoint. This is fundamentally different from a monolithic ReAct loop where the LLM decides everything and you pray it doesn’t go off the rails.

Why it matters for agent workflows

Engineers adopt LangGraph because prompt-based orchestration breaks. When you chain LLM calls with string concatenation, a single malformed output silently corrupts every downstream step. A LangGraph state machine explained in terms of explicit transitions forces you to declare: “if classification is X, go here; otherwise stop.”

Human-in-the-loop is first-class

Because state is persisted, you can interrupt the graph at a node, wait for a human to edit the state, then resume. Try doing that with a recursive agent function—you’ll end up building a half-baked state machine anyway.

Multi-agent without chaos

In the LangGraph Multi-Agent Workflows cluster, each agent is a subgraph or a node. State passes between them with typed contracts. You avoid the “every agent sees everything” anti-pattern.

Concrete code example

Below is a runnable skeleton. It classifies a query, routes to a technical retriever or a general summarizer, calls an LLM, and exits. We use the OpenAI SDK pointed at n4n.ai so model routing and fallback are handled outside our code.

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

class State(TypedDict):
    query: str
    category: str | None
    context: Annotated[list[str], lambda a, b: a + b]
    answer: str | None

def classify(state: State) -> dict:
    cat = "tech" if any(k in state["query"] for k in ["stack", "trace", "bug"]) else "gen"
    return {"category": cat}

def retrieve_tech(state: State) -> dict:
    # stub retrieval
    return {"context": ["docs: deployment rollback procedure"]}

def retrieve_gen(state: State) -> dict:
    return {"context": ["general knowledge snippet"]}

def generate(state: State) -> dict:
    from openai import OpenAI
    # n4n.ai exposes one OpenAI-compatible endpoint with automatic fallback
    client = OpenAI(base_url="https://api.n4n.ai/v1", api_key="sk-yourkey")
    resp = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": "Answer using context."},
            {"role": "user", "content": f"{state['query']}\n{state['context']}"}
        ]
    )
    return {"answer": resp.choices[0].message.content}

def route(state: State) -> str:
    return "retrieve_tech" if state["category"] == "tech" else "retrieve_gen"

g = StateGraph(State)
g.add_node("classify", classify)
g.add_node("retrieve_tech", retrieve_tech)
g.add_node("retrieve_gen", retrieve_gen)
g.add_node("generate", generate)

g.add_edge(START, "classify")
g.add_conditional_edges("classify", route)
g.add_edge("retrieve_tech", "generate")
g.add_edge("retrieve_gen", "generate")
g.add_edge("generate", END)

app = g.compile()
result = app.invoke({"query": "How to read a stack trace from the bug report?"})
print(result["answer"])

The langgraph state machine explained here is concrete: classify writes category, the conditional edge reads it, and only the relevant retriever runs. State updates are explicit; nothing hidden.

If you swap the model name to another supported by the gateway, n4n.ai forwards cache-control hints and meters per-token usage without code changes. That’s the kind of provider abstraction that pairs well with graph orchestration.

Common misconceptions

“It’s just a visual flowchart”

No. A flowchart is static. A LangGraph state machine carries a mutable state object and reducers that define how concurrent writes merge. You can run nodes in parallel, branch, and join. The graph is a runtime, not a diagram.

“It replaces my agent’s reasoning”

Wrong. LangGraph orchestrates; it does not think. The LLM calls still live inside nodes. If your prompt is bad, the graph will faithfully execute bad decisions. The value is containing those decisions within observable boundaries.

“Only useful for multi-agent systems”

Single-agent loops benefit too. Any workflow with more than two LLM calls and a branch deserves explicit state. The LangGraph state machine explained pattern shines even for a linear extract→validate→format pipeline because you get checkpoints and retries for free.

“Conditional edges are the same as exceptions”

They are not. Exceptions unwind the stack; conditional edges forward state to a different node and keep the graph alive. You can route to a human_review node on low confidence and resume later. That’s impossible with a try/except around a chain.

When to skip it

If your task is a single LLM call with no branching, adding LangGraph is pure overhead. The framework earns its keep when you have stateful retries, human approval, or multiple specialized steps. Don’t cargo-cult it into a hello-world RAG demo.

Debugging tips

Use app.get_graph().draw_mermaid() to visualize. Log state at each node with a thin wrapper. Because state is typed, mypy catches a surprising number of orchestration bugs before runtime.

Set a checkpoint saver (MemorySaver for dev, Postgres for prod). Then you can app.invoke(inputs, config={"thread_id": "x"}) and later app.invoke(None, config=...) to resume after interruption.

The LangGraph state machine explained mental model—state in, explicit transition, state out—beats any “agentic loop” abstraction when you need reliability. Build the graph, compile, and ship.

Tagslanggraphstate-machineagentscode-example

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 →