Conditional Edges
Branches and loops.
A flow chart with no decisions isn't very interesting. The real power comes from arrows that depend on the state. That's how you build routers, and that's how you build loops.

- No decisions, no power
- Arrows that depend on state
- Routers and loops
Routing with a conditional edge
This choose function looks at the state, and returns the name of the next node. In this example, it's plain code, though a model could do this just as easily. We connect it using add conditional edges. Run it. Three messages, three different routes. This is our routing pattern, as a graph.
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
class State(TypedDict):
message: str
reply: str
def tracking(state):
return {"reply": "Let me look up your parcel."}
def returns(state):
return {"reply": "Here is our returns policy."}
def human(state):
return {"reply": "A person will contact you."}
def choose(state):
text = state["message"].lower()
if "where" in text:
return "tracking"
return "returns" if "return" in text else "human"
g = StateGraph(State)
for name, fn in [("tracking", tracking), ("returns", returns), ("human", human)]:
g.add_node(name, fn)
g.add_edge(name, END)
g.add_conditional_edges(START, choose)
app = g.compile()
for m in ["Where is it?", "Can I return this?", "I need a lawyer"]:
print(f"{m:<20} {app.invoke({'message': m})['reply']}")Where is it? Let me look up your parcel. Can I return this? Here is our returns policy. I need a lawyer A person will contact you.
A loop with an exit
Now for a loop. The write node makes a draft. The review function looks at it, and says either again, or done. Again points back to write. And done goes to the end. Notice that review also counts the rounds, so that it can't loop forever. Run it. It shrank the draft over the rounds, and then it stopped. Our evaluator optimizer pattern is now just two lines of edges.
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
class State(TypedDict):
draft: str
rounds: int
def write(state):
n = state["rounds"] + 1
return {"draft": "word " * (60 - n * 20), "rounds": n}
def review(state):
words = len(state["draft"].split())
print(f"Round {state['rounds']}: {words} words")
return "done" if words <= 25 or state["rounds"] >= 3 else "again"
g = StateGraph(State)
g.add_node("write", write)
g.add_edge(START, "write")
g.add_conditional_edges("write", review, {"again": "write", "done": END})
app = g.compile()
print("Final rounds:", app.invoke({"draft": "", "rounds": 0})["rounds"])Round 1: 40 words Round 2: 20 words Final rounds: 2
Why cycles matter
This is what makes LangGraph different from older tools, which ran in straight lines. Agents need cycles. Think, act, observe, and go back to think. A cycle with a clear exit is an agent. And the framework has a recursion limit too, as a backstop, so a mistake can't run forever.
Summary
Branches and cycles are just conditional edges. Next, we connect a real model, and rebuild our agent as a graph.
Key takeaways- Conditional edges pick the next node
- Loops point back
- Always have an exit