Interview Questions — LangGraph¶
Q1: What problem does LangGraph solve that a plain tool-calling loop can't?
Show answer
A plain tool-calling loop handles one linear flow: model → tool → model → tool → answer. It can't express:
- Branching: route to node A if score is high, node B if low
- Cycles: retry a node based on quality output
- Parallelism: execute independent nodes simultaneously
- Human-in-the-loop: pause the graph and wait for human input
- Persistent state: resume a graph from a checkpoint after a crash or user pause
- Multi-agent coordination: orchestrator routes to specialized sub-agents
LangGraph represents these control-flow patterns explicitly as graph structure (nodes + edges), making complex agent behavior inspectable, debuggable, and testable rather than buried in loop logic.
Q2: What is the difference between a direct edge and a conditional edge in LangGraph?
Show answer
Direct edge: graph.add_edge("node_a", "node_b") — execution always goes from A to B. Deterministic, no branching.
Conditional edge: graph.add_conditional_edges("node_a", router_fn, {"key": "node_b", ...}) — execution goes to the node named by whatever string router_fn(state) returns. The router reads the current state and returns a key; the key maps to a target node.
The router function has the signature (state: StateType) -> str. It must return a value that exists as a key in the edge map — otherwise LangGraph raises a KeyError at runtime.
Always include a catch-all path or validate router outputs to prevent unexpected failures.
Q3: Explain the Annotated[list[X], operator.add] pattern in LangGraph state. What does it change?
Show answer
Without annotation, when a node returns {"messages": [new_msg]}, LangGraph replaces the entire messages field with [new_msg] — losing all previous messages.
Annotated[list[BaseMessage], operator.add] attaches a reducer to the field. The reducer defines how to merge new values with existing ones. operator.add for lists is list concatenation — so returning {"messages": [new_msg]} appends new_msg to the existing list instead of replacing it.
This is essential for message history in conversational agents: every node adds its messages and all previous messages are preserved.
You can write custom reducers too:
Q4: What does graph.compile(checkpointer=MemorySaver()) give you that compilation without a checkpointer doesn't?
Show answer
Without a checkpointer, the graph executes and returns. If execution fails mid-way, state is lost. There's no way to pause, inspect intermediate state, or implement human-in-the-loop.
With MemorySaver() (or a persistent backend like SqliteSaver):
1. State is saved after every node — if execution fails, you can resume from the last successful node
2. app.get_state(config) returns the current state at any point, for inspection and debugging
3. Human-in-the-loop: call app.invoke(..., interrupt_before=["node_name"]) to pause before a specific node and wait for human approval before continuing
4. Separate sessions: different thread_id values in config maintain completely independent state, enabling multiple concurrent users
MemorySaver stores state in Python memory (process-local). For production, use SqliteSaver or a Redis-based checkpointer for durability across restarts.
Q5: Describe the supervisor pattern in multi-agent LangGraph. What does the supervisor do, and how does it return control from specialists?
Show answer
In the supervisor pattern:
- The supervisor is a node that reads the current state and decides which specialist to invoke next. It returns {"next_step": "researcher"} or similar.
- A conditional edge from the supervisor routes to the correct specialist based on next_step.
- Specialist nodes do their work and route back to the supervisor via a direct edge: graph.add_edge("research", "supervisor").
- The supervisor runs again, checks what's been completed, and routes to the next specialist or to END.
This creates a hub-and-spoke topology: supervisor → specialist → supervisor → specialist → ... → END.
The supervisor's state-reading logic determines when to stop. Example: if research is done and analysis is done and writing is done, return "done" which routes to END.
Q6: When should you use LangGraph instead of a simpler tool-calling loop or LCEL chain?
Show answer
Use LCEL chain when: linear pipeline, no branching, no loops, fixed number of steps. Most RAG pipelines fit here.
Use tool-calling loop when: unknown number of steps, dynamic tool selection, single goal. Most straightforward agents fit here.
Use LangGraph when any of these apply: - Retry loop: "generate → validate → retry if bad" — needs a cycle - Quality-gated workflow: route based on score/classification - Multiple specialized agents: researcher + writer + critic with explicit handoffs - Human-in-the-loop: must pause and wait for review - Persistent state: conversation that resumes across sessions - Parallel execution: independent subtasks that can run concurrently
The key question: is the control flow expressible as a graph? If yes, LangGraph. If it's a simple sequence, LCEL is lighter. If it's a basic tool loop, use the raw API directly.