Subgraphs: a graph inside a graph
A subgraph is a compiled graph used as a node inside another graph. It lets you build a piece once and reuse it, and keep a large flow readable.
Last updated: 27 Sep, 2026 · LangGraph 1.2
Once a flow grows past a handful of nodes, packing part of it into a subgraph keeps the parent graph small. If the subgraph shares state keys with its parent, you add it directly as a node.
Adding a compiled graph as a node
sub = sub_builder.compile() # a compiled graph
parent.add_node("step", sub) # use it as a node (shared state keys)
# different schema: wrap it and map the state in and out
def call_sub(state):
out = sub.invoke({"bar": state["foo"]})
return {"foo": out["bar"]}
parent.add_node("step", call_sub)The shared state
Start with the imports and the shared state. Both the inner graph and the outer graph use this same State, so they share the text key.
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
class State(TypedDict):
text: str # the one key both graphs shareThe inner graph
Build a small graph on its own. It has two nodes: clean strips the spaces off the text, and shout upper-cases it. Compiling turns this builder into prepare, a finished graph you can reuse.
sub = StateGraph(State)
sub.add_node("clean", lambda s: {"text": s["text"].strip()}) # remove the spaces
sub.add_node("shout", lambda s: {"text": s["text"].upper()}) # make it upper case
sub.add_edge(START, "clean")
sub.add_edge("clean", "shout")
sub.add_edge("shout", END)
prepare = sub.compile() # a finished, reusable graphThe parent graph
Now the outer graph. It adds the compiled prepare as one node, then a tag node that puts [done] in front of the text. Because both graphs share the text key, the subgraph reads and writes it with no mapping.
parent = StateGraph(State)
parent.add_node("prepare", prepare) # the compiled subgraph is one node
parent.add_node("tag", lambda s: {"text": "[done] " + s["text"]})
parent.add_edge(START, "prepare")
parent.add_edge("prepare", "tag")
parent.add_edge("tag", END)Running the parent
Run the parent with text that has spaces around it. The subgraph trims and upper-cases first, then tag adds the label.
print(parent.compile().invoke({"text": " hello "}))A subgraph inside a graph, end to end
The same pieces in one file, ready to run.
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
class State(TypedDict):
text: str
# a small subgraph that trims and upper-cases
sub = StateGraph(State)
sub.add_node("clean", lambda s: {"text": s["text"].strip()})
sub.add_node("shout", lambda s: {"text": s["text"].upper()})
sub.add_edge(START, "clean")
sub.add_edge("clean", "shout")
sub.add_edge("shout", END)
prepare = sub.compile()
# the parent uses the subgraph as one node
parent = StateGraph(State)
parent.add_node("prepare", prepare)
parent.add_node("tag", lambda s: {"text": "[done] " + s["text"]})
parent.add_edge(START, "prepare")
parent.add_edge("prepare", "tag")
parent.add_edge("tag", END)
print(parent.compile().invoke({"text": " hello "}))How the two graphs shared state
- The subgraph
preparehas its own nodes (clean, shout) but shares thetextkey with the parent. - The parent adds the compiled subgraph as a single node, so its whole flow runs in that one step.
- Because the state key is shared, the subgraph reads and writes the parent's
textwith no mapping needed.
Two ways to add a subgraph
| Situation | How |
|---|---|
| Subgraph shares the parent's state keys | Add the compiled graph directly: add_node("step", sub) |
| Subgraph has a different state shape | Wrap it in a function that maps state in and out |
Where subgraphs fit
- A reusable piece, such as a retrieval-and-answer block used by several agents.
- Breaking one large graph into named parts that are easier to read and test.
Related
- Previous: Send: run one node per item
- Next: Messages: how a model reads and writes
- Reference: Use subgraphs
- Add a third node to the subgraph that adds an exclamation mark, and rerun.
- Give the subgraph a different key name and switch to the wrapper form.
- Call
prepare.invoke({"text": " hi "})on its own. It runs as a normal graph.
Slow is fine. Stopping is the only problem.