How to Fix 'state not updating when scaling' in LangGraph (Python)
When LangGraph says your state is not updating when scaling, it usually means the graph is running, but the values you expect to accumulate across nodes are being overwritten, dropped, or isolated per branch. This shows up a lot when you move from a single-node prototype to parallel branches, loops, or multi-step workflows in Python.
The root issue is almost always state schema design: the graph is updating state exactly as defined, but your fields are not marked for accumulation, or you’re mutating state in a way LangGraph does not track.
The Most Common Cause
The #1 cause is using plain dict fields for values that should accumulate across nodes. In LangGraph, if multiple nodes write to the same key and you do not define a reducer, later writes overwrite earlier ones.
A common symptom is seeing state updates in one node, then losing them after branching or scaling out with StateGraph.
| Broken pattern | Fixed pattern |
|---|---|
| Plain list/dict assignment overwrites prior values | Use Annotated[...] with a reducer like operator.add |
| Node mutates input state directly | Node returns a partial update dict |
| No merge strategy for concurrent writes | State schema defines how updates combine |
Broken code
from typing import TypedDict
from langgraph.graph import StateGraph, END
class AgentState(TypedDict):
messages: list[str]
def node_a(state: AgentState):
return {"messages": ["A"]}
def node_b(state: AgentState):
return {"messages": ["B"]}
graph = StateGraph(AgentState)
graph.add_node("a", node_a)
graph.add_node("b", node_b)
graph.set_entry_point("a")
graph.add_edge("a", "b")
graph.add_edge("b", END)
app = graph.compile()
result = app.invoke({"messages": []})
print(result)
This looks fine until you scale to multiple branches or repeated writes. The last writer wins.
Fixed code
from typing import Annotated, TypedDict
import operator
from langgraph.graph import StateGraph, END
class AgentState(TypedDict):
messages: Annotated[list[str], operator.add]
def node_a(state: AgentState):
return {"messages": ["A"]}
def node_b(state: AgentState):
return {"messages": ["B"]}
graph = StateGraph(AgentState)
graph.add_node("a", node_a)
graph.add_node("b", node_b)
graph.set_entry_point("a")
graph.add_edge("a", "b")
graph.add_edge("b", END)
app = graph.compile()
result = app.invoke({"messages": []})
print(result)
# {'messages': ['A', 'B']}
If your error appears during parallel execution, you may also see behavior that looks like this in logs:
- •
InvalidUpdateError: At key 'messages': Can receive only one value per step - •
InvalidConcurrentGraphUpdateError - •state keys appearing empty after a branch rejoins
That’s usually a reducer problem, not a runtime bug.
Other Possible Causes
1. You are mutating state in place
LangGraph expects nodes to return updates, not edit shared objects and hope they persist.
# Wrong
def summarize(state):
state["summary"] = "done"
return {}
# Right
def summarize(state):
return {"summary": "done"}
If you mutate nested objects in place, especially lists and dicts, the graph may not detect the change reliably across steps.
2. Your state type does not match what nodes return
If your schema says int but your node returns a string or list, updates can fail silently until execution hits validation boundaries.
from typing import TypedDict
class State(TypedDict):
count: int
def bad_node(state: State):
return {"count": ["1"]} # wrong type
Fix it by keeping the schema and returned payload aligned:
def good_node(state: State):
return {"count": 1}
If you use Pydantic models or TypedDict, keep field names exact. A typo like message vs messages looks like “state not updating” because you’re writing to a different key.
3. You forgot to define reducers for parallel branches
This becomes visible when two nodes write to the same key in the same step. LangGraph can raise an error instead of guessing how to merge values.
from typing import Annotated, TypedDict
import operator
class State(TypedDict):
logs: Annotated[list[str], operator.add]
Without that reducer, parallel fan-out often leads to errors such as:
- •
InvalidConcurrentGraphUpdateError - •
InvalidUpdateError: At key 'logs': Can receive only one value per step
Use reducers for:
- •lists of messages/logs/events
- •counters that should increment
- •sets of collected outputs
4. You are using checkpointing or threads incorrectly
If you scale by adding persistence and thread IDs, each thread gets its own state history. If you reuse the wrong thread_id, or omit it when resuming, it looks like state reset itself.
config = {"configurable": {"thread_id": "user-123"}}
app.invoke({"messages": []}, config=config)
Common failure mode:
- •one request uses
"user-123" - •another request accidentally uses
"default" - •resume logic reads an empty thread and assumes updates were lost
Also check your checkpointer wiring:
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
app = graph.compile(checkpointer=checkpointer)
Without a checkpointer, nothing persists across separate invocations.
How to Debug It
- •Print the exact state at each node
- •Add logging inside every node.
- •Confirm whether the value disappears before or after the node returns.
def debug_node(state):
print("INPUT:", state)
update = {"messages": ["debug"]}
print("OUTPUT:", update)
return update
- •
Check whether multiple nodes write to the same key
- •If yes, add a reducer.
- •If no reducer exists and branches converge, expect
InvalidUpdateError.
- •
Verify your schema types
- •Compare returned keys against your
TypedDictor Pydantic model. - •Look for wrong field names and wrong value types.
- •Compare returned keys against your
- •
Test with a single linear path first
- •Remove fan-out/fan-in temporarily.
- •If it works linearly but fails when branching, you have a merge/reducer issue.
Prevention
- •
Define reducers up front for any field that will be appended to across steps:
- •messages
- •logs
- •tool results
- •audit trails
- •
Keep nodes pure:
- •take state in
- •return partial updates out
- •do not mutate shared objects in place
- •
Add one integration test that runs:
- •linear flow
- •branched flow
- •resumed flow with checkpointing
That catches most “state not updating when scaling” failures before they hit production.
Keep learning
- •The complete AI Agents Roadmap — my full 8-step breakdown
- •Free: The AI Agent Starter Kit — PDF checklist + starter code
- •Work with me — I build AI for banks and insurance companies
By Cyprian Aarons, AI Consultant at Topiax.
Want the complete 8-step roadmap?
Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.
Get the Starter Kit