Course Content
LangGraph Agents
7 sections · 49 lessons
How do you manage concurrency issues when multiple nodes update overlapping state?
What you need to know
Python
1import operator2from typing import Annotated, TypedDict34class State(TypedDict):5 findings: Annotated[list, operator.add] # safe: each branch appends6 best_quote: Annotated[int, min] # safe: order-independent7 status: str # unsafe if two branches write itRules
- Reducer on every shared key. The error message says "Can receive only one value per step" — the fix is a reducer, not sequential execution.
- No read-modify-write. Two branches doing
{"count": state["count"] + 1}both read the same old value. Emit an item per branch and count later, or useoperator.addon an int. - Order-independent merge. Sort lists by a stable key (source id, timestamp) before using them.
- Join correctly. With equal-length branches, the join node runs once. With unequal lengths (one branch has two steps, another has one), the join would run early and again later;
add_node("merge", merge, defer=True)makes it wait until nothing else is pending. - Idempotent side effects. If a run fails mid-step, LangGraph saves the writes of branches that finished, so they are not re-run; the failed one is. Anything that charges, sends or deletes needs an idempotency key.
What LangGraph does not do
It does not lock external systems. If two branches update the same database row, you need the database's own concurrency control.
A real-life example
A travel agent searches three flight providers in parallel for a Mumbai to Singapore fare. Each branch wrote {"best_fare": its_price} to a plain key and the run crashed; a developer changed it to a read-compare-write, which returned whichever branch finished last, sometimes Rs 31,400 when Rs 27,900 was available. The fix: quotes: Annotated[list, operator.add] with each branch appending {"provider": ..., "fare": ...}, and a pick node with defer=True that sorts and takes the minimum. Results became correct and repeatable.
Follow-up questions to expect
- "Is the parallelism real threads?" — Sync nodes run in a thread pool; async nodes run concurrently on the event loop. Either way, updates merge only at the end of the super-step.
- "How do you limit how many branches run at once?" — Set
max_concurrencyin the run config, and use client-side rate limiters for provider limits. - "What if one branch fails?" — By default the step fails and the error propagates, but completed branches' writes are saved. Catch expected errors inside the branch and return a partial result so the run continues.