Nodes and Edges
Learn how to add nodes with add_node, connect them with edges, and set entry/finish points for your LangGraph applications.
Nodes and edges are the building blocks of any LangGraph application. Nodes do the work, edges define the flow.
Adding Nodes with add_node
Every node is a Python function registered with a unique name:
from langgraph.graph import StateGraph
from typing_extensions import TypedDict
class State(TypedDict):
messages: list[str]
count: int
builder = StateGraph(State)
def greet(state: State) -> dict:
return {"messages": state["messages"] + ["Hello!"]}
def increment(state: State) -> dict:
return {"count": state["count"] + 1}
builder.add_node("greet", greet)
builder.add_node("increment", increment)Node Name Rules
- Names must be unique within the graph
- Use descriptive names (
"analyze_query"over"node_1") - Names are used in edge definitions and streaming output
- Avoid special characters and spaces
Adding Multiple Nodes
def node_a(state: State) -> dict:
return {"messages": state["messages"] + ["A"]}
def node_b(state: State) -> dict:
return {"messages": state["messages"] + ["B"]}
def node_c(state: State) -> dict:
return {"messages": state["messages"] + ["C"]}
builder.add_node("a", node_a)
builder.add_node("b", node_b)
builder.add_node("c", node_c)You can add nodes in any order. The execution order is determined by edges, not by the order you call add_node().
Node Return Values
Nodes must return one of:
Return Type | Behavior |
|---|---|
dict | Keys are merged into state (shallow merge) |
None | State is unchanged |
{} | Empty dict, state is unchanged |
Pattern: Reading and Writing State
def node_with_side_effects(state: State) -> dict:
# Read current state
previous = state["messages"]
current_count = state["count"]
# Process (call LLM, run logic, etc.)
new_message = f"Processed message #{current_count + 1}"
# Return updates
return {
"messages": previous + [new_message],
"count": current_count + 1
}Never mutate state directly. Always return a new dict with the updates. LangGraph handles the merge internally.
Edges: Connecting Nodes
Edges define which nodes execute and in what order.
Simple Edge
# After node A finishes, run node B
builder.add_edge("A", "B")Parallel Edges (Fan-Out)
# After node A finishes, run B and C in parallel
builder.add_edge("A", "B")
builder.add_edge("A", "C")When A completes, both B and C execute simultaneously in separate threads. Each receives a copy of the current state.
Sequential Chain
# A → B → C → D (linear pipeline)
builder.add_edge("A", "B")
builder.add_edge("B", "C")
builder.add_edge("C", "D")Parallel fan-out is one of LangGraph's superpowers. Multiple nodes read the same state, process independently, and their updates are merged when all complete.
Entry and Finish Points
Using set_entry_point / set_finish_point
builder = StateGraph(State)
builder.add_node("first", first_node)
builder.add_node("second", second_node)
builder.add_node("last", last_node)
builder.set_entry_point("first")
builder.add_edge("first", "second")
builder.add_edge("second", "last")
builder.set_finish_point("last")Using START and END Constants (Modern Approach)
from langgraph.graph import START, END
builder = StateGraph(State)
builder.add_node("first", first_node)
builder.add_node("second", second_node)
builder.add_edge(START, "first")
builder.add_edge("first", "second")
builder.add_edge("second", END)Use START and END constants instead of set_entry_point/set_finish_point. They are more explicit and work better in complex graphs with multiple entry or exit points.
Multiple Entry Points (Advanced)
# Graph can start from either node
builder.add_edge(START, "ingest_api")
builder.add_edge(START, "ingest_file")Both entry nodes execute in parallel when invoke() is called.
Multiple Finish Points
builder.add_edge("success_handler", END)
builder.add_edge("error_handler", END)The graph ends when any branch reaches END.
Complete Graph Topology Example
from langgraph.graph import StateGraph, START, END
from typing_extensions import TypedDict
from typing import List
class ProcessingState(TypedDict):
data: str
validated: bool
transformed: str
result: str
def validate(state: ProcessingState) -> dict:
is_valid = len(state["data"]) > 0
return {"validated": is_valid}
def transform(state: ProcessingState) -> dict:
transformed = state["data"].upper().strip()
return {"transformed": transformed}
def save(state: ProcessingState) -> dict:
return {"result": f"Saved: {state['transformed']}"}
def report_error(state: ProcessingState) -> dict:
return {"result": "Error: Invalid data"}
builder = StateGraph(ProcessingState)
builder.add_node("validate", validate)
builder.add_node("transform", transform)
builder.add_node("save", save)
builder.add_node("error", report_error)
builder.add_edge(START, "validate")
builder.add_edge("validate", "transform")
builder.add_edge("transform", "save")
builder.add_edge("save", END)
builder.add_edge("validate", "error")
builder.add_edge("error", END)
app = builder.compile()
# Visualize
print(app.get_graph().draw_mermaid())The above example has a problem: after validate, the graph runs both transform AND error in parallel because there are edges to both. Use conditional edges (next lesson) to route based on the validation result.
Fan-In: Multiple Nodes Merging to One
When multiple nodes connect to the same target:
builder.add_edge("search_web", "aggregate")
builder.add_edge("search_db", "aggregate")
builder.add_edge("search_api", "aggregate")The aggregate node runs after all three incoming nodes complete. Their updates are merged before aggregate receives the state.
Fan-out lets you parallelize work. Fan-in lets you synchronize results. Together, they enable powerful Map-Reduce patterns inside your graph.
Nodes Without Return Values
Sometimes a node performs an action without modifying state (e.g., logging, sending a notification):
def log_node(state: State) -> None:
print(f"Current state messages: {state['messages']}")
# No return — state is unchanged
builder.add_node("logger", log_node)Nodes that return None are useful for side effects like logging, metrics emission, or webhook calls. They don't modify state but can access it.
Node and Edge Best Practices
- One responsibility per node: Each node should do one thing (validate, transform, search, generate)
- Descriptive names:
"classify_intent"is better than"step_3" - Keep nodes pure when possible: Avoid side effects in the main logic; add separate side-effect nodes
- Limit parallel breadth: Too many parallel nodes can overwhelm thread pools
- Test nodes independently: Each node should be testable in isolation
# Bad: One giant node
def do_everything(state: State) -> dict:
# validates, transforms, searches, generates, and saves
# ... dozens of lines of logic
pass
# Good: Composable nodes
def validate_input(state: State) -> dict: ...
def search_knowledge(state: State) -> dict: ...
def generate_response(state: State) -> dict: ...
def save_to_db(state: State) -> dict: ...Common Edge Patterns
Pipeline (Sequential)
START → A → B → C → END
Fan-Out (Parallel)
START → A → B → END
↓
C → END
Fan-In (Merge)
START → B → D → END
START → C → END
Branching (Conditional)
START → A → B → END
↓
C → END
(Conditional edges, covered in lesson 9, determine which path to take)
Practice Questions
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
What method is used to register a function as a node in LangGraph?
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
What happens when a node returns None?
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
What does add_edge('A', 'B') do?
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
What happens when two edges leave the same node?
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
What does the END constant represent?
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
Which approach to setting entry points is recommended for modern LangGraph code?
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
If nodes B and C both connect to node D, when does D run?
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
Can you have multiple START nodes?
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
What is the best practice for node naming?
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
What does fan-out mean in the context of LangGraph?
Key Takeaways
add_node('name', func)registers a node; names must be unique and descriptive- Nodes receive the full state and return partial updates (or None for no changes)
- Edges define topology:
add_edge('A', 'B')means B runs after A STARTandENDconstants mark entry and exit points- Fan-out enables parallel execution; fan-in synchronizes multiple branches
- Never mutate state directly: always return a dict of updates
- Use a single responsibility per node for composability and testing
- Multiple
add_edge(START, ...)calls create parallel entry points