Memory in LangGraph is not one thing. There is the live state flowing through a single run, the persisted checkpoints that let that run pause and resume, and any longer-lived memory you store across many runs or threads.
Understanding those layers is essential because they solve different problems. Short-term state helps the graph keep context. Checkpoints help the runtime survive time, failure, and human pauses. Long-term memory helps the application remember beyond one thread.
This page will help you separate those concerns so your graphs remain durable without becoming bloated.
A graph state exists while a run is active. A checkpoint is a saved snapshot of that state at execution boundaries so the runtime can resume later. Official docs describe these snapshots as being organized under threads, which is why `thread_id` matters so much.
If you do not persist checkpoints, you can still run graphs. You just lose durable pause-resume behavior, time travel, and many human-in-the-loop patterns.
Persistence turns graphs from request-bound pipelines into long-lived systems. A user can leave and return. A human reviewer can approve later. A worker can restart without forgetting the workflow position.
It also unlocks debugging capabilities such as inspecting state history or replaying from earlier checkpoints.
In-memory persistence is excellent for local development and tests because it removes infrastructure overhead. It is not durable across process restarts. Production runs should use a persistent backend so checkpoints survive service restarts and distributed execution.
The practical rule is simple: if losing the process means losing business context, you need durable checkpoint storage.
Checkpoint persistence is thread-scoped execution memory. Long-term memory is application memory that can outlive a thread and be reused later, such as customer preferences, prior decisions, semantic summaries, or retrieved knowledge.
Store those durable facts outside the graph state, then read them into state when a new run needs them. That keeps the graph state focused while still letting the application remember.
Persisted threads create a schema evolution problem. If you change the state shape after deployment, older checkpoints may resume into newer graph code. That means your graph changes must be treated like a compatibility-sensitive API change.
Production teams should version important state changes, add migration logic when needed, and test resume behavior rather than assuming new code will fit old checkpoints cleanly.
A LangGraph checkpoint is more than saved memory. It is a durable snapshot of execution state that makes resume, replay, time travel, and debugging possible. Treat checkpoints as part of the application data model whenever users depend on long-running or interruptible workflows.
Thread identifiers should be stable, tenant-safe, and meaningful enough for operations. If the same user has multiple workflows, each should have a distinct thread or namespace strategy. Accidentally reusing thread IDs can mix state between tasks, which is both confusing and dangerous.
Checkpoint data needs retention rules. Some workflows need short-lived state; others need audit history. Sensitive state should be minimized, encrypted where appropriate, and deleted according to policy. Durable execution does not mean keeping everything forever.
Use time travel and replay for debugging, but understand side effects. Replaying a node that sends email or updates a ticket can be unsafe unless side effects are isolated, idempotent, or mocked during replay.
Checkpoint persistence and user memory solve different problems. Checkpoints let a run continue. Memory helps future runs use relevant facts or preferences. Do not confuse a checkpoint with a long-term memory store. A checkpoint may contain temporary observations that should not influence unrelated future tasks.
When you add long-term memory to LangGraph, make memory retrieval an explicit node or task. That node should apply relevance, permission, recency, and confidence filters. It should also record which memories entered state so later debugging can explain the answer.
Memory updates should happen at controlled points, often after a successful run or explicit user confirmation. Storing memory mid-run can preserve false assumptions from incomplete work.
Schema evolution affects both checkpoints and memory. If state or memory structures change, write migration logic or compatibility adapters. Otherwise old runs and old memories will break under new code.
Create a run, pause it, inspect the checkpoint, resume it, then replay from an earlier state. This hands-on exercise makes durable execution concrete. It also reveals whether state fields are understandable outside the code path that produced them.
Next, simulate a process crash after a node completes and before the user sees the result. The graph should resume or report failure without losing important state. If it repeats a side effect, the node boundary needs redesign.
Finally, review retention. Checkpoints may contain sensitive user input, retrieved context, and tool outputs. Decide what must be retained, what can expire, and what should be redacted.
This is the smallest persistence example worth learning: compile with a checkpointer and invoke using a `thread_id`.
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver
class CounterState(TypedDict):
count: int
def increment(state: CounterState) -> dict:
return {"count": state["count"] + 1}
builder = StateGraph(CounterState)
builder.add_node("increment", increment)
builder.add_edge(START, "increment")
builder.add_edge("increment", END)
graph = builder.compile(checkpointer=InMemorySaver())
config = {"configurable": {"thread_id": "counter-1"}}
print(graph.invoke({"count": 0}, config=config))
Interrupts rely on persisted checkpoints so the graph can pause and later continue from the same thread.
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import interrupt, Command
class ApprovalState(TypedDict):
draft: str
approved: bool
def human_gate(state: ApprovalState) -> dict:
approved = interrupt("Approve this draft?")
return {"approved": bool(approved)}
Keep persistent application facts outside the graph state and inject only what the current run needs.
class SupportState(TypedDict):
customer_id: str
preferences: dict
latest_request: str
reply: str
def load_customer_profile(state: SupportState) -> dict:
# Pretend this reads from a durable store or database.
profile = {"language": "en", "refund_tier": "gold"}
return {"preferences": profile}
No. Add it when you need persistence, replay, interrupts, or thread continuity.
No. Checkpointing persists execution state; long-term memory stores reusable facts beyond one run.
Treating persisted state changes as harmless code refactors when they can break resumed threads.
Explore 500+ free tutorials across 20+ languages and frameworks.