Bypassing Langgraph SuperSteps with Sub-graph

I am currently building a graph orchestration and execution platform using LangGraph as the backend.
I’m trying to optimize the execution time of a graph by bypassing LangGraph’s superstep execution model. One approach I’ve experimented with is encapsulating parts of the workflow into subgraphs, which improves performance in some graph designs.
Is there any mechanism in LangGraph that allows execution to bypass or relax the superstep synchronization?

For example, consider the following graph. [Each node performs a complex algorithm and cannot be easily merged with other nodes at the backend level.]

Are there any recommended patterns, APIs, or execution strategies in LangGraph to reduce or eliminate this synchronization overhead while preserving correctness?
Is there any Langgraph supersteps API documentation that I could read so i could try to tackle the problem?

Any suggestions or helps from the community or the contributor would be very nice.

Adding one more example with the type of the node.

Could I use the planner- supersteps API of the langgraph so somehow i could auto-rearrange the graph?

Code of the langgraph that does decision on which node falls to which superstep would be also very helpful.

Hey @albertchristianto,

A few clarifications that should unblock you:

There is no “planner-supersteps API”. This doesn’t exist in LangGraph. Superstep assignment is fully automatic and determined by your graph topology - not configurable.

How the assignment actually works (from _algo.py):
A node runs in superstep N if any of its trigger channels was updated in superstep N-1. All such nodes run concurrently in a thread pool. That’s it - no API needed, it’s purely your graph’s edges.

# _algo.py:1260 - the exact trigger check:
for chan in proc.triggers:
    if versions.get(chan) > seen.get(chan):
        return True  # this node fires in the next superstep

You can’t bypass the barrier between supersteps - it’s fundamental to state consistency (all writes from step N must be applied before step N+1 can determine which nodes to trigger). What you can do:

  1. Maximize parallelism within a superstep - connect multiple independent nodes from the same parent; they all fire in the same superstep in parallel automatically.

  2. Send API for dynamic fan-out - return [Send("node", state) for item in items] from a conditional edge; all instances run concurrently in one superstep (map-reduce pattern).

  3. Subgraph encapsulation (your instinct is right) - a compiled subgraph added as a node counts as 1 node in the parent. A chain A→B→C inside a subgraph = 1 parent superstep instead of 3. Compile the subgraph without a checkpointer to skip internal checkpoint I/O:

    sub = inner_builder.compile(checkpointer=None)
    parent.add_node("my_chain", sub)
    
  4. Use ainvoke / async nodes for I/O-bound work to get true parallelism within a superstep without GIL contention.

The subgraph approach is the closest thing to what you’re looking for - it collapses the parent-level synchronization barriers for sequential chains without changing LangGraph’s internal model.

Hi, @pawel-twardziak

I really appreciate your immediate response—I wasn’t expecting to hear back so quickly. Thank you for all the help and for directing me to the correct file.

It looks like I’ll need to implement a graph simplification algorithm to handle more complex graphs.

Are there any plans within LangGraph to further optimize this specific issue (or maybe not an issue) beyond the existing subgraph API?

hi @albertchristianto

anytime!
Glad it helped! On your question about future optimizations - actually, LangGraph already has the answer for your use case, and it’s already released: the Functional API (@entrypoint + @task).

Instead of expressing your graph as a topology of nodes/edges (where parallelism depends on superstep assignment), the functional API lets you write plain Python with futures. You dispatch @task calls and collect results whenever you need them - the scheduler handles parallelism automatically without you having to reason about superstep barriers at all:

from langgraph.func import entrypoint, task

@task
def step_a(input): ...  # complex algorithm

@task
def step_b(input): ...  # complex algorithm

@task
def step_c(a_result, b_result): ...  # depends on both

@entrypoint()
def my_workflow(input):
    fa = step_a(input)        # dispatched immediately
    fb = step_b(input)        # dispatched immediately, runs in parallel with A
    fc = step_c(fa.result(), fb.result())  # waits only for A and B, then runs
    return fc.result()

This means your “graph simplification algorithm” reduces to Python dependency ordering - you just call tasks and collect futures at the point you need the result. No need to build a topology simplifier on top of LangGraph.

The functional API supports checkpointing, retry policies, caching, and interrupt() for human-in-the-loop - same guarantees as StateGraph, but with far less synchronization overhead for complex dependency graphs.

You also might want to mix both worlds: Graph API and Functional API.

Docs: LangGraph overview - Docs by LangChain (look for the Functional API / @entrypoint section)

Hi @pawel-twardziak

Got it. Thank you so much once again.

It is very helpful to know the Functional API from Langgraph.