beginner30 minutesLesson 4 of 10

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

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:

python
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

python
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)
💡Tip

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 TypeBehavior
dictKeys are merged into state (shallow merge)
NoneState is unchanged
{}Empty dict, state is unchanged

Pattern: Reading and Writing State

python
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 }
📌Important

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

python
# After node A finishes, run node B builder.add_edge("A", "B")

Parallel Edges (Fan-Out)

python
# 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

python
# A → B → C → D (linear pipeline) builder.add_edge("A", "B") builder.add_edge("B", "C") builder.add_edge("C", "D")
ℹ️Note

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

python
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)

python
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)
💡Tip

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)

python
# 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

python
builder.add_edge("success_handler", END) builder.add_edge("error_handler", END)

The graph ends when any branch reaches END.


Complete Graph Topology Example

python
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())
100%
⚠️Warning

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:

python
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.

100%
Success

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):

python
def log_node(state: State) -> None: print(f"Current state messages: {state['messages']}") # No return — state is unchanged builder.add_node("logger", log_node)
ℹ️Note

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

  1. One responsibility per node: Each node should do one thing (validate, transform, search, generate)
  2. Descriptive names: "classify_intent" is better than "step_3"
  3. Keep nodes pure when possible: Avoid side effects in the main logic; add separate side-effect nodes
  4. Limit parallel breadth: Too many parallel nodes can overwhelm thread pools
  5. Test nodes independently: Each node should be testable in isolation
python
# 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

Practice Question

What method is used to register a function as a node in LangGraph?

Practice Question

What happens when a node returns None?

Practice Question

What does add_edge('A', 'B') do?

Practice Question

What happens when two edges leave the same node?

Practice Question

What does the END constant represent?

Practice Question

Which approach to setting entry points is recommended for modern LangGraph code?

Practice Question

If nodes B and C both connect to node D, when does D run?

Practice Question

Can you have multiple START nodes?

Practice Question

What is the best practice for node naming?

Practice Question

What does fan-out mean in the context of LangGraph?


Success

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
  • START and END constants 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
Progress40%