Recall Module 1’s honest, unresolved list of questions — how does a workflow persist progress so a crash at step 4 of 7 resumes at step 4, not step 1? This module gives the real, complete answer. Everything since Module 1 has been building the graphs; this is where those graphs stop losing their memory the moment your program stops running.
What a checkpointer actually does
A checkpointer saves a real, complete snapshot of a graph’s state after every single node finishes running. Not just at the end — after every step, individually.
Watching the problem without one
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
class State(TypedDict):
step: str
def step_one(state: State) -> dict:
print("Step one ran.")
return {"step": "one"}
def step_two(state: State) -> dict:
print("Step two ran.")
raise RuntimeError("Simulated crash!")
builder = StateGraph(State)
builder.add_node("step_one", step_one)
builder.add_node("step_two", step_two)
builder.add_edge(START, "step_one")
builder.add_edge("step_one", "step_two")
builder.add_edge("step_two", END)
graph = builder.compile()
try:
graph.invoke({"step": ""})
except RuntimeError as e:
print(f"Crashed: {e}")
Run this, and the crash genuinely destroys everything — step_one’s real, completed work is simply gone. Run the graph again, and it starts from START, with no memory that step_one ever ran at all.
Example 1: adding a real checkpointer
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver
class State(TypedDict):
step: str
def step_one(state: State) -> dict:
return {"step": "one"}
def step_two(state: State) -> dict:
return {"step": "two"}
builder = StateGraph(State)
builder.add_node("step_one", step_one)
builder.add_node("step_two", step_two)
builder.add_edge(START, "step_one")
builder.add_edge("step_one", "step_two")
builder.add_edge("step_two", END)
checkpointer = InMemorySaver()
graph = builder.compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": "run-1"}}
result = graph.invoke({"step": ""}, config=config)
print(result)
compile(checkpointer=checkpointer) is the entire real change. Every node’s completion now writes a genuine, real snapshot of state, tagged with thread_id, into the checkpointer — even though, from this single successful run, you can’t yet see the real difference it made.
Example 2: inspecting what actually got saved
state_snapshot = graph.get_state(config)
print("Current values:", state_snapshot.values)
print("Next node to run:", state_snapshot.next)
get_state(config) genuinely reads the checkpointer directly — this isn’t re-running the graph, it’s inspecting real, persisted data about exactly where this specific thread’s execution actually stands.
Example 3: simulating and surviving a real crash
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver
class State(TypedDict):
step: str
should_crash: bool
def step_one(state: State) -> dict:
return {"step": "one"}
def step_two(state: State) -> dict:
if state["should_crash"]:
raise RuntimeError("Simulated crash!")
return {"step": "two"}
def step_three(state: State) -> dict:
return {"step": "three"}
builder = StateGraph(State)
builder.add_node("step_one", step_one)
builder.add_node("step_two", step_two)
builder.add_node("step_three", step_three)
builder.add_edge(START, "step_one")
builder.add_edge("step_one", "step_two")
builder.add_edge("step_two", "step_three")
builder.add_edge("step_three", END)
checkpointer = InMemorySaver()
graph = builder.compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": "recoverable-run"}}
try:
graph.invoke({"step": "", "should_crash": True}, config=config)
except RuntimeError:
print("Crashed after step_one.")
print("Saved state after crash:", graph.get_state(config).values)
# genuinely resume — same thread_id, corrected input, from where it actually left off
result = graph.invoke({"should_crash": False}, config=config)
print("Final result after recovery:", result)
Run this, and watch closely: after the simulated crash, get_state shows step_one’s real, completed work still intact. The recovery call doesn’t restart from START — it genuinely resumes from step_two, because the checkpointer already knows step_one finished. This is the real, honest answer to Module 1’s original question.
flowchart LR
A[step_one completes] --> B[Checkpoint saved]
B --> C[step_two crashes]
C --> D["Process restarts —\ncheckpoint still exists"]
D --> E["Resume: step_one is NOT\nre-run, step_two runs again"]
Why this connects directly back to Klarna’s real result
Recall Module 1’s real, documented grounding — Klarna’s own workflow needed persistent state across conversation turns, and building it as a graph produced a real, measured 80 percent reduction in resolution time. Checkpointing is the literal, concrete mechanism behind that claim: a genuinely multi-turn resolution process, surviving across real time, real interruptions, and real system restarts, exactly because its state was never only ever living in one process’s temporary memory.
Common mistakes worth avoiding
Forgetting checkpointer= at compile time and expecting persistence anyway. Recall this module’s very first, uncheckpointed example — without it, every .invoke() genuinely starts fresh, no matter how carefully you designed your graph’s nodes.
Using InMemorySaver and expecting it to survive a real restart. Recall this exact caveat from Module 12 — InMemorySaver genuinely only exists in your running process’s memory. A real production deployment needs a persistent, database-backed checkpointer, covered properly once this course reaches production architecture.
Assuming checkpointing alone makes a graph safe to resume with different input. Recall Example 3 — the recovery call still needed genuinely correct, updated input (should_crash: False). Checkpointing preserves progress; it doesn’t automatically fix whatever caused the original failure.
What you should take away from this module
- A checkpointer saves a real, complete state snapshot after every single node — not just at the end.
compile(checkpointer=...)plus athread_idinconfigis the entire mechanism — genuinely small, for what it enables.get_state(config)reads real, persisted data directly, without re-running anything.- A crashed, checkpointed graph resumes from where it actually stopped, not from
START— this is the real, concrete answer to the question Module 1 opened this entire course with.
Where this goes next
The next module covers Threads properly — the real thread_id mechanism you just used, and exactly how it keeps many separate, real conversations genuinely isolated from each other.
- Author
- TechByteByByte Editorial Team
- Reviewed by
- TechByteByByte Admin
- Published
- Last reviewed