Course · Part 4, Teams · Lesson 4.3 · 14 min
Parallelization: the race that corrupted the state
Two workers edited the same report at the same time, each from the version it had read, and the second save erased the first.
Before you read: find the line
Read the run below once. Decide which line is the mistake. The lesson tells you.
- 1userUpdate the Q3 report with the new revenue and cost figures.
- 2systemorchestrator: fan out: worker A (revenue), worker B (costs); shared doc q3-report
- 3toolA: read_doc(id="q3-report") -> version 7
- 4toolB: read_doc(id="q3-report") -> version 7
- 5toolA: write_doc(id="q3-report", body=<v7 with new revenue>) -> saved version 8
- 6toolB: write_doc(id="q3-report", body=<v7 with new costs>) -> saved version 9
- 7llmorchestrator: "Both sections updated."
- 8donestatus: success
What broke
A reporting agent builds the quarterly summary. To make it fast, the orchestrator fans out two workers: one updates the revenue section, one updates the costs section. Both run at once. Each worker reads the whole document, edits its section, and writes the whole document back. On the last day of the quarter worker A read version 7, worker B read version 7, A saved version 8 with the new revenue, and B saved version 9 with the new costs, built from version 7. Version 9 has the new costs and the old revenue. The orchestrator reported both sections updated. The run ended with status success.
The finance lead found the stale revenue number in the board deck. Nobody could tell when it had reverted, because the log said both updates succeeded. They had. One of them was then overwritten.
Why it broke
Line 6 is the write that erased the other write. B's document was built from a snapshot that was already stale, and the write tool accepted it. This is a lost update, the oldest race there is, and it has nothing to do with language models. Two writers, one shared value, no coordination.
It happened because parallel work was added to a design made for one worker. Anthropic's "Building effective agents" (December 2024) describes parallelization in two forms: sectioning, where a task is split into independent subtasks that run at the same time, and voting, where the same task runs several times. The word that matters is independent. Two workers that write the same document are not independent, however different their sections are.
Frameworks know this. LangGraph refuses the ambiguous case: if two parallel nodes write the same state key in one step and the key has no reducer, it raises InvalidUpdateError rather than guess which write wins. That is the good default. Silent last write wins is the bad one, and a plain write_doc tool gives you exactly that.
What a senior engineer does
Three fixes, in order of preference. First, change what the workers write. Each worker returns only its section as a structured result: the section name and the new body. The orchestrator merges the two sections into the document with one write, in code. The workers never touch the shared document. This is sectioning done properly, and it is how a reducer works: the parallel branches produce values, one function combines them.
Second, if workers must write to a shared store, make the write conditional. The read returns a version. The write carries the version it was based on, and the store rejects it if the current version has moved. HTTP has done this for years with ETags and If-Match; RFC 9110 section 13.1.1 defines the header. The rejected worker re-reads and retries, or gives up and reports. A lost update becomes a visible conflict.
Third, if neither is possible, do not parallelize that step. Run revenue, then costs. The time saved by running two model calls at once is seconds. The time lost finding a reverted number in a board deck is a day.
Whatever you pick, tag every write with the worker and the run, so when the trace shows two saves you can see which one came from which snapshot.
What to say in the interview
"Line 6. Worker B saved a whole document built from version 7 after A had already saved version 8, and the write tool took it. It is a lost update. I would have workers return their section as data and let the orchestrator do the one merge, or make the write conditional on the version read." Then say when you would not parallelize at all: when the steps share state and the gain is seconds.
The line
- 1userUpdate the Q3 report with the new revenue and cost figures.
- 2systemorchestrator: fan out: worker A (revenue), worker B (costs); shared doc q3-report
- 3toolA: read_doc(id="q3-report") -> version 7
- 4toolB: read_doc(id="q3-report") -> version 7
- 5toolA: write_doc(id="q3-report", body=<v7 with new revenue>) -> saved version 8
- 6toolB: write_doc(id="q3-report", body=<v7 with new costs>) -> saved version 9
- 7llmorchestrator: "Both sections updated."
- 8donestatus: success
Line 6. Worker B wrote a whole document built from version 7, after A had already saved version 8 with the new revenue. The write tool accepted it, and the new revenue was gone. Nothing in the run failed, so nothing said so.
The pause
Both workers must keep running in parallel. Name the one change to the write that turns the silent lost update into a visible error.
In the interview
“You fan out work to several agents at once. How do you keep them from stepping on each other?”
Say your answer out loud, in under a minute. Then compare.
Notes
- Reference entry: Parallelization, pattern 3 of 21.
- Building effective agents, Anthropic: parallelization, sectioning and voting (2024-12-19)
- INVALID_CONCURRENT_GRAPH_UPDATE, LangGraph docs
- RFC 9110, HTTP Semantics, section 13.1.1 If-Match (2022-06)