What a node is: a function that updates state
Why this matters
Nodes are the atomic units of work in langgraph: understanding how they consume and produce state is foundational to building any graph. Without this, you won't know how to structure the functions that actually do the work in your agent or workflow.
Explanation
A node is a Python function that is registered as a named unit in a langgraph StateGraph. It receives the entire current state as input, processes it, and returns new state values. The graph engine calls nodes in sequence or conditionally based on edges you define. Mechanically, when you call graph.add_node("node_name", function_reference), you're registering that function. When the graph executes and reaches that node, langgraph unpacks the state, passes it to the function, captures the return value, and merges it back into state using the state schema. The key rule: a node function's return value must match the shape your state schema expects: either a dict of fields to update, or a new complete state object.
Analogy
Think of a node like a worker at a factory station. The worker receives a form (the state) with current values, makes modifications or additions to it (processing), and passes it forward with updated fields. The next station (node) receives that updated form. If a worker returns something incompatible with what the form expects, the line stops.
Code
from langgraph.graph import StateGraph, START, END
from typing_extensions import TypedDict
class State(TypedDict):
count: int
name: str
def increment_node(state: State) -> State:
"""A node that increments count and appends to name."""
return {
"count": state["count"] + 1,
"name": state["name"] + "_processed"
}
def greet_node(state: State) -> dict:
"""A node that returns only partial state updates."""
return {"name": f"Hello {state['name']}, count is {state['count']}"}
graph = StateGraph(State)
graph.add_node("increment", increment_node)
graph.add_node("greet", greet_node)
graph.add_edge(START, "increment")
graph.add_edge("increment", "greet")
graph.add_edge("greet", END)
compiled_graph = graph.compile()
initial_state = {"count": 0, "name": "user"}
result = compiled_graph.invoke(initial_state)
print(f"Final count: {result['count']}")
print(f"Final name: {result['name']}") Final count: 1 Final name: Hello user_processed, count is 1
What just happened?
The graph created two nodes. When invoked with initial state, it passed the state to increment_node, which returned a dict with updated count (1) and name (user_processed). That updated state was then passed to greet_node, which returned a new name field that overwrote the previous one. The final state merged all updates and returned it.
Common gotcha
Returning a partial dict (only some state fields) does NOT reset the other fields: langgraph merges your return value into the existing state. So if you return {"count": 5} and state also has {"name": "foo"}, the result is {"count": 5, "name": "foo"}. Many developers expect a return value to be the complete state and accidentally leave fields unchanged when they meant to update them.
Error recovery
TypeError: unsupported operand type(s)ValidationError from pydanticKeyError: 'field_name'Experienced dev note
In langgraph, nodes are pure functions: they should not mutate the input state dict directly. Always return a new dict with updated fields. This prevents subtle bugs where two nodes accidentally share state references. Also, nodes run synchronously by default; if your node is slow (API calls, DB queries), consider async nodes later, but start with synchronous to keep mental models simple.
Check your understanding
If you have a node that receives state {count: 5, name: 'foo'} and the node returns {count: 10}, what will the state be after that node executes? Why?
Show answer hint
The correct answer shows that state will be {count: 10, name: 'foo'}: the return dict is merged, not replaced. The why must explain that langgraph performs a shallow merge of the return value into the current state, not a full replacement.