Functional API
The Functional API is LangGraph's decorator-based alternative to StateGraph. It gives you the same checkpointing, streaming, and human-in-the-loop guarantees with plain Python functions — but only works for linear and branching workflows. This article covers when to use it, how it fails in production, and the one deployment gotcha that silently breaks task caching.
Quick Reference
- →@entrypoint(checkpointer=saver): marks a function as a LangGraph workflow — automatic checkpointing, streaming, human-in-the-loop
- →@task: marks a unit of work — independently checkpointed, returns a future, caches result on resume
- →Deployment gotcha: always pass checkpointer= at compile time — the API server's runtime-injected checkpointer is invisible to @task caching
- →entrypoint.final(value=X, save=Y): decouple what the caller receives from what gets saved to the checkpoint
- →Injectable params: previous (last return value), store (long-term memory), writer (custom streaming), config (RunnableConfig)
- →Determinism rule: wrap all randomness and side effects inside @task — not in the entrypoint body
- →Security: langgraph-checkpoint >= 4.0.0 required — earlier versions have RCE vulnerabilities in the cache/checkpoint serializer
Should I Use the Functional API?
Use Functional API for linear/branching workflows. Upgrade to StateGraph when you need cycles, Send(), or multi-agent.
The Functional API is the right choice when your workflow is a linear pipeline or a branching pipeline without cycles. If you need loops (an agent that retries until it gets a good answer), dynamic parallelism (Send() fan-out over a variable-length list), or multi-agent coordination (multiple specialized agents handing off to each other), you need StateGraph. For everything else — data pipelines, RAG chains, approval flows, simple chatbots — the Functional API is less boilerplate with the same guarantees.
| Feature | Functional API | StateGraph |
|---|---|---|
| Syntax | Decorated functions (@entrypoint, @task) | add_node() + add_edge() + compile() |
| State schema | Inferred from function params and returns | Explicit TypedDict or Pydantic BaseModel |
| Cycles / loops | Not supported — linear and branching only | First-class: edges can loop back to earlier nodes |
| Conditional routing | Python if/else inside the entrypoint | add_conditional_edges() with path maps |
| Dynamic fan-out | Not supported (no Send() equivalent) | Send() API maps work across variable-length inputs |
| Human-in-the-loop | interrupt() works inside @task functions | interrupt() + interrupt_before/interrupt_after |
| Subgraphs | Call other @entrypoint functions directly | Compiled graph added as a node |
| Best for | Linear workflows, branching, data pipelines | Agents with cycles, multi-agent systems, Send() |
Most workflows (data pipelines, RAG, simple chatbots, approval chains) don't need cycles. Start with the Functional API. Migrate to StateGraph only when you actually hit a limitation — cycles, Send(), or complex multi-agent routing. Premature migration adds 50–100 lines of boilerplate without benefit.