0% found this document useful (0 votes)
5 views23 pages

LangChain LangGraph Tutorial

The document serves as a comprehensive guide on LangChain and LangGraph, focusing on their architectures, functionalities, and applications in machine learning engineering. It details LangChain's modular framework for building applications with large language models and LangGraph's stateful orchestration capabilities, enabling complex workflows with cycles and human-in-the-loop support. Additionally, it includes an interview preparation section with questions on conceptual, coding, and system design topics related to these frameworks.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
5 views23 pages

LangChain LangGraph Tutorial

The document serves as a comprehensive guide on LangChain and LangGraph, focusing on their architectures, functionalities, and applications in machine learning engineering. It details LangChain's modular framework for building applications with large language models and LangGraph's stateful orchestration capabilities, enabling complex workflows with cycles and human-in-the-loop support. Additionally, it includes an interview preparation section with questions on conceptual, coding, and system design topics related to these frameworks.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

LangChain & LangGraph

Complete Engineering Tutorial & Interview Preparation Guide

Sourced from: LangChain Docs · LangGraph GitHub · DataCamp · Towards Data Science
Latenode · Real Python · Towards AI · LangChain Academy · Versalence Blogs

March 2026 · ML Engineering Reference


Part Contents

PART 1 — LangChain Architecture · LCEL · RAG Pipelines · Tool Calling · Agents

PART 2 — LangGraph StateGraph · Nodes/Edges · Memory · Human-in-Loop · Multi-Agent

PART 3 — Interview 23 questions: Conceptual · Coding · System Design · Advanced


Qs

Cheatsheet Quick-reference commands for LangChain and LangGraph


PART 1 — LangChain: Framework Deep Dive

1.1 What is LangChain?


LangChain is an open-source Python and JavaScript framework that simplifies building applications powered
by large language models. Its core value is composability — it provides a unified interface for connecting
LLMs to data sources, tools, and APIs through modular, pipeable components. LangChain excels at linear,
sequential workflows: RAG pipelines, chatbots, document processing, and simple tool-calling agents.

Key insight: LangChain provides the components (models, prompts, retrievers, tools). LangGraph provides the
orchestration engine (stateful graphs, cycles, human-in-the-loop). They are complementary — LangGraph is
built on top of LangChain concepts.

1.2 Core Architecture — Six Pillars


Pillar Purpose Key Classes / Functions

Models Unified interface to LLMs and chat ChatOpenAI, ChatAnthropic, HuggingFaceHub


models from any provider.

Prompts Template-based prompt management ChatPromptTemplate, PromptTemplate,


with variables and few-shot examples. FewShotPromptTemplate

Chains / LCEL Composable pipelines via pipe (|) RunnableSequence, RunnableParallel,


syntax. Async and streaming built-in. RunnablePassthrough

Memory Persist conversation context across ConversationBufferMemory,


turns. Multiple storage backends. RedisChatMessageHistory,
PostgresChatMessageHistory

Retrievers / Ingest, embed, and retrieve documents Chroma, FAISS, Pinecone, Weaviate, Milvus
VectorStores for RAG.

Agents and Tools LLM dynamically selects tools and AgentExecutor, @tool, StructuredTool,
decides execution order. create_tool_calling_agent

1.3 LangChain Expression Language (LCEL)


LCEL is the declarative way to compose chains using the pipe operator (|). Every component is a Runnable —
implementing invoke(), stream(), batch(), and ainvoke(). Chaining Runnables creates a new Runnable,
enabling nesting, async support, and streaming with no extra code. LCEL builds a Directed Acyclic Graph
(DAG) — linear, no loops.
◆ PYTHON — Basic LCEL chain
from langchain_openai import ChatOpenAI

from langchain_core.prompts import ChatPromptTemplate

from langchain_core.output_parsers import StrOutputParser

prompt = ChatPromptTemplate.from_template(

"Summarise this in one sentence: {text}"

llm = ChatOpenAI(model="gpt-4o")

parser = StrOutputParser()

# Pipe operator assembles the chain

chain = prompt | llm | parser

# --- Three execution modes ---

# 1. Synchronous

result = [Link]({"text": "LangChain is an LLM framework..."})

# 2. Streaming (token-by-token)

for chunk in [Link]({"text": "LangChain is an LLM framework..."}):

print(chunk, end="", flush=True)

# 3. Batch (parallel inference over a list)

results = [Link]([

{"text": "Document one..."},

{"text": "Document two..."},

])

◆ PYTHON — RunnableParallel for RAG fan-out

from langchain_core.runnables import RunnableParallel, RunnablePassthrough

# retriever and format_docs defined separately

rag_chain = (

RunnableParallel(

context = retriever | format_docs, # fetch + format docs

question = RunnablePassthrough() # pass query unchanged

| qa_prompt # inject context + question into prompt

| llm

| StrOutputParser()

answer = rag_chain.invoke("What is LangChain?")


# RunnableParallel runs both branches concurrently, merges into a dict

1.4 Structured Output


Modern LLMs can return structured JSON matching a Pydantic schema. with_structured_output() wraps the
model with schema enforcement via tool/function calling, returning validated Python objects instead of raw
strings.
◆ PYTHON — Structured output with Pydantic

from pydantic import BaseModel, Field

from langchain_openai import ChatOpenAI

class SentimentResult(BaseModel):

label: str = Field(description="positive | negative | neutral")

confidence: float = Field(description="Score between 0 and 1")

reasoning: str = Field(description="Brief explanation")

llm = ChatOpenAI(model="gpt-4o-mini")

structured_llm = llm.with_structured_output(SentimentResult)

result = structured_llm.invoke("I absolutely love building with LangChain!")

print([Link]) # positive

print([Link]) # e.g. 0.97

print([Link]) # "Strong positive sentiment expressed..."

1.5 Building a RAG Pipeline


Retrieval-Augmented Generation (RAG) grounds LLM responses in external documents, reducing
hallucinations. It has two phases: an offline indexing phase and an online retrieval-generation phase.

• Phase 1 — Indexing (offline): Load → Split → Embed → Store in vector DB.


• Phase 2 — Retrieval + Generation (online): Embed query → Search → Inject context → LLM.
◆ PYTHON — Full RAG pipeline

from langchain_community.document_loaders import WebBaseLoader

from langchain_text_splitters import RecursiveCharacterTextSplitter

from langchain_openai import OpenAIEmbeddings, ChatOpenAI

from langchain_community.vectorstores import Chroma

from langchain_core.prompts import ChatPromptTemplate

from langchain_core.output_parsers import StrOutputParser

from langchain_core.runnables import RunnableParallel, RunnablePassthrough

# ■■ PHASE 1: INDEXING ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

loader = WebBaseLoader("[Link]
docs = [Link]()

splitter = RecursiveCharacterTextSplitter(

chunk_size=1000, # chars per chunk

chunk_overlap=200, # overlap to preserve context at boundaries

splits = splitter.split_documents(docs)

vectorstore = Chroma.from_documents(splits, OpenAIEmbeddings())

retriever = vectorstore.as_retriever(search_kwargs={"k": 4})

# ■■ PHASE 2: RETRIEVAL + GENERATION ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

def format_docs(docs):

return "\n\n".join(d.page_content for d in docs)

prompt = ChatPromptTemplate.from_template("""

Answer ONLY based on the context below. If uncertain, say so.

Context:

{context}

Question: {question}

Answer:""")

rag_chain = (

RunnableParallel(

context = retriever | format_docs,

question = RunnablePassthrough()

| prompt

| ChatOpenAI(model="gpt-4o")

| StrOutputParser()

answer = rag_chain.invoke("What does LangChain do?")

Chunking strategy guide

Strategy chunk_size chunk_overlap Best for

Small 256–512 chars 50–100 Dense factual docs, Q&A; systems

Medium 1000–1500 chars 200–300 Technical docs, code, tutorials

Large 2000–4000 chars 400–600 Narrative text, books, long articles


Strategy chunk_size chunk_overlap Best for

Semantic Variable N/A Split at natural paragraph / section breaks

1.6 Tool Calling and Agents


Agents differ from chains in one key way: the LLM dynamically decides which tool to invoke and in what order,
rather than following a hard-coded sequence. This makes agents powerful for open-ended tasks where the
required steps are not known in advance.
◆ PYTHON — Creating a tool-calling agent

from [Link] import create_tool_calling_agent, AgentExecutor

from langchain_core.tools import tool

from langchain_openai import ChatOpenAI

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

# Define tools with docstrings (used as tool descriptions by the LLM)

@tool

def get_weather(city: str) -> str:

"""Return the current weather forecast for a given city."""

return f"Sunny, 28C in {city}" # replace with real API call

@tool

def search_knowledge_base(query: str) -> str:

"""Search the internal knowledge base for relevant information."""

results = [Link](query)

return results[0].page_content

tools = [get_weather, search_knowledge_base]

llm = ChatOpenAI(model="gpt-4o")

# Prompt must include agent_scratchpad for intermediate steps

prompt = ChatPromptTemplate.from_messages([

("system", "You are a helpful assistant. Use tools when needed."),

MessagesPlaceholder("chat_history"),

("user", "{input}"),

MessagesPlaceholder("agent_scratchpad"),

])

agent = create_tool_calling_agent(llm, tools, prompt)

executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

result = [Link]({
"input": "What is the weather in Mumbai, and what is LangChain?",

"chat_history": []

})

print(result["output"])
PART 2 — LangGraph: Stateful Agent
Orchestration

2.1 Why LangGraph?


LCEL chains flow in one direction — once data enters, it moves forward and exits. Real agents need cycles:
retry a failed tool, reflect on output quality, ask a human to approve an action, or coordinate multiple
sub-agents. LangGraph fills this gap with a graph-based runtime that supports cycles, branching, parallel
execution, persistent state, and human-in-the-loop interruptions — all production-grade.

2.2 LangChain vs LangGraph — Comparison


Dimension LangChain (LCEL) LangGraph

Graph type DAG — acyclic, linear flows Directed graph — cycles fully supported

State mgmt No built-in shared state TypedDict / Pydantic schema with reducers

Human-in-loop Not natively supported First-class interrupt / resume support

Checkpointing None MemorySaver, SqliteSaver, PostgresSaver

Best for RAG, linear pipelines, simple chains Multi-agent, long-running stateful workflows

Abstraction Higher — easier to start Lower — more control, steeper learning


curve

Parallel exec Via RunnableParallel Native fan-out / fan-in node execution

Streaming stream() per chain stream_mode values / updates / messages

2.3 Core Primitives


Primitive 1 — State
State is a TypedDict (or Pydantic model) that is the single source of truth flowing through the entire graph.
Every node reads from it and returns partial updates. The Annotated type with a reducer function controls how
updates merge — critical for concurrent nodes.
◆ PYTHON — Defining agent state with reducers

from typing import TypedDict, Annotated, List, Optional

import operator

from langchain_core.messages import AnyMessage

class AgentState(TypedDict):
# [Link] means new messages are APPENDED (not overwritten)

messages: Annotated[List[AnyMessage], [Link]]

user_query: str # original question

summary: str # accumulated summary

step_count: int # track iteration depth

next_action: str # routing hint

# Without the reducer annotation:

# state["messages"] = [new_msg] -- overwrites!

# With [Link] reducer:

# state["messages"] += [new_msg] -- appends!

# This matters when multiple parallel nodes write to the same list.

Primitive 2 — Nodes
Nodes are plain Python functions (or async functions, LangChain Runnables, or compiled sub-graphs) that
receive the full state and return a dict of keys to update. Nodes perform the actual work: calling an LLM,
executing a tool, routing, or running business logic.
◆ PYTHON — Defining nodes

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o")

def call_llm(state: AgentState) -> dict:

"""Node: invoke the LLM with accumulated messages."""

response = [Link](state["messages"])

# Return only the keys we want to update

return {"messages": [response]}

def run_tool(state: AgentState) -> dict:

"""Node: execute tool calls from the last AI message."""

last_msg = state["messages"][-1]

tool_calls = last_msg.tool_calls

results = []

for tc in tool_calls:

tool_fn = tools_by_name[tc["name"]]

output = tool_fn.invoke(tc["args"])

[Link](ToolMessage(content=str(output), tool_call_id=tc["id"]))

return {"messages": results}

Primitive 3 — Edges
Edges define control flow. Direct edges always go from A to B. Conditional edges call a router function that
inspects state and returns the name of the next node — enabling branching and cycles (loops).
◆ PYTHON — Building and compiling a graph with conditional edges

from [Link] import StateGraph, START, END

def should_continue(state: AgentState) -> str:

"""Router: if last message has tool_calls, execute them; else stop."""

last = state["messages"][-1]

if hasattr(last, "tool_calls") and last.tool_calls:

return "run_tool" # continue the loop

return END # done

# ■■ BUILD ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

builder = StateGraph(AgentState)

builder.add_node("call_llm", call_llm)

builder.add_node("run_tool", run_tool)

# Edges

builder.add_edge(START, "call_llm") # entry point

builder.add_conditional_edges("call_llm", should_continue) # branch / end

builder.add_edge("run_tool", "call_llm") # cycle back!

# ■■ COMPILE ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

# compile() validates structure, wires checkpointer, sets recursion limit

graph = [Link]()

from langchain_core.messages import HumanMessage

result = [Link]({

"messages": [HumanMessage(content="What is 144 * 37?")],

"user_query": "What is 144 * 37?",

"summary": "",

"step_count": 0,

"next_action": "",

})

2.4 Memory and Checkpointing


LangGraph supports state persistence at two levels: short-term (within a session) via the state dict, and
long-term (across sessions) via a checkpointer or external store.
Checkpointer Storage Backend Survives Use Case
Restart?

MemorySaver Python dict (RAM) No Development, testing, single-process demo

SqliteSaver SQLite file Yes Small-scale prod, single server

PostgresSaver PostgreSQL DB Yes Multi-instance distributed production

◆ PYTHON — Checkpointing and multi-turn memory

from [Link] import MemorySaver

from langchain_core.messages import HumanMessage

checkpointer = MemorySaver()

graph = [Link](checkpointer=checkpointer)

# thread_id is the session identifier — isolates state per user

config = {"configurable": {"thread_id": "user-shiv-42"}}

# Turn 1 — introduce name

[Link](

{"messages": [HumanMessage("Hi! My name is Shiv.")]},

config

# Turn 2 — graph REMEMBERS "Shiv" from turn 1 via checkpoint

[Link](

{"messages": [HumanMessage("What is my name?")]},

config

# LLM correctly responds: "Your name is Shiv."

2.5 Human-in-the-Loop
LangGraph can pause mid-graph to wait for human input or approval. Compile with
interrupt_before=['node_name'] to pause before that node. After human review, call invoke(None, config) to
resume from the exact checkpoint.
◆ PYTHON — Human approval before tool execution

from [Link] import MemorySaver

# Pause BEFORE run_tool to let a human review/approve the tool call

graph = [Link](

checkpointer=MemorySaver(),

interrupt_before=["run_tool"]
)

config = {"configurable": {"thread_id": "approval-session"}}

# Step 1: Run until interrupt

snapshot = [Link](initial_state, config)

# Inspect what the agent wants to do

pending_state = graph.get_state(config)

print("Agent wants to call:", pending_state.values["messages"][-1].tool_calls)

# (Optional) Modify the state before resuming

# graph.update_state(config, {"messages": [corrected_msg]})

# Step 2: Resume — passing None continues from checkpoint

final_result = [Link](None, config)

2.6 Multi-Agent Supervisor Pattern


A supervisor node (powered by an LLM classifier) routes each query to a specialised sub-agent. Each
sub-agent is an independent node or sub-graph. Conditional edges from the supervisor implement the routing
logic.
◆ PYTHON — Supervisor routing to specialised agents

from typing import TypedDict, Literal

from langchain_core.messages import SystemMessage, HumanMessage

from [Link] import StateGraph, START, END

class MultiAgentState(TypedDict):

question: str

question_type: str # "DATABASE" | "LANGCHAIN" | "GENERAL"

answer: str

CLASSIFY_PROMPT = """

Classify the question into exactly one of:

DATABASE - queries about databases or SQL

LANGCHAIN - queries about LangChain or LangGraph

GENERAL - anything else

Reply with ONLY the category word, nothing else.

"""

def router_node(state: MultiAgentState) -> dict:

"""LLM classifies the question type."""

response = [Link]([
SystemMessage(content=CLASSIFY_PROMPT),

HumanMessage(content=state["question"])

])

return {"question_type": [Link]()}

def db_agent(state: MultiAgentState) -> dict:

"""Handles database-related questions."""

answer = [Link](f"Answer this database question: {state['question']}")

return {"answer": [Link]}

def lc_agent(state: MultiAgentState) -> dict:

"""Handles LangChain/LangGraph questions."""

answer = [Link](f"As an LLM expert, answer: {state['question']}")

return {"answer": [Link]}

def general_agent(state: MultiAgentState) -> dict:

"""Handles general questions."""

answer = [Link](state["question"])

return {"answer": [Link]}

def route_by_type(state: MultiAgentState) -> str:

routing = {"DATABASE": "db_agent", "LANGCHAIN": "lc_agent"}

return [Link](state["question_type"], "general_agent")

# Assemble graph

builder = StateGraph(MultiAgentState)

builder.add_node("router", router_node)

builder.add_node("db_agent", db_agent)

builder.add_node("lc_agent", lc_agent)

builder.add_node("general_agent", general_agent)

builder.add_edge(START, "router")

builder.add_conditional_edges("router", route_by_type)

for agent in ["db_agent", "lc_agent", "general_agent"]:

builder.add_edge(agent, END)

graph = [Link]()

2.7 Streaming Modes


◆ PYTHON — Three streaming modes in LangGraph

# Mode 1: 'values' — full state after each node completes


for snapshot in [Link](initial_state, config, stream_mode="values"):

last_msg = snapshot["messages"][-1]

print(f"[State after node] {last_msg.content[:80]}")

# Mode 2: 'updates' — only the changed keys per node (more efficient)

for chunk in [Link](initial_state, config, stream_mode="updates"):

# chunk = {"node_name": {"changed_key": new_value, ...}}

for node_name, updates in [Link]():

print(f"[{node_name}] updated: {list([Link]())}")

# Mode 3: 'messages' / astream_events — LLM token-by-token streaming

import asyncio

async def stream_tokens():

async for event in graph.astream_events(initial_state, config, version="v2"):

if event["event"] == "on_chat_model_stream":

token = event["data"]["chunk"].content

print(token, end="", flush=True)

[Link](stream_tokens())

2.8 Subgraphs and Modular Composition


A compiled StateGraph is itself a Runnable. It can be added as a node inside a parent graph, enabling
Lego-like composition of complex agent systems from reusable sub-graphs.
◆ PYTHON — Subgraph as a node in a parent graph

# Define and compile a reusable research sub-graph

research_builder = StateGraph(ResearchState)

research_builder.add_node("search", web_search_node)

research_builder.add_node("summarise", summarise_node)

research_builder.add_edge(START, "search")

research_builder.add_edge("search", "summarise")

research_builder.add_edge("summarise", END)

research_graph = research_builder.compile() # this is now a Runnable

# Use the compiled sub-graph as a single node in the parent

parent_builder = StateGraph(ParentState)

parent_builder.add_node("research", research_graph) # sub-graph as node!

parent_builder.add_node("write", write_node)

parent_builder.add_edge(START, "research")

parent_builder.add_edge("research", "write")
parent_builder.add_edge("write", END)

parent_graph = parent_builder.compile()
PART 3 — Interview Questions (23 Total)

Section A — LangChain Core


CONCEPTUAL Q1. What is LangChain and what problems does it solve?
LangChain is an open-source Python/JS framework for building LLM-powered applications. It solves three
problems: (1) Boilerplate — ready-made components for prompts, models, parsers, retrievers; (2)
Composability — LCEL's pipe syntax wires components declaratively; (3) Integration — 100+ provider
integrations (OpenAI, Anthropic, Chroma, FAISS, etc.) behind a unified interface, so swapping providers
requires minimal code changes.

CONCEPTUAL Q2. Explain the difference between a Chain and an Agent in LangChain.
A Chain has a fixed, hard-coded sequence of operations defined at build time — data flows through
predetermined steps in a DAG (no loops). An Agent uses an LLM as a reasoning engine: at each step it
decides which tool to call, with what arguments, and whether to stop — the execution path is dynamic.
Chains are fast and predictable; agents are flexible but more expensive, harder to debug, and require
careful loop-control.

CONCEPTUAL Q3. What is LCEL and why was it introduced?


LCEL (LangChain Expression Language) is a declarative pipe-syntax for composing Runnable objects. It
was introduced to replace the older Chain class hierarchy with a single unified interface: every Runnable
exposes invoke(), batch(), stream(), and ainvoke(). Benefits include built-in async support, streaming
without extra code, schema validation, LangSmith tracing integration, and seamless composition (the
output of one Runnable is the input of the next).

CONCEPTUAL Q4. How does RunnableParallel differ from RunnablePassthrough?


RunnableParallel executes multiple branches concurrently and merges their outputs into a dict — used in
RAG to fetch context and carry the question simultaneously. RunnablePassthrough forwards its input
unchanged — used to thread the original query through a chain while other branches transform it. They are
frequently combined: RunnableParallel(context=retriever|format_docs, question=RunnablePassthrough()).

CONCEPTUAL Q5. Describe the RAG pipeline. What are the main failure modes?
Indexing phase: load documents, split with RecursiveCharacterTextSplitter, embed with
OpenAIEmbeddings, store in Chroma/FAISS. Retrieval+generation phase: embed query, top-k similarity
search, inject retrieved chunks as context, LLM generates answer. Main failure modes: (1) Poor chunking
— chunks too large lose precision, too small lose context; (2) Embedding mismatch — query and
document embeddings in different semantic spaces; (3) Low retrieval recall — relevant chunks not
retrieved; (4) Context window overflow — too many/large chunks; (5) Hallucination when retrieved context
is insufficient.
CONCEPTUAL Q6. How does with_structured_output() work and when would you use it?
with_structured_output(PydanticModel) wraps the LLM to enforce that responses conform to a schema,
using tool/function calling under the hood. The LLM is instructed to call a pseudo-tool whose arguments
match the schema, and the result is returned as a validated Python object. Use it for: classification (label +
confidence), entity extraction, structured data pipelines, or any case where downstream code needs typed,
parseable output rather than free-form text.

CONCEPTUAL Q7. What are the limitations of ConversationBufferMemory in production?


(1) Context window overflow — full history is injected on every turn, hitting token limits as conversations
grow; (2) In-memory only — lost on restart; use RedisChatMessageHistory or
PostgresChatMessageHistory for persistence; (3) Cost — every API call sends the full conversation
history; (4) No summarisation — old messages are never pruned. Alternatives:
ConversationSummaryMemory (LLM-generated summary), ConversationTokenBufferMemory
(token-limited window), or LangGraph's built-in checkpointing.

Section B — LangGraph Core


CONCEPTUAL Q8. What is LangGraph and how does it differ from LangChain LCEL?
LangGraph is a low-level graph-based orchestration framework for stateful, long-running agentic workflows.
LCEL builds linear DAGs — no loops, no shared mutable state, no persistence. LangGraph builds directed
graphs with cycle support (for iterative reasoning and retry loops), a typed shared state schema with
reducers, checkpointing for durability, and first-class human-in-the-loop interruptions. LangChain agents
are now built on LangGraph internally.

CONCEPTUAL Q9. Explain StateGraph, State, Nodes, and Edges.


State is a TypedDict defining all data flowing through the graph — fields annotated with reducer functions
control how concurrent updates merge. Nodes are Python functions that receive state and return a dict of
updates (not the full state). Edges connect nodes — direct edges always fire; conditional edges call a
router function returning the next node name (enabling branching and cycles). StateGraph is the builder:
add nodes, add edges, then call compile() to get an executable graph.

ADVANCED Q10. What is a reducer in LangGraph state and why is it critical?


A reducer is a function annotated on a state field specifying how to merge a new value with the existing
one. Annotated[List[AnyMessage], [Link]] means new messages are appended rather than
overwriting the list. Without reducers, multiple parallel nodes updating the same field would clobber each
other's results. Reducers are especially important in multi-agent fan-out patterns where several nodes write
to shared lists simultaneously.
CONCEPTUAL Q11. What are the three checkpointing backends and when do you use each?
MemorySaver: state stored in a Python dict in RAM — ephemeral (lost on restart). Use for development,
testing, and single-process demos. SqliteSaver: persists state to a SQLite file, survives restarts,
single-server — good for small production deployments. PostgresSaver: shared relational storage —
required when multiple server instances need shared thread state for horizontal scaling. All three are
passed to [Link](checkpointer=...).

CONCEPTUAL Q12. How does human-in-the-loop work in LangGraph?


Compile with interrupt_before=['node_name']. When execution reaches that node boundary, the graph
saves state to the checkpointer and returns control to the caller. The caller inspects state via
graph.get_state(config), optionally modifies it with graph.update_state(config, updates), then resumes by
calling [Link](None, config). The thread_id in config identifies which checkpoint to resume from.
Multiple nodes can be listed — the graph pauses at each in sequence.

ADVANCED Q13. What is time-travel debugging in LangGraph?


Because every state transition is saved by the checkpointer, developers can call
graph.get_state_history(config) to retrieve all past state snapshots for a thread. A past snapshot can be
branched from by invoking the graph from that checkpoint. Use cases: (1) replay a failed run from before
the error; (2) A/B test different routing decisions from a historical branch point; (3) recover from a bad tool
call without re-running the full workflow.

CONCEPTUAL Q14. What streaming modes does LangGraph support?


stream_mode='values': emits the full state dict after each node — easy to inspect but verbose.
stream_mode='updates': emits only the changed keys per node — efficient for large state objects.
stream_mode='messages': streams LLM tokens as they are generated (for chat-style UIs).
astream_events(): fine-grained event callbacks (on_chat_model_stream, on_tool_start, on_chain_end,
etc.) for LangSmith tracing or custom streaming front-ends.

CONCEPTUAL Q15. How do subgraphs work and when should you use them?
A compiled StateGraph is itself a Runnable and can be added as a node in a parent graph via
builder.add_node('name', compiled_subgraph). The subgraph's state schema must have compatible
overlapping keys with the parent. Use subgraphs to: (1) encapsulate a research, data-fetching, or
code-execution agent as a reusable module; (2) run multiple specialised sub-agents in parallel fan-out; (3)
enforce separation of concerns and enable independent testing.

Section C — System Design


Q16. Design a production RAG system for a company's internal knowledge
SYSTEM DESIGN
base.
Ingestion: crawl Confluence/SharePoint with document loaders, chunk with RecursiveCharacterTextSplitter
(1000 chars, 200 overlap), embed with text-embedding-3-large, store in Pinecone/Weaviate with metadata
filters (team, date, doc_type, access_level). Retrieval: hybrid search (dense cosine similarity + BM25
sparse) with Reciprocal Rank Fusion reranking, top-4 chunks. Generation: system prompt enforcing
'answer only from context', structured output with source citations and confidence. Evaluation: RAGAS
metrics (faithfulness, context recall, answer relevance). Caching: GPTCache semantic cache for repeated
queries. Scale: async inference, Redis for conversation memory, PostgresSaver checkpointer, LangSmith
for observability.

SYSTEM DESIGN Q17. Design a multi-agent research system with LangGraph.


State: {question, search_queries, retrieved_chunks, reflections, revision_count, answer}. Nodes: (1)
Supervisor — classifies query, selects agent; (2) Web Research Agent — uses Tavily/Serper, stores
results in state; (3) KB Agent — queries internal vector store; (4) Reflection Node — LLM evaluates if
research is sufficient (conditional edge: loop back to research if not, else proceed); (5) Finalize —
synthesises all findings into answer. Checkpointer: PostgresSaver. Streaming: astream_events for
real-time UI. Safeguards: recursion_limit=25, token budget tracking in state, LangSmith alerting.

Q18. How do you prevent runaway costs and silent failures in production
SYSTEM DESIGN
agents?
(1) Recursion limit: set in [Link]() config — raises GraphRecursionError when exceeded. (2)
Token budgeting: track cumulative tokens in state; guard node routes to END when budget hit. (3) Step
monitoring: add step_count to state, fail fast after N steps. (4) Circuit breaker: track consecutive tool
failures in state, route to fallback after N errors. (5) Model tiering: use gpt-4o-mini for routing/classification,
gpt-4o only for final synthesis. (6) LangSmith alerts: set up monitors on trace duration and token count. (7)
Async with timeouts: wrap LLM calls in asyncio.wait_for().

ADVANCED Q19. Compare LangGraph to CrewAI and AutoGen.


CrewAI is high-level with predefined roles, tasks, and crew abstractions — fast to prototype, less
customisable, opinionated cognitive architecture. AutoGen focuses on conversational multi-agent patterns
with back-and-forth message passing between agents. LangGraph is the most low-level: no predefined
architecture, full control over state, edges, interrupts, and checkpointing. LangGraph wins for production
systems requiring durability, human-in-the-loop, time-travel debugging, fine-grained observability, and
custom routing logic. CrewAI/AutoGen win for faster prototyping when flexibility is not the priority.
Q20. What is MCP (Model Context Protocol) and how does LangGraph support
ADVANCED
it?
MCP is an open standard allowing AI models to auto-discover and invoke tools from external servers
(databases, APIs, file systems) without hard-coding tool schemas. LangGraph integrates with MCP via the
[Link].mcp_tools or custom tool loaders, allowing agents to dynamically discover available
tools at runtime. This enables plug-and-play tool ecosystems — new MCP-compliant tools become
available to agents without code changes, supporting true ambient agents that adapt their tool set based on
what servers are connected.

Section D — Coding Questions


CODING Q21. Implement a ReAct agent with LangGraph (web search + calculator).
State = MessagesState (annotated list of AnyMessage). Node 1 (call_model): bind tools to LLM with
llm.bind_tools(tools), invoke with state['messages'], return AI message. Node 2 (tool_node): use prebuilt
ToolNode(tools) which automatically parses tool_calls from the last AI message and returns ToolMessage
results. Conditional edge from call_model: if last message has tool_calls -> 'tool_node', else -> END. Direct
edge from tool_node -> 'call_model' (the ReAct cycle). Compile with MemorySaver. The loop continues
until the LLM stops calling tools.

CODING Q22. Implement a self-reflection agent (Reflexion architecture).


State: {draft_answer, critique, revision_count, final_answer}. Node 1 (generate): LLM writes a draft answer.
Node 2 (critique): separate LLM prompt evaluates quality — returns {quality_score, feedback}. Node 3
(revise): LLM rewrites draft based on critique feedback. Conditional edge from critique: if quality_score >=
0.85 OR revision_count >= 3 -> END, else -> revise. Edge from revise -> generate (reflection loop). This
implements the Reflexion paper (Shinn et al. 2023). Key: the critique node uses a different system prompt
focusing on accuracy, completeness, and clarity — not just repeating the answer.

CODING Q23. How do you implement cross-session long-term memory in LangGraph?


Use an InMemoryStore (or Redis/Postgres store) passed to [Link](store=store). Access within
nodes via the config argument: store = [Link]('store'). Pattern: (1) 'load_memory' node at graph start
retrieves user-level facts keyed by (user_id, memory_type) and prepends them as a SystemMessage; (2)
'save_memory' node after key interactions extracts important facts via an LLM and writes them back with
[Link](namespace, key, value). Thread checkpointing handles within-session (short-term) memory; the
store handles cross-session (long-term) memory. The two systems are orthogonal.
Quick Reference Cheatsheet

LangChain
Install pip install langchain langchain-openai langchain-community chromadb

Basic LCEL chain chain = prompt | llm | StrOutputParser()

Invoke [Link]({'key': value})

Stream tokens for chunk in [Link]({'key': value}): print(chunk, end='')

Batch inference [Link]([{'key': v1}, {'key': v2}])

Async invoke await [Link]({'key': value})

Parallel branches RunnableParallel(a=runnable_a, b=runnable_b)

Pass-through RunnablePassthrough() — forward input unchanged

Structured output llm.with_structured_output(PydanticModel)

Define tool @tool decorator or StructuredTool.from_function()

Create agent create_tool_calling_agent(llm, tools, prompt)

Run agent AgentExecutor(agent=agent, tools=tools, verbose=True).invoke(...)

Vector store Chroma.from_documents(splits, OpenAIEmbeddings())

Retriever vectorstore.as_retriever(search_kwargs={'k': 4})

LangGraph
Install pip install langgraph

class MyState(TypedDict): msgs: Annotated[List[AnyMessage],


Define state [Link]]

Create builder builder = StateGraph(MyState)

Add node builder.add_node('node_name', node_fn)

Direct edge builder.add_edge('a', 'b') or builder.add_edge(START, 'first_node')

Conditional edge builder.add_conditional_edges('node', router_fn)

Interrupt before [Link](interrupt_before=['tool_node'])

Compile graph = [Link](checkpointer=MemorySaver())

Invoke [Link](state, {'configurable': {'thread_id': 'abc'}})

Resume (HITL) [Link](None, config) # resume from checkpoint

Get state graph.get_state(config)

Update state graph.update_state(config, {'key': new_value})


State history graph.get_state_history(config) # time-travel

Stream updates [Link](state, config, stream_mode='updates')

Stream values [Link](state, config, stream_mode='values')

async for event in graph.astream_events(state, config, version='v2'):


Async stream events ...

Subgraph as node builder.add_node('sub', compiled_subgraph)

SqliteSaver SqliteSaver.from_conn_string('agent_state.db')

Sources: LangChain Official Docs ([Link]) · LangGraph GitHub · DataCamp · Towards Data Science · Latenode · Real
Python · Towards AI · LangChain Academy · Versalence Blogs · March 2026

You might also like