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

One node, many items

Lesson 9 ran two different nodes side by side. This is how you run the same node once per item, when you do not know until the run starts how many items there are.

Twenty tickets arrive. Each one needs an answer, and you cannot draw twenty nodes because tomorrow there will be five.

Two states, one big and one small

The outer state holds all the tickets. The inner one holds a single ticket, and that is all a worker ever sees.

python
import operator
from typing import Annotated
from typing_extensions import TypedDict

class State(TypedDict):
    tickets: list
    replies: Annotated[list, operator.add]

class One(TypedDict):
    ticket: str

replies gets the reducer from lesson 10. Every copy of the worker finishes in the same step and all of them write to that key, which is exactly the situation that needed one.

The worker, and the thing that hands out work

python
def answer(state: One):
    return {"replies": [f"re: {state['ticket']}"]}

It takes a different state class. This node never sees the ticket list, only the one ticket it was handed, which is what makes it easy to write and easy to reuse.

python
from langgraph.types import Send

def fan_out(state):
    return [Send("answer", {"ticket": t}) for t in state["tickets"]]

Send means run this node with this state, not the whole state, only what you hand it. Returning a list of three means three runs of the same node, side by side, in one step.

fan_out goes in a conditional edge, exactly like pick_team in lesson 6. The difference is that instead of one node name it returns a list of Send objects, each carrying its own small state.

Wire it and run it

python
from langgraph.graph import StateGraph, START

builder = StateGraph(State)
builder.add_node("answer", answer)
builder.add_conditional_edges(START, fan_out, ["answer"])
Example
tickets = ["late", "wrong size", "charged twice"]

print(builder.compile().invoke({"tickets": tickets, "replies": []})["replies"])

Three tickets in, three replies out, and the number came from the data rather than from how many edges you drew. This is the pattern people call map and reduce: split the work, do the pieces side by side, and let a reducer put the answers back together.

They come back in whatever order they finish
There is no ordering guarantee worth relying on. If the order matters, send an index along with each item and sort at the end.
Try it yourself
  • Add a fourth ticket and confirm nothing else changes.
  • Take operator.add off replies and read the error.
  • Send an index with each ticket and print it in the reply.

Slow is fine. Stopping is the only problem.