Here are the Top 100 LangGraph Interview Question and Answers:
Core Concepts & Architecture:
1. What is LangGraph and how does it differ from LangChain’s LCEL?
LangGraph is a library for building stateful, multi-actor applications with LLMs using a graph based execution model. LCEL (LangChain Expression Language) is a declarative chain composition system — linear or branching but stateless between steps. LangGraph adds: persistent state across nodes, cycles/loops, human-in-the-loop, and fine-grained control over agent execution flow. LCEL is for pipelines; LangGraph is for agents and workflows that need memory and iteration.
2. What is a StateGraph and how is it different from a MessageGraph?
- StateGraph: The primary graph type. You define a custom TypedDict state schema. Every node reads from and writes to this shared state object. Full control over what data flows through the graph.
- MessageGraph: A convenience subclass where the state is simply a list of messages (list[BaseMessage]). Simpler for pure chat agents but less flexible for complex workflows needing custom state fields.
3. Explain the concept of “state” in LangGraph. How is it managed?
State is a typed dictionary(using Python TypedDict) that is passed between all nodes in the graph. Each node receives the current state, performs work and returns a partial update. LangGraph merges the updates into the state using reducers. State persists across the entire graph execution and, with checkpointing, across multiple invocations (turns in a conversation).
4. What are reducers in LangGraph and why are they important?
Reducers define how state fields are updated when a node returns a value. By default, a returned value overwrites the existing field. Custom reducers allow different merge behaviors:
- operator.add on a list field appends instead of replacing
- Custom functions can implement deduplication, max, merge logic
from typing import Annotated
from operator import add
class State(TypedDict):
messages: Annotated[list, add] # appends instead of overwrites count: int # overwrites
5. What is the role of START and END nodes in LangGraph?
START is a virtual entry point — edges from START define which node(s) execute first when the graph is invoked. END is a virtual terminal node — when execution reaches END, the graph stops and returns the final state. They are imported from langgraph.graph and used to define the graph’s entry and exit points without creating actual node functions.
6. How do you add nodes and edges to a StateGraph?
from langgraph.graph import StateGraph, START, END
graph=StateGraph(State)
graph.add_node(“node_a”, function_a)
graph.add_node(“node_b”, function_b)
graph.add_edge(START, “node_a”)
graph.add_edge(“node_a”, “node_b”)
graph.add_edge(“node_b”, END)
compiles =graph.compile()
Nodes are Python callables. Edges define execution order. Conditional edges use a routing function.
7. What is graph compilation and what does it do? graph.compile() validates the graph structure (checks for unreachable nodes, missing edges), sets up the runtime execution engine, and optionally attaches a checkpointer and interrupt configuration. The compiled graph is what you actually invoke. Compilation catches structural errors early before runtime.
8. What is the difference between invoke, stream and astream on a compiled graph?
- invoke(input): Synchronous, runs the full graph, returns final state.
- stream(input): Synchronous generator, yields state updates after each node execution. Good for observability.
- astream(input): Async generator version of stream. Use in async frameworks (FastAPI, etc.).
- astream_events: Streams granular events including LLM token-level streaming.
9. How does LangGraph handle cycles, and why are they useful? LangGraph explicitly supports cycles via edges that point back to earlier nodes. This enables agent loops — the agent acts, observes tool results, decides to act again, and repeats until a termination condition is met. Without cycles, you’d need to pre-define the number of steps. Cycles make true agentic behavior possible.
10. What is a conditional edge and how do you implement one? A conditional edge routes execution to different nodes based on the current state. You provide a routing function that returns a node name (or list of node names for parallel execution).
def route(state: State) -> str:
if state["next_action"] == "tool":
return "tool_node"
return END
graph.add_conditional_edges("agent", route)
Checkpointer & Persistance:
11. What is a checkpointer in LangGraph and why is it critical?
A checkpointer persists the graph state after every node execution to a storage backend. This enables: multi-turn conversations (state survives between calls), human-in-the-loop (pause and resume), fault tolerance (resume from last checkpoint on failure), and time-travel debugging (replay from any past state). Without a checkpointer, state is lost after each invoke call.
12. What checkpointer backends does LangGraph support?
- MemorySaver: In-memory, for development/testing only. Lost on restart.
- SqliteSaver: SQLite-backed, good for local persistence.
- PostgresSaver/AsyncPostgresSaver: Production-grade, from langgraph-checkpoint-postgres
- RedisSaver: Redis-backed for high-throughput scenarios
- Custom: Implement the BaseCheckpointSaver interface for any backend
13. What is a thread_id and how does it relate to checkpointing?
thread_id is a unique identifier for a conversation or execution session. When you invoke a graph with config={“configurable”: {“thread_id”: “user-123”}}, the checkpointer saves/loads state scoped to that thread. Different thread IDs=independent conversation histories. Same thread ID=continued conversation with full state history.
14. Explain the concept of checkpoints, threads, and runs in LangGraph.
- Thread: A persistent conversation session identified by thread_id. Contains the full history of states.
- Checkpoint: A snapshot of the graph state at a specific point in execution. Multiple checkpoints exist per thread (one per node execution).
- Run: A single invocation of the graph within a thread. A thread can have many runs, each adding new checkpoints.
15. How does time-travel work in LangGraph? Because every node execution is checkpointed, you can replay the graph from any past checkpoint. Use graph.get_state_history(config) to list all checkpoints for a thread, then invoke with a specific checkpoint_id to branch from that point. Useful for debugging, A/B testing different agent decisions, and correcting mistakes.
history = list(graph.get_state_history(config))
past_config = history[2].config # 3rd checkpoint
graph.invoke(None, past_config) # resume from there
16. How do you update state manually between graph invocations?
Use graph.update_state(config, values) to inject state changes outside of node execution. This is useful for human-in-the-loop corrections — a human reviews the state, modifies it, then resumes execution.
graph.update_state(config, {"messages": [HumanMessage("Actually, use Python")]})
graph.invoke(None, config) # continues with updated state
17. What is checkpoint_ns and when does it matter?
checkpoint_ns (namespace) is used to scope checkpoints within subgraphs. When a parent graph calls a subgraph, the subgraph’s checkpoints are namespaced separately to avoid collisions. It matters when debugging subgraph execution or implementing fine-grained state inspection in nested graph architectures.
18. How do you implement cross-thread memory (shared state across conversations)?
LangGraph’s built-in checkpointing is per-thread. For cross-thread memory (e.g., user preferences shared across sessions), use the Store interface (InMemoryStore, AsyncPostgresStore). The store is a key-value system accessible from any node via the RunnableConfig or by injecting it as a node parameter.
from langgraph.store.memory import InMemoryStore
store = InMemoryStore()
graph.compile(checkpointer=checkpointer, store=store)
Human in the loop:
19. How do you implement human-in-the-loop in LangGraph?
Use interrupt_before or interrupt_after in graph.compile() to pause execution at specific nodes. The graph raises an Interrupt exception, saves state and waits. A human reviews/modifies state, then resumes with graph.invoke (None, config).
graph.compile(
checkpointer=checkpointer,
interrupt_before=["tool_node"] # pause before tool execution
)
20. What is the interrupt() function and how does it differ from compile-time interrupts?
interrupt() (introduced in newer LangGraph versions) is called inside a node function to pause execution dynamically based on runtime conditions. Unlike compile-time interrupt_before/after which always pauses at a node, interrupt() lets you conditionally pause based on state values — more flexible for dynamic approval workflows.
def human_review_node(state):
decision = interrupt({"question": "Approve this action?", "data": state})
return {"approved": decision}
21. How do you resume a graph after a human-in-the-loop interrupt?
After an interrupt, the graph state is saved in the checkpointer. To resume:
- Optionally update state with human feedback: graph.update_state(config, new_values)
- Resume execution: graph.invoke(None, config)— LangGraph detects the interrupted checkpoint and continues from where it stopped.
22. What is the difference between interrupt_before and interrupt_after?
- interrupt_before=[“node_name”] : Pauses BEFORE the node executes. The node hasn’t run yet — human can prevent or modify the action before it happens. Ideal for approval workflows.
- interrupt_after=[“node_name”] : Pauses AFTER the node executes. Human reviews the output of the node. Ideal for review-and-correct workflows.
Tools & Tool Nodes
23. What is a ToolNode and how does it work?
ToolNode is a pre-built LangGraph node that executes tool calls found in the last AIMessage in the state. It automatically:
- Extracts tool calls from the message
- Executes each tool (in parallel if multiple)
- Returns ToolMessage results appended to the messages list
from langgraph.prebuilt import ToolNode
tools = [search_tool, calculator_tool]
tool_node = ToolNode(tools)
graph.add_node("tools", tool_node)
24. How do you handle tool errors in LangGraph?
ToolNode has a tool_handle_error parameter (default True) that catches exceptions and returns them as ToolMessage with error content instead of crashing the graph. You can also pass a custom error handler function. For custom tool nodes, wrap tool calls in try/except and return error messages in the state.
25. How do you implement parallel tool execution in LangGraph?
ToolNode executes multiple tool calls from a single AIMessage in parallel using asyncio.gather (async) or ThreadPoolExecutor (sync). For custom parallel execution, use Send API to fan out to multiple nodes simultaneously.
26. What is create_react_agent and when should you use it vs building a custom graph?
create_react_agent is a prebuilt function that creates a standard ReAct (Reason + Act) agent graph with an LLM node and a ToolNode in a loop. Use it for standard tool-calling agents. Build a custom graph when you need: custom state fields, multiple agents, complex routing logic, specialized node behavior, or non-standard agent patterns.
27. How do you bind tools to an LLM in LangGraph?
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o")
llm_with_tools = llm.bind_tools(tools)
def agent_node(state):
response = llm_with_tools.invoke(state["messages"])
return {"messages": [response]}
The LLM generates AIMessage with tool_calls field populated when it decides to use a tool.
28. How do you implement a tool that modifies graph state directly?
Use the InjectedState annotation to pass state into a tool, or use InjectedStore for store access. For direct state modification, return an Command object from the tool with state updates — this is the “tool-as-node” pattern in newer LangGraph versions.
from langgraph.prebuilt import InjectedState
from typing import Annotated
def my_tool(query: str, state: Annotated[dict, InjectedState]) -> str:
# access state inside tool
return f"User context: {state['user_id']}, query: {query}"
Multi-Agent Architectures
29. What are the main multi-agent patterns in LangGraph?
- Supervisor: A central LLM routes tasks to specialized sub-agents and aggregates results.
- Swarm/Handoff: Agents pass control to each other peer-to-peer using handoff tools.
- Hierarchical: Supervisor agents manage other supervisor agents in a tree structure.
- Parallel fan-out: Multiple agents run simultaneously on different subtasks, results merged.
- Sequential pipeline: Output of one agent feeds into the next.
30. How do you implement a supervisor agent in LangGraph?
The supervisor is an LLM node that decides which worker agent to call next (or to finish). Workers are subgraphs or nodes. The supervisor uses a structured output or tool call to route.
def supervisor_node(state):
response = supervisor_llm.invoke(state["messages"])
return {"next": response.next_agent} # "researcher", "coder", or "FINISH"
graph.add_conditional_edges("supervisor", lambda s: s["next"], {
"researcher": "researcher_node",
"coder": "coder_node",
"FINISH": END
})