How conditional edges encode the routing decision in a LangGraph supervisor
A routing function reads the state and returns a node name (or END); LangGraph dispatches the edge to the named node. The LLM writes the decision into state; plain Python dispatches it.
Imagine a hotel concierge with a clipboard. A guest walks up, the concierge writes the next thing the guest should do (room, restaurant, gym) on the clipboard, and then a separate doorman reads the clipboard and points the guest the right way. The concierge can be moody and inconsistent (that is the language model), but the doorman is robotic and predictable: he reads exactly what is on the paper and never invents a destination. Splitting the decision (concierge) from the dispatch (doorman) means you can audit and unit-test the doorman even though the concierge is a black box. That split is what LangGraph's conditional edges encode.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
LangGraph's conditional edges are the mechanism that turns a graph of LLM nodes into something an executor can actually run, trace, and resume. They encode a clean separation between the decision (an LLM call inside the supervisor node) and the dispatch (a deterministic Python function that reads state and returns a node name).
This separation is not a stylistic preference. It is the engineering choice that makes the rest of LangGraph work: testable routing, observable transitions, and resumable runs after a crash. Frameworks that collapse decision and dispatch into a single LLM emission (handoff tool calls, GroupChat selector LLMs) give up the same properties.
Mental model: the supervisor decides; the router dispatches. The split is the whole feature.
The routing function in detail
Signature and contract
A routing function has the shape:
def route(state: State) -> str:
return state["next_agent"]
The input is the current state, the output is a string. The string must match one of the keys in the mapping you pass to add_conditional_edges, or it must be the END sentinel.
For parallel fan-out, the return type widens to list[str]: returning ["worker_a", "worker_b"] sends the same state to both nodes in parallel; the reducer at the join (per typed-channel rules) merges their state updates.
What the function must not do
The router is a pure function over state. It must not:
- Call the LLM.
- Make tool calls or side-effectful operations.
- Mutate the state in place.
- Depend on global state or random sources.
Violating any of these breaks resumability: a crash-resumed run reloads the state and re-runs the router, and if the router is non-deterministic, the resumed path can diverge from the original.
How the mapping ties it together
You register the edge with:
graph.add_conditional_edges(
"supervisor",
route,
{"worker_a": "worker_a", "worker_b": "worker_b", "finish": END},
)
The first arg is the source node, the second is the router, the third is the mapping from return values to downstream nodes. If the router returns a value not in the mapping, LangGraph raises a ValueError. This is the safety mechanism that catches bugs early.
Situations where this technique stops working.
2–4 min · Everything important, quickly.
Real products, models, and research that use this idea.
- LangGraph's create_supervisor prebuilt encodes exactly this pattern: a supervisor LLM call plus a conditional edge router.
- Anthropic's published agent traces from 2025 show the same split (decide in the model, dispatch in Python) as a recommended pattern.
What an interviewer would ask next. Try answering before peeking at the approach.
QHow do you handle the case where the supervisor LLM returns an invalid node name (a typo or a hallucinated worker)?
Validate the LLM output in the supervisor node before writing to state (cast against a Literal type or a Pydantic enum). On invalid output, write a fallback value the router maps to a 'replanning' node that re-prompts the supervisor with a stricter instruction or escalates to a human. Never let invalid output reach the router; the router should assume valid state.
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Letting the LLM directly determine control flow inside the routing function. The routing function should be pure Python over state; the LLM call belongs in the supervisor node that writes its decision into the state for the router to read.
60 second bullets to scan on the way to the call.
The signature of a routing function (state -> str or list[str])
How add_conditional_edges binds the function to the source node
Primary sources. Browse if you want the original framing.
Same topic, related formats. Practice these next.