Node output: must return partial state dict
Why this matters
LangGraph merges your node's return value into the full state automatically. If you return the wrong shape (the full state, a single value, or extra fields), the graph breaks silently or crashes with confusing key errors.
Explanation
In LangGraph, the state is the shared memory across all nodes. When a node executes, it receives the current state, does work, and returns updates. The critical rule: nodes must return a dictionary with only the keys they want to update, not the full state object.
Here's the mechanic: LangGraph's StateGraph merges your return value into the state using shallow merging. If you return {'messages': [...]}, it updates only the messages key. If you return the full state dict, you're doubling keys or overwriting unintended fields. If you return a non-dict value (like a string or a list), the merge fails with a TypeError.
This is by design: it lets the graph engine track what changed and enables advanced features like state filtering and streaming. Every node must follow this contract.
Analogy
Think of the state as a shared spreadsheet. Each node can read the entire sheet, but when it's done, it writes back only the cells it modified: not a photocopy of the whole sheet. The framework handles merging your changes back into the master sheet automatically.
Code
from langgraph.graph import StateGraph, START, END
from typing import TypedDict
class State(TypedDict):
messages: list[str]
counter: int
metadata: dict
def node_a(state: State) -> dict:
print(f"Node A input state: {state}")
return {"messages": state["messages"] + ["node_a"]}
def node_b(state: State) -> dict:
print(f"Node B input state: {state}")
return {"counter": state["counter"] + 1}
def node_c(state: State) -> dict:
print(f"Node C input state: {state}")
return {"metadata": {"processed": True, "final": True}}
graph = StateGraph(State)
graph.add_node("a", node_a)
graph.add_node("b", node_b)
graph.add_node("c", node_c)
graph.add_edge(START, "a")
graph.add_edge("a", "b")
graph.add_edge("b", "c")
graph.add_edge("c", END)
compiled = graph.compile()
initial_state = {
"messages": ["start"],
"counter": 0,
"metadata": {}
}
result = compiled.invoke(initial_state)
print(f"\nFinal state: {result}") Node A input state: {'messages': ['start'], 'counter': 0, 'metadata': {}}
Node B input state: {'messages': ['start', 'node_a'], 'counter': 0, 'metadata': {}}
Node C input state: {'messages': ['start', 'node_a'], 'counter': 1, 'metadata': {}}
Final state: {'messages': ['start', 'node_a'], 'counter': 1, 'metadata': {'processed': True, 'final': True}} What just happened?
Each node received the full current state (all keys), but returned only a partial dict with the keys it modified. Node A added a message, node B incremented the counter, node C set metadata. The framework merged each return value into the shared state in order. By the end, all three modifications were present in the final state: each one added only its slice.
Common gotcha
The most common mistake is returning the entire state object from a node: return state. This works in some scenarios but is fragile. If another node later adds a new key to the state type, your node's old return still doesn't include it, which can cause subtle bugs. Always return {'key': value} with only the fields you modified. Also, returning None is valid (means no updates), but returning an empty dict {} is also valid and means the same thing: don't confuse the two.
Error recovery
TypeError: 'str' object does not support item assignmentKeyError on mergeState not updatingExperienced dev note
In early langgraph versions, nodes could return the full state and it would work. That's gone: and it's better gone. The partial dict return is a forcing function that makes state flow explicit. Use it to your advantage: if you return a diff, not a copy, you enable streaming and debugging. Also, if a node has no work to do conditionally, returning None is cleaner than {}: they're equivalent but None reads as 'no update' to humans.
Check your understanding
You have a State with keys messages, user_id, and timestamp. Your node only modifies messages. What should your node return, and what would happen if you returned state instead?
Show answer hint
The answer must include: (1) return <code>{'messages': ...}</code> only, (2) returning the full state would work once but create brittle code because new state keys added later won't be initialized by your node, and (3) the framework doesn't care about <code>user_id</code> or <code>timestamp</code> in your return: only keys you return get updated.