Day 2 · by Kunal Hore (@KunalOnTech)
Quick Jump
- Quick Recap of Day 1
- Why I'm Learning This Publicly
- Today's Map
- 1. The Router
- 2. The Conditional Edge
- 3. Build & Run — The Full Exercise
- Kunal's Interview Corner
- Today's Takeaway
Quick Recap of Day 1
On Day 1, we built our first LangGraph: a food-ordering workflow using state (the facts the graph remembers), nodes (functions that read and update the state), and edges (the fixed connections between nodes).
But that graph had one limitation — it could only follow one fixed path. Every run looked exactly the same:
Feel hungry → Open app → Browse restaurants → Pick dish → Place order
No matter what happened, the graph always followed that sequence.
Real-world workflows aren't that predictable. What if the restaurant is closed? Should the graph still try to pick a dish and place an order?
That's today's lesson: teaching the graph how to make decisions.
Kunal's one-liner: "An edge says go there. A conditional edge says let me check first."
New here? This is Day 2 of the series. Complete Day 1 first — today's lesson builds directly on the graph we created there.
Why I'm Learning This Publicly
I'm learning LangGraph from zero — one concept a day. No shortcuts, no pretending I already know it. Whatever I learn each day, I share the same day: the wins, the confusion, the "oh THAT'S what it means" moments, all of it.
My goal is simple:
- One concept per day, explained with everyday analogies — no jargon walls
- Real code every single day, because you learn by building, not by theory alone
- Write it the way I wish someone had explained it to me
Your goal, if you learn alongside me: by the end of this series, we'll both be able to design and build stateful AI agents — the kind of systems that can decide, branch, retry, and loop, not just respond once.
And since I'm learning as I go, I might miss something or get a detail wrong. If you're reading and feel I've overlooked anything, drop it in the comments — it genuinely helps me grow, and it makes this series better for everyone following along.
No fluff. One bite a day.
Today's Map
- The Router — the function that reads the current state and decides which path to take next.
- The Conditional Edge — the mapping that connects the router's decision to the next node.
- Build & Run — extend the graph from Day 1 by adding your first conditional branch.
1. The Router
Imagine standing at a crossroads with multiple paths ahead. Before moving forward, someone needs to look at the current situation and decide which direction the journey should take. That's exactly what a router does.
A router is simply a function that reads the current state, makes a routing decision, and returns a string label such as "restaurant_open" or "restaurant_closed". This label represents the decision and is used by the conditional edge to determine which node should run next.
The router's responsibility ends after making the decision. It does not update the state or perform the actual processing — that responsibility belongs to the nodes.
| Role | Action |
|---|---|
| Router | Decides and returns a label |
| Conditional Edge | Uses that label to choose the next node |
| Node | Processes and updates state |
# Router — reads state, returns a string label, never edits state
def route_by_availability(state):
if state["is_restaurant_open"]:
return "restaurant_open"
return "restaurant_closed"
Remember this:
- A router decides, but never edits. A router's only job is to read the current state, decide the path, and return a label. It should never modify the state itself. Keeping decision-making separate from state updates makes your graph easier to understand and debug. If a router starts changing values, it becomes unclear whether the router or a node caused that change.
- The router runs fresh every time. Whenever execution reaches a conditional edge, LangGraph executes the router function again. It does not remember or reuse a previous decision. Instead, it reads the current state at that moment and makes a fresh routing decision based on the latest information available. This means if a previous node updated the state before reaching the conditional edge, the router automatically sees those changes and can return a different label, allowing the graph to follow the correct path.
2. The Conditional Edge
A conditional edge is simply a connection between the router's decision and the next node to execute. It uses a mapping dictionary that defines where each label should go. For example, the label "restaurant_open" maps to the "pick_dish" node, while the label "restaurant_closed" maps to the "notify_closed" node.
When the router returns a label, the conditional edge looks up that label in the mapping dictionary and routes execution to the corresponding node. In other words, the router sends its decision in the form of a label, and the conditional edge uses that label to determine the next node in the graph.
graph.add_conditional_edges(
"check_availability", # Source node
route_by_availability, # Router function
{
"restaurant_open": "pick_dish", # Branch 1
"restaurant_closed": "notify_closed", # Branch 2
}
)
Remember this:
- Mapping dictionary keys must exactly match the router's output. Every key in the mapping dictionary must be identical to the label returned by the router. If the router returns "restaurant_open", the mapping must also use "restaurant_open" — not "open" or "Restaurant_Open". Even a small typo or capitalization mismatch means the conditional edge cannot find a matching route, and the graph throws an error at runtime. The router's return values and the mapping dictionary's keys form a contract, so they must always match exactly.
-
Three arguments, in order.
add_conditional_edges()always takes three arguments in a fixed order:- Source node — where the routing decision happens.
- Router function — the function that reads state, makes the decision, and returns a label.
- Mapping dictionary — the lookup table that maps each label to the next node.
A simple way to remember it: Source → Router → Mapping
3. Build & Run — The Full Exercise
Problem statement: Extend the food-ordering graph from Day 1 by adding your first conditional branch. After browsing the menu, check whether the restaurant is open. If it's open, pick a dish and place the order. If it's closed, notify the user and end the workflow. One router, and two possible branches.
This series uses Python 3.11+ — NotRequired needs 3.11 or newer.
from langgraph.graph import StateGraph, START, END
from typing import TypedDict, NotRequired
# The backpack — state shared across the graph
class FoodOrderState(TypedDict):
mood: str
app_opened: bool
restaurant: str
dish: str
order_placed: bool
is_restaurant_open: NotRequired[bool] # Created later by a node
# Nodes — each one reads state in, changes one field, returns state out
def feel_hungry(state):
state["mood"] = "hungry"
return state
def open_app(state):
state["app_opened"] = True
return state
def browse_restaurants(state):
state["restaurant"] = "Biryani House"
return state
def check_availability(state):
# Imagine checking a real restaurant API here
state["is_restaurant_open"] = False # Change to True to test the other branch
return state
def pick_dish(state):
state["dish"] = "Chicken Biryani"
return state
def place_order(state):
state["order_placed"] = True
return state
def notify_closed(state):
print(f"{state['restaurant']} is closed. Can't order right now.")
return state
# Router — reads state, returns a string label, never edits state
def route_by_availability(state):
if state["is_restaurant_open"]:
return "restaurant_open"
return "restaurant_closed"
# Build the graph
graph = StateGraph(FoodOrderState)
# Register nodes
graph.add_node("feel_hungry", feel_hungry)
graph.add_node("open_app", open_app)
graph.add_node("browse_restaurants", browse_restaurants)
graph.add_node("check_availability", check_availability)
graph.add_node("pick_dish", pick_dish)
graph.add_node("place_order", place_order)
graph.add_node("notify_closed", notify_closed)
# Shared path
graph.add_edge(START, "feel_hungry")
graph.add_edge("feel_hungry", "open_app")
graph.add_edge("open_app", "browse_restaurants")
graph.add_edge("browse_restaurants", "check_availability")
# Conditional edge
graph.add_conditional_edges(
"check_availability",
route_by_availability,
{
"restaurant_open": "pick_dish",
"restaurant_closed": "notify_closed",
},
)
# Branch 1
graph.add_edge("pick_dish", "place_order")
graph.add_edge("place_order", END)
# Branch 2
graph.add_edge("notify_closed", END)
# Compile
app = graph.compile()
# Initial state — only includes values known before execution starts
initial_state = {
"mood": "",
"app_opened": False,
"restaurant": "",
"dish": "",
"order_placed": False,
}
# Run
final_state = app.invoke(initial_state)
print("\nFinal State:")
print(final_state)
Output:
Final State:
Biryani House is closed. Can't order right now.
{'mood': 'hungry', 'app_opened': True, 'restaurant': 'Biryani House', 'dish': '', 'order_placed': False, 'is_restaurant_open': False}
Notice that dish is still empty and order_placed is still False. That's because the graph never entered Branch 1. The check_availability node set is_restaurant_open to False, the router read that value and returned the label "restaurant_closed", and the conditional edge routed execution down the closed branch.
Now change is_restaurant_open to True inside the check_availability node and run the graph again. The graph itself doesn't change — only the state produced by that node does. This time, the router returns a different label, the conditional edge follows the open branch, and the graph picks a dish before placing the order. That's the power of conditional routing.
Kunal's Interview Corner
Four questions someone might actually ask about today's lesson — the kind that surface gaps a quiet read-through never does. These aren't definitions to memorise. Each one comes with an answer framework — the structure an interviewer wants to hear. Try answering before you tap.
· Answer Framework: Contrast the two → Give a mental picture · Answer: A normal edge always follows the same path — after one node finishes, the next node is fixed. A conditional edge chooses the next path at runtime based on the current state. Think of a normal edge as a fixed roadmap, and a conditional edge as a GPS that picks the best route based on the current situation.Q1. What's the difference between a normal edge and a conditional edge?
· Answer Framework: Define its job → State the golden rule · Answer: A router is a plain function that reads the current state, decides which path to take, and returns a string label. Its only job is to make the routing decision. Think of it as a navigator — it tells you where to go, but it never drives the car.Q2. What is a router function in LangGraph?
· Answer Framework: Direct answer → Why → The consequence · Answer: No. A router should only read the state and decide the next path. Updating the state is the responsibility of nodes. Mixing those responsibilities makes it difficult to understand where a value changed and makes debugging much harder.Q3. Can a router modify state?
· Answer Framework: State the fact → Explain the behaviour · Answer: The graph can't determine where to go next, so execution fails with an error. The router's return value must exactly match one of the keys in the mapping dictionary. Even a small typo or difference in capitalization breaks the route.Q4. What happens if the router returns a label that's not in the mapping dictionary?
"If you can answer these four questions without checking your notes, Day 2 has truly stuck."
Today's Takeaway
If you remember only three things from today, remember these:
| Term | Meaning |
|---|---|
| Router | Reads the current state, decides which path the graph should take, and returns a label that the conditional edge uses to route execution. |
| Conditional Edge | Calls the router, looks up the returned label in the mapping dictionary, and routes execution to the correct node. |
| Mapping Dictionary | A lookup table inside the conditional edge that maps router labels to nodes. Every key must exactly match a label the router can return. |
Day 1 gave your graph a body — a fixed sequence of steps.
Day 2 gave it a brain — the ability to read its current state and choose the next path.
Next up → Day 3: Loops — When Graphs Revisit Previous Nodes
Remember the one-liner from Day 1?
"Code that can loop back, can think."
We've taught our graph how to choose a path. Next, we'll explore how graphs can revisit earlier nodes to model real-world workflows.
Following along? Connect with me on LinkedIn · YouTube: @kunalontech
Top comments (2)
That edge vs conditional edge distinction is a clean mental model. In agent workflows, the conditional edge is where product judgment enters the graph: what evidence is enough, what fallback is allowed, and when the system should stop.
@alexshev
That's a great way to put it — product judgment entering at the conditional edge. Curious about the 'when the system should stop' part specifically: in your experience, do you hardcode a max-retry/step count, or is the stop condition usually something smarter — like a confidence threshold from the LLM itself?