Skip to main content
Not every branch or component in a LangGraph needs the same state shape. As graphs grow, reusing components or composing subgraphs with different data requirements becomes essential. Handling multiple state schemas lets each part of the graph declare the fields it requires without forcing a one-size-fits-all global state.
For example, a summarization branch might track paragraphs and topic scores, while a QA branch stores citations and tool logs. Forcing all branches to conform to a single schema makes components heavier and harder to reuse. Instead, define the schema that each subgraph or node needs and keep global state optional where components intersect.
Why use multiple schemas? They let you:
  • Document assumptions per module.
  • Catch schema mismatches early via typing and tests.
  • Reason locally about each node’s inputs and outputs.
  • Reuse subgraphs across different parent graphs with confidence.
Analogy: imagine Ravi carries different forms for deliveries — one for medical items, another for food, another for paperwork — each form contains fields tailored to the task. The same applies to graph state: each subgraph should own the fields it requires.
When your system handles multiple flows (e.g., summarization and translation), you may share some common fields (like input and response) while each branch tracks its own specialized fields. Keeping separate TypedDicts for these subgraphs clarifies those differences and makes it safe to reuse branches across other graphs. It also helps agents and routers dynamically adapt to context.
Practical patterns
  • Local schemas: give each subgraph or node its own TypedDict that documents required and optional fields.
  • Composed parent state: the parent graph can accept a broader GraphState with many total=False fields so subgraphs can coexist.
  • Adapter (transition) nodes: map one schema to another when composing subgraphs.
  • Type guards: verify runtime state before assuming fields are present.
Example typed declarations (useful for IDEs and static checks):
Subgraphs: isolate functionality and preserve contracts A subgraph is a small, self-contained graph with its own state schema. Use subgraphs to encapsulate search, summarization, validation, or any reusable logic.
This example shows a SearchState that requires query but treats search_results as optional. Compile the subgraph and reuse it inside multiple parent graphs. Subgraphs make it easy to test and swap implementations without touching the outer workflow. Adapters: translate state between schemas When composing subgraphs with different schemas, use small adapter nodes to map one shape to another. Adapters perform renames, conversions, enrichments, or cleanup.
Register adapters like any other node (e.g., graph.add_node("adapter", adapter_fn)) and connect them between subgraphs. Adapters are particularly valuable when integrating components from different teams or when evolving schemas over time. Runtime vs static checks: use type guards TypedDicts are development-time tools; at runtime the state is a plain dictionary. Protect nodes that expect specific fields with explicit type guards:
Use guards inside routers or nodes to branch safely and avoid KeyError exceptions. You can create stricter guards that check types with isinstance or raise informative errors when a state is malformed.
Type hints and TypedDicts improve clarity and tooling support. Always validate runtime assumptions with explicit guards or adapter nodes when composing independent subgraphs.
Benefits of multiple schemas
  • Local clarity: each node/subgraph documents exactly what it needs.
  • Reusability: isolate functionality, then reuse across projects.
  • Easier debugging: smaller, focused schemas reduce search space for bugs.
  • Safe evolution: change a subgraph’s schema without breaking unrelated branches.
Quick reference table Further reading and references Summary Treat schemas as local contracts for subgraphs and nodes. Use TypedDict and type hints to improve developer experience, introduce adapters to translate state between modules, and protect runtime behavior with explicit type guards. This approach keeps LangGraph workflows modular, easier to reason about, and safer to evolve over time.

Watch Video