LangGraphLangGraph 1.2 · Python 3.10+
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
38 small wins to finish your pathNext lesson →

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

python
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.

python
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 result

The 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.

python
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.

python
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.

python
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.

Example
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_out returns one Send per item, each carrying its own small state {"item": i}.
  • square runs three times, once per item, and each run returns one squared value.
  • The add reducer on results collects 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 edgesSend
RunsA fixed set of different nodesThe same node, once per item
Item count knownAt build timeOnly at run time
Use forDo A and B side by sideMap 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.
Watch out. The key the workers write to needs a reducer (like operator.add), or the parallel writes collide. Import Send from langgraph.types.
Try it yourself
  • Change square to cube and predict the new list.
  • Remove the reducer on results and 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.