May 29, 2026
24 min

Top 35 LangGraph Interview Questions (2026)

35 LangGraph interview questions and answers for 2026, covering state graphs, checkpointing, human-in-the-loop, multi-agent graphs, and streaming.

By Roy Lee· Founder of Interview Coder. Banned from Columbia for building it.· Updated Jun 12, 2026

LangGraph interview questions now show up in almost every agentic-AI and AI-engineer loop where the team builds on the LangChain stack. If LangGraph is on the rubric, the interviewer is checking whether you can reason about typed state, checkpoints, and human-in-the-loop gates — not whether you can recite the docs. Agentic roles that list LangGraph are reported to pay in the low-to-mid six figures for senior engineers in 2026, and the bar moved up with the 1.0 release.

This guide is a 35-question bank. Each answer comes with the signal the interviewer is grading, plus runnable snippets for the hard concepts. The facts match the October 2025 1.0-era stack. For timed mocks against this exact rubric, Interview Coder runs the same drills with live feedback.

LangGraph in 2026: what changed

Both libraries hit 1.0 LTS in October 2025: LangChain 1.2.7 and LangGraph 1.0.7 at the time of writing. The big shift is architectural. LangChain's high-level agents now execute on the LangGraph runtime. AgentExecutor is deprecated and in maintenance until December 2026, and the migration path is create_react_agent for the prebuilt case or a hand-built StateGraph for everything else.

So in 2026, "LangChain agent" and "LangGraph" are not two competing choices. LangGraph is the runtime; LangChain is the batteries-included layer on top. An interviewer who asks "LangGraph vs LangChain" is testing whether you know that. Read the LangGraph docs and the LangChain docs before any onsite — the prebuilt create_react_agent and the low-level graph API both matter.

How LangGraph interviews are structured

Most loops split LangGraph into five buckets. Here's how the rounds map to what's being graded and which questions below cover each:

BucketWhat it gradesQuestions
Fundamentals & stateThe graph model, typed state, reducersQ1–Q8
Graph constructionNodes, edges, conditional routing, subgraphsQ9–Q14
Persistence & HITLCheckpointers, threads, interruptsQ15–Q25
Multi-agent & streamingSupervisor patterns, handoffs, stream modesQ26–Q31
ProductionCapacity, debugging, migration off AgentExecutorQ32–Q35

Every question below carries a Testing tag. That's the signal the interviewer is actually grading — the thing that separates "memorized the API" from "shipped this." Read the tag before you read the answer, then check whether your own answer would hit it.

Fundamentals & State Graphs (8 Questions)

The first 20 minutes. Short answers, clean tradeoffs, no hand-waving on state.

Q1. What is LangGraph and when do you reach for it over plain LangChain?

LangGraph is a low-level runtime for building stateful, multi-step LLM applications as a graph: nodes do work, edges decide what runs next, and a typed state object flows through. You reach for it over a plain LangChain chain when control flow stops being linear — when you need cycles (retry until a quality bar), branching on model output, persistence across steps, or a human approval gate. A chain is a straight line; LangGraph is a state machine.

Testing: Whether you pick LangGraph for a real reason (cycles, state, persistence) instead of because it's trendy.

Q2. Explain the state graph model: nodes, edges, typed state.

The state is a typed dict (usually a TypedDict or Pydantic model) that defines the shape of everything passing through the graph. Each node is a function that takes the current state and returns a partial update. Edges connect nodes and decide order. The graph runs a node, merges its returned update into state, then follows edges to the next node. State is the contract; nodes only ever see and update that shared object.

Testing: Whether you can describe the run loop precisely — node returns a partial update, runtime merges it — not just name the parts.

Q3. What is a reducer, and what happens when parallel branches write the same field without one?

A reducer is a function attached to a state field that says how to merge updates instead of overwriting. The classic is add_messages (or operator.add) on a message list, so each node appends rather than replaces.

Without a reducer, the default behavior is last-write-wins overwrite. If two parallel branches both write the same field in the same step, you get a conflict — LangGraph raises an InvalidUpdateError because it can't decide which write wins. With a reducer, both writes merge deterministically.

from typing import Annotated, TypedDict
import operator

class State(TypedDict):
    # parallel branches can both append; results concatenate
    results: Annotated[list, operator.add]
    # no reducer: two branches writing this in one step -> error
    summary: str

Testing: This is the trap question. They want to hear "without a reducer, concurrent writes to the same key error out," not a vague "it merges somehow."

Q4. StateGraph vs MessageGraph vs prebuilt create_react_agent.

StateGraph is the general low-level API — you define your own state schema and full control flow. MessageGraph is the older specialization where state is just a list of messages; it's largely superseded by StateGraph with a messages field plus add_messages. create_react_agent is the prebuilt: one call gives you a ReAct tool-calling agent already running on the LangGraph runtime. Reach for the prebuilt to ship fast, drop to StateGraph when you need custom state or non-ReAct control flow.

Testing: Whether you know the prebuilt exists and when its convenience stops being worth the loss of control.

Q5. How does state flow and merge through nodes?

A node receives the full current state and returns a dict containing only the keys it wants to change. The runtime takes that partial dict and merges it into the running state — overwrite by default, or via the reducer if the key has one. Keys the node didn't return are untouched. This is why you return {"count": state["count"] + 1} and not the whole object: you describe the delta, the runtime applies it.

Testing: Whether you understand partial updates and merge semantics, the source of most LangGraph bugs.

Q6. Conditional edges vs normal edges — show the routing logic.

A normal edge always goes from A to B. A conditional edge runs a function that reads state and returns the name of the next node, so the graph branches on data or model output.

def route(state: State) -> str:
    if state["score"] >= 0.8:
        return "finish"
    return "retry"

graph.add_conditional_edges("grade", route, {"finish": END, "retry": "generate"})

Testing: Whether you can write the router cleanly and know the return value maps to a node name.

Q7. START, END, entry points, and graph compilation.

START and END are sentinel nodes. An edge from START sets the entry point; an edge to END terminates that path. You build the graph by adding nodes and edges, then call .compile() to get a runnable. Compilation validates the graph (reachability, dangling edges) and is also where you attach a checkpointer. You invoke the compiled object, not the builder.

Testing: Whether you know .compile() is a real step and where the checkpointer gets wired in.

Q8. How do you keep state small and typed in production?

Put only what nodes actually need in state, and type every field. Don't stuff giant tool outputs or full documents into state — store them by reference (an ID or URL) and fetch on demand, because every checkpoint serializes the whole state. Use reducers deliberately so lists don't grow unbounded. A bloated, loosely-typed state is the thing that makes checkpoints expensive and bugs invisible.

Testing: Whether you've felt the cost of fat state — serialized on every checkpoint — in a real system.

Nodes, Edges & Conditional Routing (6 Questions)

Now they want you writing graph code, not describing it.

Q9. Write a node function — signature and return shape.

A node takes state and returns a partial update dict. That's the whole contract.

def generate(state: State) -> dict:
    response = llm.invoke(state["messages"])
    return {"messages": [response]}  # appended via add_messages reducer

It can also take a second config arg for runtime data (thread_id, user info). It must return a dict whose keys exist in the state schema — return nothing you don't intend to merge.

Testing: Whether you return a partial dict, not the mutated full state, and whether you know the optional config param.

Q10. Build a conditional router that branches on state.

from langgraph.graph import StateGraph, START, END

def needs_tool(state: State) -> str:
    last = state["messages"][-1]
    return "tools" if last.tool_calls else "respond"

g = StateGraph(State)
g.add_node("agent", agent_node)
g.add_node("tools", tool_node)
g.add_node("respond", respond_node)
g.add_edge(START, "agent")
g.add_conditional_edges("agent", needs_tool, {"tools": "tools", "respond": "respond"})
g.add_edge("tools", "agent")  # loop back
g.add_edge("respond", END)
app = g.compile()

Testing: Whether you wire the router, the branch map, and the loop-back edge correctly in one pass.

Q11. Loops and retries: the quality-score retry pattern.

Generate, grade, and loop back if the grade is below a bar — with a max-attempts guard so it can't spin forever.

def route(state: State) -> str:
    if state["score"] >= 0.8 or state["attempts"] >= 3:
        return END
    return "generate"

g.add_conditional_edges("grade", route, {"generate": "generate", END: END})

The attempt counter lives in state and increments in the node. Without the cap, a model that never clears the bar loops until it blows your token budget.

Testing: Whether you add the hard attempt cap. Anyone who's run a retry loop in prod adds it reflexively.

Q12. Cycles vs DAGs — when LangGraph beats a chain.

A plain LangChain chain is a DAG: it flows one direction and stops. LangGraph allows cycles — a node can route back to an earlier node. That's the whole reason it exists. Any pattern that needs "keep going until a condition holds" — ReAct loops, reflection, retry-until-valid — is a cycle. If your task is genuinely one-directional, a chain is simpler and you don't need LangGraph.

Testing: Whether you can name the dividing line: cycles are the value, linear flows don't need the runtime.

Q13. Tool nodes and the ToolNode / tools_condition pattern.

ToolNode is a prebuilt node that takes the tool calls from the last AI message, runs the matching tools, and appends the results as tool messages. tools_condition is the matching prebuilt router: it sends flow to the tool node if the last message has tool calls, otherwise to the end.

from langgraph.prebuilt import ToolNode, tools_condition

g.add_node("tools", ToolNode(tools))
g.add_conditional_edges("agent", tools_condition)
g.add_edge("tools", "agent")

Testing: Whether you know the prebuilt tool-calling loop instead of hand-rolling tool dispatch.

Q14. Subgraphs: when and how to nest a graph.

A subgraph is a compiled graph used as a node inside a parent graph. Reach for it when a chunk of logic is self-contained and reusable — a research subroutine, a per-agent loop in a multi-agent system. The key question is state: if the subgraph shares state keys with the parent, they merge directly; if its schema differs, you wrap it in a node that maps parent state in and subgraph results out. Subgraphs keep big graphs readable and let teams own pieces independently.

Testing: Whether you understand state mapping at the boundary, the part people get wrong.

Checkpointing & Persistence (6 Questions)

This is where LangGraph earns its keep, and where weak candidates get exposed.

Q15. What is a checkpointer and why does it need a thread_id?

A checkpointer saves the full graph state after every step. That snapshot is what lets you pause, resume, recover from a crash, and do human-in-the-loop. The thread_id is the key that scopes a conversation or run: all checkpoints for one user session share a thread_id, so when you resume you pass that id and the runtime loads the right history. No thread_id, no way to know which run's state to restore.

from langgraph.checkpoint.memory import InMemorySaver
app = g.compile(checkpointer=InMemorySaver())
app.invoke(inp, config={"configurable": {"thread_id": "user-42"}})

Testing: Whether you connect the checkpointer to the thread_id as the scoping key — not just "it saves state."

Q16. In-memory vs durable checkpointers (Postgres / SQLite) — tradeoffs.

InMemorySaver keeps checkpoints in process memory: zero setup, gone on restart, fine for tests and notebooks. A durable saver (PostgresSaver, SqliteSaver) persists to a database so state survives process restarts, scales across workers, and supports real recovery. In production you use a durable one — in-memory means a crash loses every in-flight run. Postgres also lets multiple app instances share thread state.

Testing: Whether you reach for durable in prod and know in-memory is a dev-only convenience.

Q17. Checkpointer (within-thread) vs Store (cross-thread long-term memory).

These are two different memory systems. The checkpointer is short-term, within-thread memory: it holds the state of one conversation, keyed by thread_id. The Store is long-term, cross-thread memory: a key-value store, namespaced (often by user), that persists facts across separate threads — so something learned in Monday's session is available in Friday's. Checkpointer answers "where were we in this run"; Store answers "what do we know about this user across all runs."

Testing: Whether you keep the two straight. Mixing them up — trying to use thread state for long-term user memory — is a common, telling mistake.

Q18. Resume after a crash: load the checkpoint, re-enter at the next node.

With a durable checkpointer, resume is just invoking again with the same thread_id and no new input. The runtime loads the latest checkpoint and continues from the node after the last successful one — it does not replay completed nodes.

# crash happened mid-run; relaunch with the same thread_id
app.invoke(None, config={"configurable": {"thread_id": "user-42"}})

Passing None as input signals "continue from saved state" rather than "start fresh."

Testing: Whether you know resume re-enters at the next pending node and doesn't re-run finished work.

Q19. Partial side effects on resume — idempotency keys.

Resume re-enters at the node after the last checkpoint. But if a node did real work — charged a card, sent an email, wrote a row — and then the process died before checkpointing, a resume can re-run that node and repeat the side effect. The fix is idempotency keys: derive a stable key (thread_id + step + action) and have the downstream system dedupe on it, or check "did I already do this" before acting. Checkpoints make state recoverable; they don't make external side effects exactly-once on their own.

Testing: Whether you've thought about the gap between "state recovered" and "side effect not repeated." Senior-level signal.

Q20. Cost of checkpointing and checkpoint-by-reference for big tool results.

Every checkpoint serializes the whole state, so fat state means slow writes and a big database. If a node produces a large blob — a scraped page, a big tool result, a document — don't put the blob in state. Write it to object storage or a cache, put the reference (an ID or URL) in state, and have downstream nodes fetch by reference. You checkpoint a short pointer instead of megabytes on every step.

Testing: Whether you'd keep checkpoints cheap under real payloads instead of serializing huge tool outputs repeatedly.

Human-in-the-Loop (5 Questions)

HITL is the round that maps directly to "would I let your agent touch prod."

Q21. When do you interrupt for human approval (the blast-radius rule)?

Gate any action whose blast radius is real: spending money, sending external communication, writing to a system of record, deleting data, anything irreversible or expensive. Reads and reversible internal steps run unattended. Draw the line by consequence, not by step type — if a wrong action costs money, leaks data, or pages someone, put a human in front of it.

Testing: Whether you gate by blast radius instead of gating everything (slow) or nothing (reckless).

Q22. interrupt() + Command resume — show the pattern.

interrupt() pauses the graph inside a node and surfaces a payload to the caller. The human responds, and you resume with Command(resume=...), which makes interrupt() return that value and continue.

from langgraph.types import interrupt, Command

def approval(state: State):
    decision = interrupt({"action": state["proposed_action"]})
    return {"approved": decision == "yes"}

# first call pauses at the interrupt
app.invoke(inp, config=cfg)
# human reviewed; resume with their answer
app.invoke(Command(resume="yes"), config=cfg)

Testing: Whether you know the modern interrupt() / Command(resume=...) pair, not a deprecated static-breakpoint hack.

Q23. Let a human edit the plan or state mid-run, then resume.

Two paths. The interrupt() payload can carry the current plan; the human returns an edited version, and the node writes it back to state before continuing. Or you use update_state to patch the checkpoint directly between invocations, then resume — the graph picks up the edited state. Either way, the edit lands in the checkpoint, so the resumed run reasons over the human-corrected plan.

Testing: Whether you can both surface state for editing and write the edit back so resume honors it.

Q24. Approve / edit / reject patterns at an interrupt point.

One interrupt, three outcomes based on the human's response. Approve: proceed as proposed. Edit: the human returns a modified action, which you write to state and execute. Reject: route to an alternative node — regenerate, escalate, or end. The node reads the resume value and a conditional edge branches on it. This trio covers essentially every review workflow.

Testing: Whether you handle all three branches, especially reject, instead of a binary yes/no.

Q25. Why interrupt() needs a checkpointer, and common gotchas.

interrupt() works by saving state, returning control, and later restoring — that only works if there's a checkpointer to save to. No checkpointer, no pause point to resume from. Common gotchas: forgetting to pass the same thread_id on resume (it starts fresh); putting non-idempotent side effects before the interrupt in the same node, so they re-run when the node restarts on resume; and assuming the node resumes mid-line — it re-executes from the top, with interrupt() returning the resume value.

Testing: Whether you know the node re-runs from the top on resume — the single most common interrupt bug.

Multi-Agent Graphs & Streaming (6 Questions)

Coordination and output. Where over-engineering shows up first.

Q26. Supervisor vs swarm vs hierarchical multi-agent in LangGraph.

Supervisor: one router agent owns control and delegates to worker agents, collecting results — centralized, easy to reason about. Swarm: agents hand off to each other peer-to-peer with no central boss — flexible, harder to debug. Hierarchical: supervisors of supervisors, for large systems that decompose into teams. In LangGraph each agent is a node or subgraph, and handoffs are edges or Command(goto=...) jumps. Default to supervisor; reach for swarm or hierarchy only when one router becomes the bottleneck.

Testing: Whether you default to the simplest topology that works instead of building a swarm for a two-tool problem.

Q27. Shared-state vs message-passing handoffs between agents.

Shared-state: all agents read and write one graph state; a handoff just routes to the next agent, which sees everything. Message-passing: each agent gets a scoped message and returns a result, with less shared surface. Shared state is simpler and the LangGraph-native default, but couples agents and risks key collisions (use reducers). Message-passing isolates agents at the cost of plumbing. Most LangGraph multi-agent systems use shared state with careful field ownership.

Testing: Whether you understand the coupling tradeoff and would use reducers to avoid handoff write conflicts.

Q28. When is one agent plus a tool list better than multi-agent?

Almost always, until it isn't. One agent with a good tool list is simpler to build, cheaper to run, and far easier to debug. Go multi-agent only when you have a real reason: context windows blowing up from too many tools, genuinely separate domains needing different prompts or models, or independent subtasks you want to run in parallel. Multi-agent multiplies cost and failure modes; reach for it when a single agent measurably can't cope, not by default.

Testing: Whether you resist multi-agent hype. The strongest answer starts with "usually one agent is enough."

Q29. Streaming modes: values / updates / messages / custom.

stream_mode controls what the graph emits. values: the full state after each step. updates: only the delta each node returned — lighter, good for tracing which node did what. messages: LLM tokens as they generate, for chat UIs. custom: arbitrary data you emit from inside a node (progress, tool status). You can combine modes. Pick messages for token streaming to a user, updates for observability.

for chunk in app.stream(inp, config=cfg, stream_mode="updates"):
    print(chunk)

Testing: Whether you can match each mode to a use case rather than just listing names.

Q30. Stream tokens to a UI without leaking full state.

Use stream_mode="messages" to stream only the model tokens, not values (which would push the entire state object — internal scratchpad, tool results, secrets — to the client every step). For richer UI signals, add custom events you explicitly emit, so you control exactly what the frontend sees. The rule: stream what the user should see, never the whole state.

Testing: Whether you'd avoid shipping internal state to the browser — a security and UX instinct.

Q31. Map-reduce / fan-out with Send and parallel nodes.

Send lets one node dispatch dynamic parallel work: you return a list of Send("worker", payload) objects and the runtime fans out a worker invocation per payload, in parallel. Each worker writes to a state field with a reducer (so concurrent writes append instead of colliding), and a downstream node reduces the collected results. That's map-reduce: fan out with Send, gather with a reducer.

from langgraph.types import Send

def fan_out(state):
    return [Send("worker", {"item": x}) for x in state["items"]]

Testing: Whether you pair Send with a reducer on the result field — without it, parallel workers collide (see Q3).

Production & LangGraph vs LangChain (4 Questions)

The design and judgment round.

Q32. Design a stateful research agent for 10k requests/day on LangGraph.

10k/day is about 7 requests/minute average, with bursts. That's modest — the constraints are cost and state, not raw throughput. Architecture: a create_react_agent or custom StateGraph with search and synthesis tools; a PostgresSaver checkpointer for durability and cross-instance state; large tool results stored by reference, not in state (Q20); a step cap and token budget per request to bound runaway loops; and an interrupt gate only on actions with real blast radius. Run app instances behind a queue so bursts don't exhaust API rate limits. The math is easy; the discipline is keeping state lean and bounding cost per request.

Testing: Whether you size the actual load, pick durable persistence, and bound cost — instead of over-architecting for imaginary scale.

Q33. LangGraph vs LangChain: explicit typed state vs implicit, when each wins.

LangChain chains carry state implicitly — it threads through the chain and you mostly don't manage it. LangGraph makes state explicit and typed: you declare the schema and every node operates on it. Implicit wins for simple linear pipelines where the ceremony isn't worth it. Explicit wins the moment you need branching, loops, persistence, or HITL — you want one typed object you can inspect, checkpoint, and resume. And remember the 2026 reality: LangChain's agents now run on the LangGraph runtime, so it's a layering choice, not a rivalry.

Testing: Whether you frame it as layers (LangGraph under, LangChain over) rather than competitors, and know when explicit state pays off.

Q34. Migrate an AgentExecutor app to StateGraph / create_react_agent.

AgentExecutor is deprecated and in maintenance until December 2026, so new work shouldn't use it. The fast migration: replace the AgentExecutor with create_react_agent(model, tools), which gives an equivalent ReAct loop on the LangGraph runtime, plus checkpointing and streaming for free. If you had custom logic the executor couldn't express — branching, retries, approval gates — that's the cue to drop to a hand-built StateGraph. Keep your tools and prompts; swap the execution layer underneath.

Testing: Whether you know AgentExecutor is deprecated and can name the two migration targets and when each applies.

Q35. Debug an agent that passes in dev but loops or fails in prod.

Start from the checkpoints — they're your trace. Pull the thread's state history and find where it diverged. Common prod-only causes: an in-memory checkpointer that loses state across workers, so resumes start fresh; missing or wrong reducers causing parallel-write errors under real concurrency that never fired in single-threaded dev; unbounded loops because the step cap was a dev placeholder; and fat state hitting serialization or DB limits at production payload sizes. The discipline is replaying from the checkpoint, not re-running blind.

Testing: Whether you debug from checkpoint state and know which failure modes are concurrency- and scale-specific.

How to prepare for a LangGraph interview

Reading the docs gets you to question one. It doesn't get you through the design round. Do this instead:

Build one real agent. A stateful research or support agent with a durable checkpointer, one interrupt gate, and cross-thread memory via the Store. Building it once teaches you reducers, thread scoping, and resume semantics faster than any reading — you'll hit the parallel-write error from Q3 yourself, and that's the point.

In practice, here's the moment that question is built around. The first time I fanned out three parallel research nodes that all wrote to a plain summary: str field, the run died with InvalidUpdateError on the very first concurrent step — not a silent overwrite, a hard crash. The fix was one line: change the field to summary: Annotated[list, operator.add] so the writes append instead of fighting over the key. That single error taught me reducers more permanently than the docs did, and it's exactly the scenario an interviewer is probing with Q3 and Q31. Trigger it once on purpose before your onsite and the answer stops being theoretical.

Drill the rounds out loud. Explain a reducer, defend a supervisor-vs-swarm choice, and write a conditional router live, talking through it. The questions above map to real loop buckets — work through each one out loud until the answer stops sounding rehearsed.

For timed reps in a real-loop environment, Interview Coder runs mock agentic-AI sessions with live feedback and full session logs. Two mocks a week for three weeks and the LangGraph answers stop feeling like recitation.

FAQ

Is LangGraph hard to learn? The API is small. The hard part is the mental model — typed state, reducers, checkpoints, resume semantics. Most people grasp the syntax in a day and spend a week building intuition for state merging and persistence. Build one agent end to end and it clicks.

Do I need to learn LangChain first? No, but it helps. You can use LangGraph's low-level API standalone. In practice you'll touch LangChain pieces (models, tools, message types) since LangGraph builds on them, and the prebuilt create_react_agent lives at that boundary. Know both — they're layers, not alternatives.

Is LangGraph used in production? Yes. It hit 1.0 LTS in October 2025 and is the runtime under LangChain's own agents. Teams use it for stateful agents, HITL workflows, and multi-agent systems where durable checkpointing matters. Production use is exactly what the 1.0 release was about.

LangGraph vs CrewAI or AutoGen in one line? LangGraph gives you explicit, low-level control over state and graph flow; CrewAI and AutoGen give you higher-level, opinionated multi-agent abstractions that are faster to start but harder to bend. Pick LangGraph when you need control over state and persistence.

Related Reading

Land the role

The 35 questions above are the floor. The candidates who get offers in 2026 have built a stateful agent with their own hands, can defend a topology choice under pushback, and treat checkpoints and HITL as first-class design, not afterthoughts.

If you want timed reps in the same environment as a real loop, Interview Coder runs mock agentic-AI sessions with live feedback and full session logs, so you can see exactly where you improved. Its coding answers run on Claude Sonnet 4.6, Anthropic's latest Sonnet. It's a desktop app with 20+ stealth features, used by 100K+ engineers. Free is $0, Monthly Pro is $299, and Lifetime Pro is $799 one-time. Full disclosure: this guide is published by Interview Coder, its own product. The grind is still the grind — but you can stop guessing whether your prep is working.

Related Blogs

Explore Our Similar Blogs

View All blogs
Take the Next Step

Ready to Pass Any SWE Interviews with 100% Undetectable AI?

Step into your next interview with AI support designed to stay completely undetectable.