Run graph nodes in parallel #1018

Closed
opened 2026-02-20 17:42:46 -05:00 by yindo · 4 comments
Owner

Originally created by @jsxzhub on GitHub (Oct 24, 2025).

Checked other resources

  • This is a bug, not a usage question. For questions, please use the LangChain Forum (https://forum.langchain.com/).
  • I added a clear and detailed title that summarizes the issue.
  • I read what a minimal reproducible example is (https://stackoverflow.com/help/minimal-reproducible-example).
  • I included a self-contained, minimal example that demonstrates the issue INCLUDING all the relevant imports. The code run AS IS to reproduce the issue.

Example Code

import operator
from typing import Annotated, Any
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END

class State(TypedDict):
    # The operator.add reducer fn makes this append-only
    aggregate: Annotated[list, operator.add]

def a(state: State):
    print(f'Adding "A" to {state["aggregate"]}')
    return {"aggregate": ["A"]}

def b(state: State):
    print(f'Adding "B" to {state["aggregate"]}')
    return {"aggregate": ["B"]}

def b_2(state: State):
    print(f'Adding "B_2" to {state["aggregate"]}')
    return {"aggregate": ["B_2"]}

def c(state: State):
    print(f'Adding "C" to {state["aggregate"]}')
    return {"aggregate": ["C"]}

def c_2(state: State):
    print(f'Adding "C_2" to {state["aggregate"]}')
    return {"aggregate": ["C_2"]}

def d(state: State):
    print(f'Adding "D" to {state["aggregate"]}')
    return {"aggregate": ["D"]}

builder = StateGraph(State)
builder.add_node(a)
builder.add_node(b)
builder.add_node(b_2)
builder.add_node(c)
builder.add_node(d)  
builder.add_edge(START, "a")
builder.add_edge("a", "b")
builder.add_edge("a", "c")
builder.add_edge("b", "b_2")
builder.add_edge("b_2", "d")
builder.add_edge("c", "d")
builder.add_edge("d", END)
graph = builder.compile()

from IPython.display import Image, display

display(Image(graph.get_graph().draw_mermaid_png()))

print(graph.invoke({"aggregate": []}))

Error Message and Stack Trace (if applicable)


Description

result:
Adding "A" to []

Adding "B" to ['A']

Adding "C" to ['A']

Adding "B_2" to ['A', 'B', 'C']

Adding "D" to ['A', 'B', 'C']

Adding "D" to ['A', 'B', 'C', 'B_2', 'D']

{'aggregate': ['A', 'B', 'C', 'B_2', 'D', 'D']}

Why did "D" execute twice?

System Info

System Information

OS: Linux
OS Version: #1 SMP PREEMPT_DYNAMIC Debian 6.1.148-1 (2025-08-26)
Python Version: 3.13.9 | packaged by Anaconda, Inc. | (main, Oct 21 2025, 19:16:10) [GCC 11.2.0]

Package Information

langchain_core: 1.0.0
langchain: 1.0.2
langsmith: 0.4.37
langchain_openai: 1.0.1
langgraph_api: 0.4.46
langgraph_cli: 0.4.4
langgraph_runtime_inmem: 0.14.1
langgraph_sdk: 0.2.9

Optional packages not installed

langserve

Other Dependencies

blockbuster: 1.5.25
claude-agent-sdk: Installed. No version info available.
click: 8.3.0
cloudpickle: 3.1.1
cryptography: 44.0.3
grpcio: 1.76.0
grpcio-tools: 1.76.0
httpx: 0.28.1
jsonpatch: 1.33
jsonschema-rs: 0.29.1
langchain-anthropic: Installed. No version info available.
langchain-aws: Installed. No version info available.
langchain-community: Installed. No version info available.
langchain-deepseek: Installed. No version info available.
langchain-fireworks: Installed. No version info available.
langchain-google-genai: Installed. No version info available.
langchain-google-vertexai: Installed. No version info available.
langchain-groq: Installed. No version info available.
langchain-huggingface: Installed. No version info available.
langchain-mistralai: Installed. No version info available.
langchain-ollama: Installed. No version info available.
langchain-perplexity: Installed. No version info available.
langchain-together: Installed. No version info available.
langchain-xai: Installed. No version info available.
langgraph: 1.0.1
langgraph-checkpoint: 3.0.0
langsmith-pyo3: Installed. No version info available.
openai: 2.6.0
openai-agents: Installed. No version info available.
opentelemetry-api: 1.38.0
opentelemetry-exporter-otlp-proto-http: 1.38.0
opentelemetry-sdk: 1.38.0
orjson: 3.11.3
packaging: 25.0
protobuf: 6.33.0
pydantic: 2.12.3
pyjwt: 2.10.1
pytest: Installed. No version info available.
python-dotenv: 1.1.1
pyyaml: 6.0.3
requests: 2.32.5
requests-toolbelt: 1.0.0
rich: Installed. No version info available.
sse-starlette: 2.1.3
starlette: 0.48.0
structlog: 25.4.0
tenacity: 9.1.2
tiktoken: 0.12.0
truststore: 0.10.4
typing-extensions: 4.15.0
uvicorn: 0.38.0
vcrpy: Installed. No version info available.
watchfiles: 1.1.1
zstandard: 0.25.0

Originally created by @jsxzhub on GitHub (Oct 24, 2025). ### Checked other resources - [x] This is a bug, not a usage question. For questions, please use the LangChain Forum (https://forum.langchain.com/). - [x] I added a clear and detailed title that summarizes the issue. - [x] I read what a minimal reproducible example is (https://stackoverflow.com/help/minimal-reproducible-example). - [x] I included a self-contained, minimal example that demonstrates the issue INCLUDING all the relevant imports. The code run AS IS to reproduce the issue. ### Example Code ```python import operator from typing import Annotated, Any from typing_extensions import TypedDict from langgraph.graph import StateGraph, START, END class State(TypedDict): # The operator.add reducer fn makes this append-only aggregate: Annotated[list, operator.add] def a(state: State): print(f'Adding "A" to {state["aggregate"]}') return {"aggregate": ["A"]} def b(state: State): print(f'Adding "B" to {state["aggregate"]}') return {"aggregate": ["B"]} def b_2(state: State): print(f'Adding "B_2" to {state["aggregate"]}') return {"aggregate": ["B_2"]} def c(state: State): print(f'Adding "C" to {state["aggregate"]}') return {"aggregate": ["C"]} def c_2(state: State): print(f'Adding "C_2" to {state["aggregate"]}') return {"aggregate": ["C_2"]} def d(state: State): print(f'Adding "D" to {state["aggregate"]}') return {"aggregate": ["D"]} builder = StateGraph(State) builder.add_node(a) builder.add_node(b) builder.add_node(b_2) builder.add_node(c) builder.add_node(d) builder.add_edge(START, "a") builder.add_edge("a", "b") builder.add_edge("a", "c") builder.add_edge("b", "b_2") builder.add_edge("b_2", "d") builder.add_edge("c", "d") builder.add_edge("d", END) graph = builder.compile() from IPython.display import Image, display display(Image(graph.get_graph().draw_mermaid_png())) print(graph.invoke({"aggregate": []}))� ``` ### Error Message and Stack Trace (if applicable) ```shell ``` ### Description result: Adding "A" to []
 Adding "B" to ['A']
 Adding "C" to ['A']
 Adding "B_2" to ['A', 'B', 'C']
 Adding "D" to ['A', 'B', 'C']
 Adding "D" to ['A', 'B', 'C', 'B_2', 'D']
 {'aggregate': ['A', 'B', 'C', 'B_2', 'D', 'D']} Why did "D" execute twice? ### System Info System Information ------------------ > OS: Linux > OS Version: #1 SMP PREEMPT_DYNAMIC Debian 6.1.148-1 (2025-08-26) > Python Version: 3.13.9 | packaged by Anaconda, Inc. | (main, Oct 21 2025, 19:16:10) [GCC 11.2.0] Package Information ------------------- > langchain_core: 1.0.0 > langchain: 1.0.2 > langsmith: 0.4.37 > langchain_openai: 1.0.1 > langgraph_api: 0.4.46 > langgraph_cli: 0.4.4 > langgraph_runtime_inmem: 0.14.1 > langgraph_sdk: 0.2.9 Optional packages not installed ------------------------------- > langserve Other Dependencies ------------------ > blockbuster: 1.5.25 > claude-agent-sdk: Installed. No version info available. > click: 8.3.0 > cloudpickle: 3.1.1 > cryptography: 44.0.3 > grpcio: 1.76.0 > grpcio-tools: 1.76.0 > httpx: 0.28.1 > jsonpatch: 1.33 > jsonschema-rs: 0.29.1 > langchain-anthropic: Installed. No version info available. > langchain-aws: Installed. No version info available. > langchain-community: Installed. No version info available. > langchain-deepseek: Installed. No version info available. > langchain-fireworks: Installed. No version info available. > langchain-google-genai: Installed. No version info available. > langchain-google-vertexai: Installed. No version info available. > langchain-groq: Installed. No version info available. > langchain-huggingface: Installed. No version info available. > langchain-mistralai: Installed. No version info available. > langchain-ollama: Installed. No version info available. > langchain-perplexity: Installed. No version info available. > langchain-together: Installed. No version info available. > langchain-xai: Installed. No version info available. > langgraph: 1.0.1 > langgraph-checkpoint: 3.0.0 > langsmith-pyo3: Installed. No version info available. > openai: 2.6.0 > openai-agents: Installed. No version info available. > opentelemetry-api: 1.38.0 > opentelemetry-exporter-otlp-proto-http: 1.38.0 > opentelemetry-sdk: 1.38.0 > orjson: 3.11.3 > packaging: 25.0 > protobuf: 6.33.0 > pydantic: 2.12.3 > pyjwt: 2.10.1 > pytest: Installed. No version info available. > python-dotenv: 1.1.1 > pyyaml: 6.0.3 > requests: 2.32.5 > requests-toolbelt: 1.0.0 > rich: Installed. No version info available. > sse-starlette: 2.1.3 > starlette: 0.48.0 > structlog: 25.4.0 > tenacity: 9.1.2 > tiktoken: 0.12.0 > truststore: 0.10.4 > typing-extensions: 4.15.0 > uvicorn: 0.38.0 > vcrpy: Installed. No version info available. > watchfiles: 1.1.1 > zstandard: 0.25.0
yindo added the bugpending labels 2026-02-20 17:42:46 -05:00
yindo closed this issue 2026-02-20 17:42:46 -05:00
Author
Owner

@IvanMiao commented on GitHub (Oct 25, 2025):

Seems it's not a bug. In your graph, node D has two incoming edges, one from b_2, one from c. This creates two separate paths that reach D:

  • START - A - B - B_2 - D
  • START - A - C - D

When you have multiple paths leading to the same node, seems that LangGraph executes that node once for each path.

@IvanMiao commented on GitHub (Oct 25, 2025): Seems it's not a bug. In your graph, node D has two incoming edges, one from `b_2`, one from `c`. This creates two separate paths that reach D: - START - A - B - B_2 - D - START - A - C - D When you have multiple paths leading to the same node, seems that LangGraph executes that node once for each path.
Author
Owner

@jsxzhub commented on GitHub (Oct 26, 2025):

remove "b_2"

builder.add_edge("a", "b")
builder.add_edge("a", "c")
builder.add_edge("b", "d")
builder.add_edge("c", "d")
builder.add_edge("d", END)

"D" execute only once.

Adding "A" to []
Adding "B" to ['A']
Adding "C" to ['A']
Adding "D" to ['A', 'B', 'C']
{'aggregate': ['A', 'B', 'C', 'D']}

according to what you said,"D" execute twice:

START - A - B - D
START - A - C - D

Seems it's not a bug. In your graph, node D has two incoming edges, one from b_2, one from c. This creates two separate paths that reach D:

* START - A - B - B_2 - D

* START - A - C - D

When you have multiple paths leading to the same node, seems that LangGraph executes that node once for each path.

@jsxzhub commented on GitHub (Oct 26, 2025): remove "b_2" builder.add_edge("a", "b") builder.add_edge("a", "c") builder.add_edge("b", "d") builder.add_edge("c", "d") builder.add_edge("d", END) "D" execute only once. Adding "A" to [] Adding "B" to ['A'] Adding "C" to ['A'] Adding "D" to ['A', 'B', 'C'] {'aggregate': ['A', 'B', 'C', 'D']} according to what you said,"D" execute twice: START - A - B - D START - A - C - D > Seems it's not a bug. In your graph, node D has two incoming edges, one from `b_2`, one from `c`. This creates two separate paths that reach D: > > * START - A - B - B_2 - D > > * START - A - C - D > > > When you have multiple paths leading to the same node, seems that LangGraph executes that node once for each path.
Author
Owner

@le-codeur-rapide commented on GitHub (Nov 7, 2025):

Hello @jsxzhub
This is something that was a little bit disturbing for me too at first haha.

In your graph, there are 2 edges pointing to d (c and b_2) :

  • This does not mean that d needs b_2 and c to be completed to run
  • This means that if b_2 just ran, or c just ran at some step of the graph, d will run at the next step of the graph

If you want d to run only if c and b_2 are completed, you can use the defer parameter of add_edge langgraph doc defer

Hope this helps !

@le-codeur-rapide commented on GitHub (Nov 7, 2025): Hello @jsxzhub This is something that was a little bit disturbing for me too at first haha. In your graph, there are 2 edges pointing to d (c and b_2) : - This does not mean that d needs b_2 and c to be completed to run - This means that if b_2 just ran, or c just ran at some step of the graph, d will run at the next step of the graph If you want d to run only if c and b_2 are completed, you can use the `defer` parameter of `add_edge` [langgraph doc defer](https://docs.langchain.com/oss/python/langgraph/use-graph-api#defer-node-execution) Hope this helps !
Author
Owner

@casparb commented on GitHub (Nov 7, 2025):

Hi @jsxzhub, good question, this is actually the intended behavior of LangGraph, and understanding it requires knowing how the graph execution model works.

LangGraph executes your graph in super-steps. Each super-step can be thought of as a single iteration over graph nodes, in parallel when possible. A node executes in a super-step when at least one of its incoming edges has executed.

In your first example:

  • Super-step 1: Node a runs
  • Super-step 2: Nodes b and c run in parallel (both triggered from a)
  • Super-step 3: Node b_2 runs (triggered from b), but d also runs here because c just finished
  • Super-step 4: Node d runs again because b_2 just finished

So d doesn't execute twice due to a bug, it executes once per super-step. Since d has two incoming edges (from b_2 and c), it activates twice.

If you want to enforce that a node waits for all incoming edges to complete before running, use the defer parameter:

builder.add_node(d, defer=True)  

With defer=True, node d won't run until both b_2 and c have completed.

I would encourage you, and everyone else confused by this behavior, to view your graph in Studio. You can step through each super-step and see which nodes activate. It makes things much clearer.

Docs

@casparb commented on GitHub (Nov 7, 2025): Hi @jsxzhub, good question, this is actually the intended behavior of LangGraph, and understanding it requires knowing how the graph execution model works. LangGraph executes your graph in super-steps. Each super-step can be thought of as a single iteration over graph nodes, in parallel when possible. A node executes in a super-step when at least one of its incoming edges has executed. In your first example: - **Super-step 1:** Node `a` runs - **Super-step 2:** Nodes `b` and `c` run in parallel (both triggered from `a`) - **Super-step 3:** Node `b_2` runs (triggered from `b`), but `d` also runs here because `c` just finished - **Super-step 4:** Node `d` runs again because `b_2` just finished So `d` doesn't execute twice due to a bug, it executes once per super-step. Since `d` has two incoming edges (from `b_2` and `c`), it activates twice. If you want to enforce that a node waits for all incoming edges to complete before running, use the `defer` parameter: ```python builder.add_node(d, defer=True) ``` With `defer=True`, node `d` won't run until both `b_2` and `c` have completed. I would encourage you, and everyone else confused by this behavior, to view your graph in [Studio](https://docs.langchain.com/oss/python/langgraph/local-server). You can step through each super-step and see which nodes activate. It makes things much clearer. Docs - [Thinking in LangGraph](https://docs.langchain.com/oss/python/langgraph/thinking-in-langgraph) - [Defer node execution](https://docs.langchain.com/oss/python/langgraph/use-graph-api#defer-node-execution)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/langgraph#1018