Code Beginner easy · 4 min

operator.add for list accumulation

What you will learn
Use <code>operator.add</code> to automatically merge lists in langgraph state reducers.

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.

Skip if: Don't use <code>operator.add</code> if you need custom merge logic (e.g., deduplication, sorting, or conditional appending). In those cases, write an explicit reducer function instead.

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

python
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']}")
Output
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 +
You're trying to use operator.add with a non-list type (e.g., dict or string). Ensure the field type and all node returns match: if you annotated as list, every node must return a list, even if empty.
KeyError in invoke()
A node returns a dict with the reducer key, but the key name doesn't match the state definition exactly. Double-check spelling: State defines 'items' but a node returns {'item': [...]}? That won't trigger the reducer.

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.

NEXT

Now that you can accumulate lists, learn how to consume them: using <strong>reduce()</strong> on a list field to flatten or summarize accumulated data before passing it to an LLM.

Community Notes

No notes yetBe the first to share a version-specific fix or tip.