In my previous post, we tackled Routing — sending each request to the right specialist. That’s a great pattern when you have one task to dispatch. But what happens when you have several independent tasks that all need to happen before you can produce a final answer?
If you run them one after another, your user is waiting for the sum of all the latencies. And in an LLM-heavy system, that sum gets ugly fast.
That’s where the Parallelization pattern comes in. It’s the single biggest latency win you can pull off in an agentic workflow.
Pattern #3: Parallelization#
The Problem#
Imagine you’re building a research assistant. The user asks: “Give me a comprehensive overview of the impact of government subsidies on clean technology adoption.”
To answer well, you need to:
- Research the renewable energy angle.
- Research the electric vehicle angle.
- Research the carbon capture angle.
- Synthesize everything into a coherent report.
If you chain these four calls sequentially, each one waiting for the previous, your total latency is t1 + t2 + t3 + t4. But steps 1, 2, and 3 don’t depend on each other. They’re just sitting there, idle, while one researcher waits for the other to finish.
This is exactly the kind of pain that asyncio was invented to fix — and the agentic world has its own clean abstraction for it.
The Solution#
Parallelization introduces a fan-out / fan-in structure: the workflow splits into N independent branches that execute concurrently, then merges back into a synthesis step that aggregates the results. Two approaches, depending on how much structure you need:
1. LCEL RunnableParallel — The lightweight approach. You declare a map of named runnables and LangChain runs them concurrently:
map_chain = RunnableParallel({
"summary": summarize_chain,
"questions": questions_chain,
"key_terms": terms_chain,
"topic": RunnablePassthrough(),
})
full_parallel_chain = map_chain | synthesis_prompt | llm | StrOutputParser()
response = await full_parallel_chain.ainvoke({"topic": "The history of space exploration"})Three LLM calls fire at the same time. The result is a dict with summary, questions, key_terms, and the original topic passed through. That dict feeds straight into a synthesis prompt that produces the final answer. One declarative chain, three concurrent calls, one merged output.
A note on terminology: in single-process Python, this is technically concurrency (asyncio swaps between awaiting tasks on one thread), not parallelism. But when the bottleneck is network I/O — which it always is with LLM APIs — the practical effect is identical: total latency collapses from the sum of the individual calls to roughly the slowest one.
2. LangGraph fan-out/fan-in — The structured approach. When each branch is more than just a prompt — when it has its own state, its own tools, its own logic — model it as a graph:
class ResearchState(TypedDict):
topic: str
findings: Annotated[List[str], operator.add] # reducer: appends instead of replacing
synthesis: str
builder = StateGraph(ResearchState)
builder.add_node("research_renewable_energy", research_renewable_energy)
builder.add_node("research_electric_vehicles", research_electric_vehicles)
builder.add_node("research_carbon_capture", research_carbon_capture)
builder.add_node("synthesize", synthesize)
# Fan-out: one START → three parallel researchers
builder.add_edge(START, "research_renewable_energy")
builder.add_edge(START, "research_electric_vehicles")
builder.add_edge(START, "research_carbon_capture")
# Fan-in: all three → synthesize
builder.add_edge("research_renewable_energy", "synthesize")
builder.add_edge("research_electric_vehicles", "synthesize")
builder.add_edge("research_carbon_capture", "synthesize")The pattern is built right into LangGraph: when multiple edges depart from START (or any node), they execute concurrently. The reducer annotation Annotated[List[str], operator.add] is the key — it tells LangGraph how to merge state updates from parallel branches without trampling each other. Each researcher returns {"findings": [...]}, and the reducer concatenates them. No locks, no manual merge logic.
Why This Matters#
Parallelization is not just an optimization — it changes which kinds of agents are practical to build:
- Multi-perspective research: fan out N specialist agents, each looking at a different angle, then synthesize.
- RAG over multiple sources: query several vector stores or APIs concurrently and merge results.
- Ensemble reasoning: ask the same question to N models (or N temperatures) and aggregate.
- Tool calls that don’t depend on each other: hit three APIs in the time it takes to hit one.
The cost? Complexity. Concurrent code is harder to debug, harder to log coherently, and easier to get subtly wrong (race conditions on shared state are why LangGraph’s reducer pattern exists). You also need to be aware of rate limits — fanning out to ten parallel calls is a great way to get throttled by your LLM provider.
Rule of thumb from the book: use this pattern when your workflow has multiple independent operations that can genuinely run at the same time — fetching from several APIs, processing different chunks of data, generating content from multiple angles. If there’s a real data dependency between steps, chaining is still the right answer.
The Bigger Picture#
This is post #3 in my series documenting Antonio Gulli’s Agentic Design Patterns. All credit for the conceptual work goes to him — I’m focused on producing clean, runnable Python implementations that you can clone, modify, and ship.
The interesting thing about Parallelization is that it composes with everything else. A Router can dispatch to a parallel branch. A parallel branch can contain a chain. And in the next chapter — Reflection — we’ll see how a parallel critic step can run alongside the generator to provide feedback in real time.
All the code from this post is in my repository: carlosprados/Agentic_Design_Patterns, specifically under 03_Parallelization/. Both the LCEL and LangGraph examples run with uv run, support Gemini and Ollama via the shared get_llm() abstraction, and are ready to fork.
What’s Next#
In the next post we’ll tackle Reflection — the pattern where an agent critiques its own output (or a separate critic agent does it) and refines until the result is good enough. It’s the Producer-Critic model, and it’s where agents start to feel less like text generators and more like collaborators.
Stay tuned.

