Send: run one node per item
Send runs the same node once for each item in a list, each with its own state, then a reducer collects the results. It is LangGraph's map step.
Last updated: 27 Sep, 2026 · LangGraph 1.2
Parallel edges run a fixed set of different nodes. Send is for the other case: the same node, run once per item, when you only know the number of items at run time.
The Send API
from langgraph.types import Send
def fan_out(state):
return [Send("worker", {"item": i}) for i in state["items"]]
builder.add_conditional_edges(START, fan_out, ["worker"])The state with a reducer
Start with the state. results carries the add reducer so every worker can add to it at the same time.
from operator import add
from typing_extensions import TypedDict, Annotated
from langgraph.types import Send
from langgraph.graph import StateGraph, START, END
class State(TypedDict):
items: list[int]
results: Annotated[list[int], add] # reducer: gather every worker's resultThe fan-out function
Here is the new idea. fan_out returns a list of Send objects, one per item. Each Send names the node to run and the small state to hand it.
def fan_out(state):
# one Send per item, each carrying its own small state
return [Send("square", {"item": i}) for i in state["items"]]The worker node
The worker runs once per item. It reads its one item and returns that item squared, wrapped in a list so the reducer can append it.
def square(s): # receives {"item": i}, one item at a time
return {"results": [s["item"] ** 2]}Wiring the fan-out
Wire fan_out to START with add_conditional_edges, the same call used for routing. Running over three items gives three squared values collected in results.
builder = StateGraph(State)
builder.add_node("square", square)
builder.add_conditional_edges(START, fan_out, ["square"]) # fan_out decides the Sends
builder.add_edge("square", END)
print(builder.compile().invoke({"items": [1, 2, 3], "results": []}))Squaring a list end to end
The same pieces in one file, ready to run.
from operator import add
from typing_extensions import TypedDict, Annotated
from langgraph.types import Send
from langgraph.graph import StateGraph, START, END
class State(TypedDict):
items: list[int]
results: Annotated[list[int], add]
def fan_out(state):
return [Send("square", {"item": i}) for i in state["items"]]
def square(s): # receives {"item": i}
return {"results": [s["item"] ** 2]}
builder = StateGraph(State)
builder.add_node("square", square)
builder.add_conditional_edges(START, fan_out, ["square"])
builder.add_edge("square", END)
print(builder.compile().invoke({"items": [1, 2, 3], "results": []}))How the runs combined
fan_outreturns oneSendper item, each carrying its own small state{"item": i}.squareruns three times, once per item, and each run returns one squared value.- The
addreducer onresultscollects all three into one list. The three runs happen in parallel, so the order can vary; the reducer gathers them all.
Send vs parallel edges
| Parallel edges | Send | |
|---|---|---|
| Runs | A fixed set of different nodes | The same node, once per item |
| Item count known | At build time | Only at run time |
| Use for | Do A and B side by side | Map over a list |
Where fan-out fits
- Scoring or summarising each document in a retrieved set.
- Running the same check over every item in a batch, then gathering the results.
operator.add), or the parallel writes collide. Import Send from langgraph.types.Related
- Previous: Loops and the recursion limit
- Next: Subgraphs: a graph inside a graph
- Reference: Graph API: Send
- Change
squareto cube and predict the new list. - Remove the reducer on
resultsand run. What error do you get? - Fan out over
["a", "b"]and have the worker upper-case each.
You understood something today that you didn't yesterday.