operator.add for list accumulation
Why this matters
When building langgraph agents, you often need to accumulate messages or results from multiple nodes into a single list. <code>operator.add</code> is the standard, concise way to tell langgraph how to combine those lists instead of overwriting them.
Explanation
operator.add is a built-in Python function that performs the + operation on two objects. In langgraph state reducers, it concatenates lists together. When a node updates a field defined with operator.add, langgraph appends the new values to the existing list rather than replacing it.
Under the hood, when you define a state field with add=operator.add, langgraph treats that field as a reducer. Every time a node updates that field, instead of overwriting it, langgraph calls operator.add(current_list, new_list) which produces a new concatenated list. This is how agents accumulate message histories or collect results from parallel nodes without losing prior data.
Use this pattern whenever you want list accumulation to be the default behavior across your entire graph: it's cleaner than manually concatenating in each node and prevents accidental data loss.
Analogy
Think of it like a mailbox where each node drops off letters. Without a reducer, the mailbox gets emptied and refilled by each letter. With <code>operator.add</code>, each new letter gets added to the pile that's already there.
Code
import operator
from langgraph.graph import StateGraph, START, END
from typing import Annotated
from typing_extensions import TypedDict
class State(TypedDict):
items: Annotated[list, operator.add]
def node_a(state: State) -> dict:
return {"items": ["apple", "apricot"]}
def node_b(state: State) -> dict:
return {"items": ["banana"]}
def node_c(state: State) -> dict:
return {"items": ["cherry"]}
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()
result = compiled.invoke({})
print(f"Final items: {result['items']}") Final items: ['apple', 'apricot', 'banana', 'cherry']
What just happened?
Node A returned a list with two items, which set the state's items field to ['apple', 'apricot']. Node B then returned a list with one item, and because items uses operator.add as a reducer, langgraph concatenated it to produce ['apple', 'apricot', 'banana']. Node C did the same, adding 'cherry' to the end. At no point was data overwritten: each node's return appended to what was already there.
Common gotcha
The reducer only applies when a node returns a dict with that key. If a node doesn't return the items key at all, the state value stays unchanged. Many developers expect nodes to always contribute something, then wonder why their list stopped growing when a node in the middle forgot to return that field.
Error recovery
TypeError: unsupported operand type(s) for +KeyError in invoke()Experienced dev note
Reducers are where langgraph's state management differs from simple dict updates. In a stateless function, you'd concatenate manually; here, the reducer is declared once in State and works everywhere. This also means if you accidentally omit the Annotated import or forget operator.add, you get silent overwriting: the code runs, but data vanishes. Always verify your first node output includes the reducer field, or you'll debug a phantom bug later.
Check your understanding
If node A returns {'items': ['x']}, then node B returns {'items': ['y', 'z']}, and node C returns {'items': []}, what does the final state contain, and why doesn't it matter that node C returned an empty list?
Show answer hint
A correct answer recognizes that the final list is ['x', 'y', 'z'] because operator.add([...], []) simply returns the original list unchanged, so an empty return still triggers the reducer but contributes nothing new.