45 questions - Intermediate

LangChain interview questions and answers

LangChain questions check whether you understand the pieces under an LLM app rather than one tutorial. Expect the Runnable interface and LCEL pipes, prompt templates and output parsers, document loaders, splitters and retrievers for RAG, tools and agents, structured output, chat history, streaming, fallbacks and how you trace and debug a chain. Interviewers also ask when you would not use a framework at all.

Last updated:

LangChain basics

What is LangChain?

LangChain is an open-source framework for building LLM applications. It gives one interface over many model providers and standard building blocks for prompts, output parsing, retrieval, tools and agents, which you compose into chains. It has Python and JavaScript versions.

How is the LangChain ecosystem split into packages?

langchain-core holds the base abstractions such as Runnables, messages and prompts. Provider integrations live in their own packages, and community integrations in langchain-community. LangGraph is the separate library for stateful agents, and LangSmith is the hosted tracing and evaluation platform.

What is the difference between an LLM and a chat model in LangChain?

An LLM class takes a string and returns a string, the older completion style. A chat model takes a list of messages with roles, such as system, human and AI, and returns an AI message, which can also carry tool calls. Modern models are chat models, so most code uses them.

What message types does LangChain use?

SystemMessage sets behaviour, HumanMessage is user input, AIMessage is the model's reply and may contain tool_calls, and ToolMessage returns a tool's result to the model with the matching tool_call_id. Chat history is simply a list of these.

from langchain_core.messages import SystemMessage, HumanMessage

messages = [
    SystemMessage("You are a concise assistant."),
    HumanMessage("Explain an index in one line."),
]
reply = model.invoke(messages)
print(reply.content)

How do you switch between model providers in LangChain?

Every provider's chat model implements the same chat model interface, so the rest of the chain does not change. You can construct a different provider class, or use init_chat_model with a model name and provider so the choice comes from configuration instead of code.

from langchain.chat_models import init_chat_model

model = init_chat_model(settings.MODEL_NAME, model_provider=settings.MODEL_PROVIDER, temperature=0)

When would you not use LangChain?

For a simple app that calls one model with one prompt, the provider SDK is less code and easier to debug. Frameworks add abstraction layers and version churn. LangChain pays off when you swap providers, compose retrieval, tools and parsing, or want built-in tracing and evaluation.

LCEL and Runnables

What is a Runnable?

Runnable is the common interface that models, prompts, parsers, retrievers and chains all implement. Every Runnable supports invoke for one input, batch for many, stream for incremental output, and async versions such as ainvoke and astream, so any piece can be swapped or combined the same way.

What is LCEL?

LangChain Expression Language composes Runnables with the pipe operator, so prompt | model | parser builds a chain where each output feeds the next input. The result is itself a Runnable, so it gets streaming, batching, async and tracing without extra code.

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

prompt = ChatPromptTemplate.from_template("Give one interview tip about {topic}.")
chain = prompt | model | StrOutputParser()
print(chain.invoke({"topic": "SQL joins"}))

What do RunnablePassthrough and RunnableParallel do?

RunnableParallel runs several Runnables on the same input and returns a dict of their outputs; a plain dict in a chain becomes one automatically. RunnablePassthrough passes the input through unchanged, which is how a RAG chain sends the question to the prompt while a retriever fetches context.

from langchain_core.runnables import RunnablePassthrough

rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | prompt
    | model
    | StrOutputParser()
)

How do you add your own Python function to a chain?

Wrap it in RunnableLambda, or just place the function in a pipe, and LangChain converts it. The function receives the previous step's output. Use it for formatting documents, cleaning text or small transformations between steps.

from langchain_core.runnables import RunnableLambda

def format_docs(docs):
    return "\n\n".join(doc.page_content for doc in docs)

formatter = RunnableLambda(format_docs)

How do you route an input to different chains?

Use RunnableBranch with condition and chain pairs plus a default, or write a RunnableLambda that returns the chosen chain. A common pattern is a cheap classification step first, such as "billing" versus "technical", then routing to a prompt built for that category.

How do you add retries and fallbacks?

Call .with_retry() on a Runnable to retry transient errors with backoff, and .with_fallbacks([backup]) to try another Runnable, such as a different model or provider, when the first one fails. Both return new Runnables, so they fit anywhere in a chain.

safe_model = primary_model.with_retry(stop_after_attempt=3).with_fallbacks([backup_model])

How do you stream output from a chain?

Call stream or astream instead of invoke; each step that can stream, such as the model and a string parser, yields chunks as they arrive. For a web app, forward those chunks over Server-Sent Events. astream_events gives finer events from inside the chain, such as retriever results.

for chunk in chain.stream({"topic": "Python decorators"}):
    print(chunk, end="", flush=True)

What does RunnablePassthrough.assign do?

It keeps every key of the input dict and adds new keys computed from it. That lets a chain build up a result step by step, for example adding the retrieved context, then the answer, while the original question stays available to later steps.

chain = RunnablePassthrough.assign(
    context=lambda x: format_docs(retriever.invoke(x["question"]))
) | prompt | model

How do you run a chain on many inputs at once?

Call batch, or abatch in async code, with a list of inputs. LangChain runs them concurrently and returns outputs in the same order. Pass max_concurrency in the config so you stay within the provider's rate limits.

results = chain.batch(
    [{"topic": "joins"}, {"topic": "indexes"}, {"topic": "views"}],
    config={"max_concurrency": 2},
)

How do you fix model settings such as stop sequences inside a chain?

Use .bind() to attach keyword arguments that are sent on every call, such as stop sequences, and .with_config() to attach run settings such as tags or a run name. Both return a new Runnable, so the original model object stays unchanged for other chains.

Prompts and output parsing

What is a ChatPromptTemplate?

It is a reusable prompt made of role-tagged messages with placeholders. You fill it with a dict of variables and get a list of messages for a chat model. MessagesPlaceholder inserts a list of messages, such as chat history, at a chosen spot.

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

prompt = ChatPromptTemplate.from_messages([
    ("system", "You answer questions about {company}."),
    MessagesPlaceholder("history"),
    ("human", "{question}"),
])

How do you get structured output from a model in LangChain?

Call model.with_structured_output(Schema) with a Pydantic model or JSON schema. It uses the provider's native tool calling or JSON mode where available and returns a validated object, which is more reliable than asking for JSON in the prompt and parsing the text.

from pydantic import BaseModel

class Ticket(BaseModel):
    category: str
    urgent: bool

classifier = model.with_structured_output(Ticket)
ticket = classifier.invoke("My payment failed twice, please help now")
print(ticket.category, ticket.urgent)

What are output parsers?

Output parsers turn a model's reply into a usable value: StrOutputParser extracts the text, JsonOutputParser parses JSON (and can stream partial JSON), and PydanticOutputParser validates against a model. With models that support structured output, with_structured_output is usually the better choice.

How do you add few-shot examples to a prompt?

Use FewShotChatMessagePromptTemplate with a list of example input and output pairs, or an example selector that picks the examples most similar to the current input from a vector store. Selecting examples dynamically keeps the prompt short when you have many.

What is a partial prompt?

A partial prompt fills some variables in advance and leaves the rest for later. Use it when a value is known early, such as the company name, or should be computed at call time, such as today's date, so callers only pass the remaining variables.

dated_prompt = prompt.partial(company="Acme")
dated_prompt.invoke({"history": [], "question": "What is the refund policy?"})

What is the difference between tool calling and structured output?

With tool calling, the model may choose to call one of several tools, or none, and your code runs the tool. With structured output, you force the model to return exactly one schema as its final answer. Use structured output for extraction and classification, tools for actions and lookups.

Retrieval and RAG in LangChain

What are document loaders?

Loaders read data from a source, such as PDFs, web pages, CSV files, Notion or a database, and return Document objects. Each Document has page_content for the text and a metadata dict for things like source and page, which later supports filtering and citations.

How do text splitters work?

Splitters cut long Documents into chunks of a target size with overlap. RecursiveCharacterTextSplitter tries separators in order, such as blank lines, new lines, then spaces, so chunks follow paragraphs where possible. There are also splitters for Markdown headers, code and token counts.

from langchain_text_splitters import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(chunk_size=800, chunk_overlap=100)
chunks = splitter.split_documents(docs)

What is the difference between a vector store and a retriever?

A vector store stores embeddings and runs similarity search. A retriever is a simpler interface that takes a query string and returns Documents, and it can wrap a vector store or anything else, such as a keyword index or a web search. Chains depend on the retriever, so you can swap the backend.

retriever = vector_store.as_retriever(search_type="mmr", search_kwargs={"k": 5})
docs = retriever.invoke("What is the notice period?")

Which advanced retrievers does LangChain offer?

MultiQueryRetriever asks the LLM for several versions of the question and merges results. EnsembleRetriever combines retrievers such as BM25 and vectors with rank fusion. ParentDocumentRetriever searches small chunks but returns larger parents, and contextual compression retrievers rerank or trim documents before they reach the prompt.

How do you build a RAG chain that returns its sources?

Run the retriever and the answer step in parallel structure so the output keeps both: a dict with the context documents and the generated answer. Then show each document's metadata, such as title and page, as citations next to the answer.

from langchain_core.runnables import RunnableParallel, RunnablePassthrough

answer_chain = prompt | model | StrOutputParser()
with_sources = RunnableParallel(
    {"context": retriever, "question": RunnablePassthrough()}
).assign(answer=lambda x: answer_chain.invoke(
    {"context": format_docs(x["context"]), "question": x["question"]}
))

Why do embedding models and vector stores have separate classes?

An Embeddings class only turns text into vectors, with embed_documents and embed_query. A VectorStore class handles storage and search and takes an embeddings object. Keeping them apart lets you pair any embedding model with any store and change one without the other.

What is a self-query retriever?

A self-query retriever asks the LLM to split the user's question into a semantic search string and structured metadata filters, such as year equals 2026 or type equals policy, using a description of the metadata fields you provide. The vector store then runs the filtered search.

How do you avoid duplicate chunks when you re-index documents?

Give every chunk a stable ID based on the source and a content hash, and upsert by ID instead of adding blindly. LangChain's indexing API uses a record manager to track what was written, skip unchanged chunks and delete chunks whose source document changed or disappeared.

Tools, agents and memory

How do you define a tool in LangChain?

Decorate a function with @tool. The function name, type hints and docstring become the tool's name, argument schema and description, which the model reads to decide when to call it, so write the docstring for the model.

from langchain_core.tools import tool

@tool
def get_order_status(order_id: str) -> str:
    """Return the delivery status for an order ID."""
    return lookup_status(order_id)

What does bind_tools do?

model.bind_tools([tools]) sends the tool schemas with every call. The model then replies either with text or with tool_calls naming a tool and its arguments. Your code, or an agent loop, runs the tool and sends the result back as a ToolMessage so the model can continue.

model_with_tools = model.bind_tools([get_order_status])
reply = model_with_tools.invoke("Where is order 1182?")
for call in reply.tool_calls:
    print(call["name"], call["args"])

How do agents relate to LangChain and LangGraph today?

An agent is a loop where the model chooses tools until it can answer. The older AgentExecutor classes are legacy; new agent code is built on LangGraph, either with a prebuilt tool-calling agent or a custom graph, because it gives control over state, loops, persistence and human approval.

How do you keep chat history in a LangChain app?

Store messages per conversation ID in a database, load them into a MessagesPlaceholder for each call, and append the new human and AI messages after. Trim or summarise old messages to stay within the context window. LangGraph checkpointers now handle this persistence for agent apps.

How do you count tokens and control cost in LangChain?

Chat model responses carry usage metadata with input and output token counts, and tracing tools aggregate them per run. Control cost by trimming history, limiting retrieved chunks, using a smaller model for simple steps such as routing, and caching repeated calls.

How should a tool report errors back to the model?

Catch the exception and return a short, clear error message as the tool result instead of crashing the loop. The model can then fix its arguments, try another tool or tell the user. Cap the number of retries so a failing tool cannot loop forever.

How do you stop an agent's tools from doing harm?

Give each tool the narrowest permissions it needs, validate arguments with a schema, make destructive actions require human approval, and run code tools in a sandbox. Never pass raw model output into SQL, shell commands or URLs without checks.

Debugging, tracing and production

What is LangSmith used for?

LangSmith records traces of every run: each step's inputs, outputs, latency, tokens and errors. It also stores datasets and runs evaluations against them, so you can compare prompt or model changes before release. You enable tracing with environment variables, without changing chain code.

How do you debug a chain that gives a bad answer?

Look at the trace step by step: the filled prompt, the retrieved documents, the raw model output and the parsed result. Most problems show up as a wrong variable in the prompt, poor retrieval or a parser failing on unexpected text. Locally, set_debug(True) prints every step.

How do you serve a LangChain chain as an API?

Wrap it in a web framework such as FastAPI: validate the request, call ainvoke for a normal reply or astream for streaming, and return the result. Build the chain once at startup, keep it stateless, and store conversation state in a database keyed by session.

How do you pass configuration such as user ID or model choice at runtime?

Every invoke accepts a config dict with tags, metadata, callbacks and configurable values. Use configurable fields or alternatives to switch models or settings per call, and put the user or tenant ID in metadata so traces can be filtered by it.

chain.invoke(
    {"topic": "Docker"},
    config={"metadata": {"user_id": "u_42"}, "tags": ["prod"]},
)

What are the common criticisms of LangChain and how do you answer them?

People cite frequent API changes, deep abstractions that hide what is sent to the model, and hard-to-read stack traces. A good answer: pin versions, use the core Runnable and LCEL pieces, check traces to see the real prompts, and drop down to the provider SDK where the framework adds nothing.

How do you cache model calls in LangChain?

set_llm_cache installs a global cache, in memory, SQLite or a shared store such as Redis, so an identical prompt with identical settings returns the stored reply without a new call. It suits tests and repeated batch jobs; for user chats, near-identical questions need a semantic cache instead.

How do you unit-test a chain without calling a real model?

Replace the model with a fake chat model from langchain_core that returns preset replies, then assert on the prompt that was built and on how your parsers and routing handle each reply. Keep a smaller set of slower tests that call the real model on a fixed evaluation dataset.

What are callbacks in LangChain?

Callbacks are handlers that receive events while a Runnable runs, such as a model starting, a new token arriving, a tool ending or an error. Tracing, logging and streaming to a UI are built on them, and you can pass your own handler in the config for one call.

Continue with another topic

Practise live

CrackInterviewAI listens to the live interview and gives a structured answer on screen for coding, system design, HR and project questions. Download for Windows.