A graph becomes interesting the moment the next step is not fixed. Conditional routing is where LangGraph stops being a linear pipeline builder and starts becoming a real orchestration tool.
Routing lets the graph send work to different nodes based on category, confidence, validation results, approval state, tool output, or loop counters. That power is exactly why the design must stay disciplined. Uncontrolled routing creates the most expensive bugs in agent systems.
This page focuses on one principle: branching should clarify the workflow, not turn it into a maze.
A router is healthiest when it reads a few fields and returns one of a small set of node names. If the route function grows into a mini-application, split the work into scoring or preparation nodes first.
This is especially important for production. A short route function is easy to unit test with plain state dictionaries, which makes incident triage faster later.
Loops are normal in research, coding, extraction, and tool-calling graphs. They become dangerous when the graph has no explicit limit or no alternate destination after repeated failure.
Every loop should answer three operational questions: how many times may it repeat, what evidence ends it successfully, and where does it go when success never arrives?
A graph is a communication artifact as much as an execution artifact. When you choose route names like `needs_human_review`, `retry_search`, or `publish`, future readers understand the business process instantly.
Compare that with opaque route labels like `path_a` and `path_b`. Those save seconds while writing and cost hours during debugging.
Consider a retrieval workflow: search, draft, evaluate, retry if evidence is weak. The graph is not merely repeating. It is making a stateful decision at each evaluation checkpoint.
That means the evaluation node is a business-control boundary. It determines whether the cost of another iteration is worth paying.
Current docs highlight `Command` for cases where a node naturally needs to update state and choose the next hop in one step. It keeps the graph concise when separating mutation from routing would add ceremony without clarity.
Use conditional edges when you want a distinct routing function that can be tested independently. Use `Command` when the node itself is the decision boundary.
A conditional edge is more than a branch in code. It is a product decision encoded in the graph. The route might decide whether to retrieve more evidence, call a tool, ask a human, retry after an error, or finish. Because route decisions control the workflow, they should be small, deterministic where possible, and easy to test with minimal state examples.
Good routing starts with clear route labels. Use semantic outcomes such as `needs_more_evidence`, `requires_approval`, `ready_to_answer`, or `tool_failed_retryable`. Avoid vague labels like `next`, `other`, or `continue` because they make traces harder to read and tests harder to understand.
Loops require special care. A loop without a counter, progress check, or stop condition can become an expensive failure. Store retry counts, search attempts, or completed steps in state, and route to fallback or escalation when progress stops. Controlled loops are one of the biggest differences between a reliable graph and an improvising agent.
Conditional workflows should also include negative paths. What happens when evidence is missing, confidence is low, policy blocks action, a tool fails, or the user cancels? If every branch assumes success, the graph is not production-ready.
Design a route table for one workflow. List each route name, the state fields it reads, the condition that triggers it, the next node, and the safe fallback. This makes branching behavior reviewable before you write code.
Then test the route function with tiny state fixtures. Include success, missing data, low confidence, tool failure, approval required, and max-loop cases. If a route cannot be tested without a full model call, it is probably doing too much.
Route behavior should be understandable from the trace alone. If a reviewer cannot tell why a branch was chosen, add clearer state fields or route labels.
For deeper learning, pair the concept with a tiny graph and a saved trace. Seeing state before and after each node makes the lesson concrete and gives learners a reliable debugging habit from the beginning.
A classic branching example: classify first, then send work down a specialized path.
from typing_extensions import TypedDict, Literal
from langgraph.graph import StateGraph, START, END
class TicketState(TypedDict):
message: str
category: str
reply: str
def classify(state: TicketState) -> dict:
text = state["message"].lower()
if "refund" in text:
return {"category": "billing"}
if "error" in text:
return {"category": "technical"}
return {"category": "general"}
def route(state: TicketState) -> Literal["billing", "technical", "general"]:
return state["category"]
This shows the minimum safe shape for repetition: a counter, a quality signal, and a fallback.
from typing_extensions import TypedDict, Literal
class ResearchState(TypedDict):
question: str
attempts: int
quality: str
def evaluate(state: ResearchState) -> dict:
next_attempts = state["attempts"] + 1
quality = "good" if next_attempts >= 2 else "weak"
return {"attempts": next_attempts, "quality": quality}
def route_after_evaluate(state: ResearchState) -> Literal["search", "human_review", "__end__"]:
if state["quality"] == "good":
return "__end__"
if state["attempts"] >= 3:
return "human_review"
return "search"
This pattern is useful when a node computes a control label and should immediately route without a separate router function.
from typing_extensions import TypedDict, Literal
from langgraph.types import Command
class ModerationState(TypedDict):
text: str
risk: str
def inspect_text(state: ModerationState) -> Command[Literal["queue_manual_review", "publish"]]:
flagged = "password" in state["text"].lower()
if flagged:
return Command(update={"risk": "high"}, goto="queue_manual_review")
return Command(update={"risk": "low"}, goto="publish")
Yes, but deterministic logic is easier to test. Use model-assisted routing only when language understanding is truly part of the decision.
Usually in the node that completes one iteration or in an evaluation node that decides whether another pass is warranted.
A human review node or a structured failure summary, depending on the risk and business impact.
Explore 500+ free tutorials across 20+ languages and frameworks.