Send is LangGraph's data-dependent fan-out primitive: return a list of Send objects from a conditional edge to spawn N parallel branches with distinct payloads.
Imagine a manager who reads a customer request and decides 'this needs three things done at the same time: order the parts, schedule the technician, and email the customer.' The manager writes three separate work orders, hands one to each team, and the teams work in parallel. Later the results come back together for the final report. In LangGraph, the work-order writing is the Send primitive. The manager does not know how many work orders there will be until they read the request - it could be one, three, or ten. Static graph edges cannot do that because the number of branches has to be known when the graph is built. Send decides at runtime, based on the data.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
LangGraph's Send is the primitive that makes the framework genuinely concurrent and not just async-friendly. Most graph orchestrators express static topology: each node has a known set of successors, edges are wired at build time, and execution follows the declared shape. That works for routing problems (pick one of N next nodes) but cannot express fan-out problems (run M parallel branches where M is decided at runtime by the data).
Send fills that gap. A conditional edge function returns a list of Send objects, each carrying a target node name and a payload. The runtime spawns one concurrent branch per Send, runs them in parallel, and merges their outputs back through per-key reducers. The pattern is map-reduce for agents, and it is what enables LangGraph's parallel research, multi-source RAG, and per-item evaluation patterns.
This walkthrough covers the runtime semantics of Send, why static edges cannot substitute, the reducer machinery that makes parallel writes safe, and the production patterns that use the primitive at scale.
Mental model:
add_edgeis graph wiring at build time.Sendis graph wiring at runtime, with one wire per data item.
Why static edges cannot express fan-out
What add_edge and add_conditional_edges actually do
In LangGraph, the static-graph primitives are:
graph.add_edge('planner', 'worker') # always go from planner to worker
graph.add_conditional_edges('planner', router_fn) # router_fn returns one of N next-node names
Both primitives bind a node to a fixed (or fixed set of) successor. The router function returns one node name. After traversing, the runtime is at exactly one node.
The fan-out problem
Consider: a planner reads a research brief and decomposes it into N subqueries. N is not known at graph-build time; it depends on the brief. You want to run all N subqueries in parallel.
With static edges, you cannot express this. You could declare 10 worker nodes (worker_1 through worker_10) and have the router pick one, but that gives you routing among 10, not parallelism across N.
Why this matters
Without fan-out, parallel work in LangGraph becomes either:
- A loop over subqueries calling a worker sequentially (no parallelism).
- A custom async function outside the graph (loses observability, state-management, and checkpoint integration).
Neither is acceptable for the workloads that motivated multi-agent: parallel research, parallel evaluation, parallel retrieval. The framework needed a primitive that says 'spawn N branches now, one per data item.'
Situations where this technique stops working.
2–4 min · Everything important, quickly.
Real products, models, and research that use this idea.
- LangGraph documentation uses Send for the canonical parallel-research pattern: planner decomposes, search agents run in parallel, synthesis reduces.
- Anthropic's research mode in Claude Opus 4.7 uses a Send-style fan-out internally to dispatch parallel web searches for complex queries.
What an interviewer would ask next. Try answering before peeking at the approach.
QHow does LangGraph merge results from Send-spawned parallel branches?
Each branch returns a state update; the runtime applies the per-key reducer to merge updates. For a 'results' list, the reducer is typically operator.add (concatenate). For a 'count' integer, it might be sum. For a key with no reducer, last write wins (and parallel writes raise unless you set a reducer).
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Trying to fan out with add_edge or add_conditional_edges alone. Those route to a single next node; Send is the only primitive that returns multiple parallel branches with distinct payloads.
60 second bullets to scan on the way to the call.
What Send returns and how the runtime interprets it
Why add_edge and add_conditional_edges cannot express data-dependent fan-out
Primary sources. Browse if you want the original framing.
Same topic, related formats. Practice these next.