LangChain Interview Questions · 2026

LangChain Interview Questions (2026): Most Asked, With Answers

A candidate interviewing for an AI engineering role in early 2026 walked through a retrieval-augmented generation pipeline he'd shipped, then froze when the interviewer asked why he was still importing ConversationBufferMemory. He'd built the whole thing eight months earlier and never gone back to look at what changed. That's the trap with LangChain right now: the framework itself has been mid-refactor for two years, and a lot of tutorials, and a lot of candidates' mental models, are stuck on an older shape of it. The project's own docs now frame the recommended path as create_agent built on top of LangGraph, with the older memory classes on their way out in favor of LangGraph's checkpointing (LangChain documentation, overview).

Prep guides tend to gloss over one thing: interviewers rarely ask "what is LangChain" and stop there. They ask about the seams, why a chain composed with the pipe operator behaves differently under streaming than the equivalent chain built by hand, why a tool call sometimes never fires even though the tool is bound correctly, why a retriever that worked fine last month starts returning worse results the day someone swaps the underlying vector store. Those seams are where the real signal is, and they're also where most candidates who've only followed a getting-started tutorial fall apart.

This page covers LangChain interview questions across eight areas: what the framework actually is and how its packages split up, LCEL and Runnables, prompt templates and output parsers, document loaders and text splitters, embeddings and vector stores, retrieval and RAG chain design, agents and tool calling with LangGraph underneath, and memory plus production concerns like tracing, latency, and cost. Code examples use the Python API throughout.

47Questions
create_agent on LangGraphCore Shift
2-step / agentic / hybridRAG Patterns
CheckpointingMemory Model

What LangChain actually is, and how the packages fit together

Almost every loop opens here, and a surprising number of candidates answer it as if LangChain were a single library instead of a set of packages with different jobs.

Easy questions

15

Calling a model API directly gets you text in, text out. LangChain sits on top of that and standardizes the surrounding plumbing: a common interface across model providers so swapping OpenAI for Anthropic doesn't mean rewriting your call sites, composable building blocks for prompts, retrieval, and tool use, and a way to wire those blocks into a working pipeline without hand-rolling the glue code every time.

The honest caveat: for a single API call with no retrieval and no tools, you don't need LangChain at all, and plenty of production systems skip it for exactly that reason. Its value shows up once you're composing several steps, calling a model, deciding whether to call a tool, retrieving context, and want a consistent way to structure that instead of a pile of one-off functions.

There's a full JavaScript/TypeScript implementation, langchain on npm, maintained alongside the Python package with largely equivalent concepts, Runnables, LCEL, agents, retrievers, though the two ecosystems don't always ship new features on the exact same day. Teams building a Node backend or a full-stack app that wants LLM logic on the server without a separate Python service tend to reach for the JS version specifically for that reason.

The concepts transfer almost one to one between the two, so knowing one gets you most of the way to reading the other's docs, but API surface details, package names, and some integration availability do differ, so don't assume code from a Python tutorial is a drop-in translation.

LangSmith is the observability and evaluation layer, tracing every step of a chain or agent run, prompt versions, token counts, latency per node, so you can see what actually happened inside a run instead of just its final output. It's a separate hosted product, not a hard dependency of LangChain itself.

You can absolutely ship LangChain without it, plenty of teams roll their own logging. Where LangSmith earns its keep is debugging an agent that made a wrong tool call three steps into a run, without it you're staring at a final answer with no visibility into the reasoning path that produced it.

langchain-community is a large grab-bag of integrations maintained collectively, useful for less mainstream tools where no vendor or dedicated maintainer has taken ownership of a first-class package. Partner packages like langchain-openai or langchain-anthropic are maintained directly by, or in close collaboration with, the provider, get faster updates when the provider's API changes, and are the recommended install for any integration that has one.

The practical rule: if a dedicated partner package exists for the provider you're using, install that instead of pulling the equivalent integration out of langchain-community, since the community version is more likely to lag behind provider API changes.

LCEL, LangChain Expression Language, is the declarative syntax for composing Runnables using the | operator. Every piece in the chain, the prompt template, the chat model, the output parser, implements the same Runnable interface, so | just wires the output of one into the input of the next and returns a new Runnable representing the whole pipeline.

python
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI

prompt = ChatPromptTemplate.from_template("Summarize this in one sentence: {text}")
model = ChatOpenAI(model="gpt-4o-mini")
parser = StrOutputParser()

chain = prompt | model | parser
result = chain.invoke({"text": "..."})

The thing candidates miss: the resulting chain object isn't just a convenience wrapper, it's itself a full Runnable, meaning it gets .invoke(), .stream(), .batch(), and async equivalents for free, without you writing any of that plumbing yourself.

RunnableLambda wraps a plain Python function so it can slot into an LCEL chain like any other Runnable. It's the escape hatch for custom logic, reshaping a dict between two steps, calling an external API that isn't itself a LangChain integration, running validation, that doesn't fit neatly into the built-in prompt, model, and parser abstractions.

python
from langchain_core.runnables import RunnableLambda

def extract_first_line(text: str) -> str:
  return text.strip().split("n")[0]

chain = prompt | model | StrOutputParser() | RunnableLambda(extract_first_line)

An f-string interpolates values into a flat string. ChatPromptTemplate produces a list of role-tagged messages, system, human, AI, which is what chat models actually expect as input rather than a single blob of text. It also validates that the variables you declared are the ones you pass in, and it composes cleanly with LCEL since it's itself a Runnable.

python
from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages([
  ("system", "You are a helpful assistant that answers in {language}."),
  ("human", "{question}"),
])
messages = prompt.invoke({"language": "French", "question": "What is LCEL?"})

It's a runtime error, raised the moment you call .invoke() or .format() on the template, not something caught at import time or when the template is first defined. ChatPromptTemplate validates that every variable named in the template string is present in whatever dict you pass in, and raises a clear KeyError-style message naming the missing variable if it isn't.

That's a genuine improvement over building prompts with raw string formatting, where a missing key either silently produces a malformed prompt or fails with a much less informative error deep inside string interpolation.

A document loader pulls raw content from a source, a PDF, a Notion page, a Slack export, a website, and normalizes it into LangChain's Document object, plain text content plus a metadata dict. The sheer number of loaders exists because every source has a different native format and API, and normalizing all of them into one shape is exactly the point: downstream code, splitters, embedders, retrievers, only ever has to deal with one interface regardless of where the content came from.

An embedding is a fixed-length vector of numbers produced by a model trained so that texts with similar meaning end up close together in that vector space, and texts with different meaning end up far apart, measured by something like cosine similarity or dot product. It's not keyword matching, "car" and "automobile" can embed close together even though they don't share a single character, because the model learned they're used in similar contexts.

That's the whole mechanism behind semantic search: embed the query the same way you embedded your documents, then find the document vectors nearest to the query vector. No exact word overlap required.

A vector store is the storage and indexing layer, it holds the embeddings and knows how to run a similarity search against them. A retriever is a thinner interface on top of that, or of another search mechanism entirely, that standardizes how anything upstream, a chain, an agent, asks for relevant documents given a query, without needing to know the specifics of the store underneath.

python
retriever = vectorstore.as_retriever()
docs = retriever.invoke("What's the return policy?")

The distinction matters because not every retriever is backed by a vector store at all, a BM25 keyword retriever or a hybrid retriever combining keyword and semantic search both implement the same retriever interface without necessarily wrapping a vector store underneath.

A chain executes a fixed sequence of steps you defined at build time. An agent uses the model itself to decide, at run time, what to do next, whether to call a tool, which tool, with what arguments, and when it has enough information to produce a final answer. The control flow lives inside the model's reasoning rather than in code you wrote in advance.

The official framing is direct about this: "agent = model + harness," the harness being the prompt, the available tools, and any middleware shaping behavior around the model's decisions (LangChain documentation, overview). It's a genuinely different design than hardcoding the branches yourself, and it trades predictability for the ability to handle inputs you didn't specifically plan for.

The simplest path is the @tool decorator on a plain Python function. The function's docstring becomes the tool's description and type hints become its input schema, both of which get sent to the model as part of the tool's definition, so the model is literally reading your docstring to decide when the tool is relevant.

python
from langchain_core.tools import tool

@tool
def get_order_status(order_id: str) -> str:
  """Look up the current shipping status for a given order ID."""
  return lookup_order(order_id)

A vague docstring is a direct cause of a model calling the wrong tool or the right tool with garbage arguments, this isn't a minor style nit, the docstring is functionally part of the tool's contract with the model.

Yes. A tool can return a string for a straightforward human-readable result, a structured object when downstream code or the model needs to parse specific fields out of the result rather than re-extracting them from prose, or a Command object when the tool needs to directly update the agent's graph state rather than just returning a value the model reads and reasons about.

Returning a structured object instead of a formatted string is worth it whenever the result feeds into more logic afterward, a price lookup tool that needs to trigger a follow-up calculation is more reliable returning a number the code can use directly than a string the model has to re-parse first.

Tracing, either through LangSmith or a comparable tracing setup, records every step of a run: each model call with its exact prompt and response, each tool call with its arguments and result, and the timing of each. Without it you're debugging from the final output backward, guessing at which of several steps produced a wrong answer.

Enabling tracing during development is close to free, and having it available before the first production incident, rather than adding it only after you're already trying to explain why an agent gave a wrong answer, is the difference between debugging in minutes and debugging blind.

Medium questions

20

langchain-core holds the base abstractions, runnables, messages, output parsers, with almost no dependencies. langchain holds higher-level chains and agent constructors like create_agent. Provider integrations, OpenAI, Anthropic, Pinecone, and so on, live in separate partner packages so you only install what you actually use instead of pulling in every vendor SDK LangChain has ever supported.

It matters for two practical reasons. First, dependency bloat and version conflicts used to be a real complaint before the split, installing langchain pulled in things you'd never touch. Second, langchain-core being lightweight and stable is what lets LangGraph and other tools build directly on the same Runnable interface without depending on the heavier package.

They're not competitors, they sit at different layers. LangChain's agent APIs, create_agent specifically, are described in the official docs as being "built on top of LangGraph", which handles the actual execution runtime: durable state, checkpointing, and resuming a run after a crash or a human-in-the-loop pause. LangChain gives you the higher-level agent loop and integrations, LangGraph gives you the lower-level graph of nodes and edges that the loop actually runs on.

The practical implication: if you outgrow what create_agent gives you out of the box, custom control flow, branching based on state, multiple cooperating agents, you don't reach for a different framework, you drop down to LangGraph directly and build the graph by hand, reusing the same LangChain model and tool objects.

RunnableParallel runs multiple Runnables concurrently against the same input and returns a dict keyed by whatever names you give each branch, so independent branches actually execute at the same time instead of one waiting on the other to finish.

python
from langchain_core.runnables import RunnableParallel

chain = RunnableParallel(
  summary=prompt_summary | model | parser,
  sentiment=prompt_sentiment | model | parser,
)
result = chain.invoke({"text": "..."})
# result: {"summary": "...", "sentiment": "..."}

Calling two chains sequentially, one .invoke() after another, works but pays the latency of both calls back to back. When the branches don't depend on each other's output, and both hit the same model API, RunnableParallel is a straightforward latency win with no logic change required.

.batch() sends multiple inputs through a Runnable with the underlying calls parallelized where the implementation supports it, most model integrations batch API calls concurrently up to a configurable concurrency limit, instead of the strictly sequential wait-for-one-then-start-the-next pattern a plain loop of .invoke() calls produces.

python
questions = ["What is LCEL?", "What is a retriever?", "What is a checkpoint?"]
answers = chain.batch(questions) # runs concurrently, not one at a time

For anything processing more than a handful of independent inputs against the same chain, batch is close to a free latency win, the only real cost is being mindful of provider rate limits when concurrency is high.

A model that isn't specifically instructed and constrained will happily wrap valid JSON in a markdown code fence, add a sentence of explanation before it, or produce almost-valid JSON with a trailing comma. Asking nicely in the prompt reduces how often this happens, it doesn't eliminate it, because you're still relying on the model to follow an instruction rather than enforcing a structure.

The more reliable fix is using structured output support baked into the model call itself, most current chat model integrations expose a .with_structured_output(SomeSchema) method that constrains generation to match a schema at the API level rather than parsing free text after the fact. Falling back to a parser with retry logic on top is the option when the provider doesn't support structured output natively.

with_structured_output(Schema) tells the underlying provider API to constrain or validate its own generation against a schema, so the model itself is producing conformant output, not just text you hope matches a shape. Manual parsing means the model produces free text and your code afterward tries to extract structure from it, which is strictly more fragile because nothing forced the model to comply in the first place.

python
from pydantic import BaseModel

class Answer(BaseModel):
  summary: str
  confidence: float

structured_model = model.with_structured_output(Answer)
result = structured_model.invoke("Summarize this ticket and rate your confidence.")
# result is an Answer instance, not a raw string

Few-shot examples give the model concrete input-output pairs to pattern-match against, which is generally more reliable than describing the desired format in prose alone, models are better at imitation than at following abstract instructions precisely. FewShotPromptTemplate lets you template a set of examples the same way you'd template the main prompt.

They stop helping, and start hurting, once you're burning enough tokens on examples that you're eating into the context budget you need for the actual task input, or once the examples are similar enough to each other that the model starts overfitting to their specific phrasing instead of generalizing the pattern. Past a handful of examples, the marginal benefit drops fast for most tasks.

Chunk size sets the tradeoff between precision and context. Small chunks retrieve more precisely, the embedding represents a narrower, more specific idea, but a small chunk on its own might not carry enough surrounding context for the model to actually use it. Large chunks carry more context but dilute the embedding, a chunk covering three unrelated topics produces a vector that's a mediocre match for any single query about one of those topics.

Overlap exists to stop a sentence that's meaningfully split by a hard chunk boundary from losing its context in either half. Too little overlap and you get chunks that end mid-thought; too much and you're storing and searching over redundant text, which bloats your index and can surface near-duplicate chunks in a single retrieval.

A fixed character or token count splitter is fast and predictable but cuts wherever the count runs out, mid-sentence, mid-code-block, with no regard for the document's actual structure. A recursive splitter tries a hierarchy of separators, double newline, then single newline, then sentence, then word, falling back only when a chunk still doesn't fit, so it tends to break at more natural boundaries first.

For structured content, markdown, code, HTML, a splitter that's aware of that structure, respecting headers or function boundaries, beats both, because it keeps semantically related content together instead of relying on whitespace patterns to approximate structure. The cost is it's slower to configure correctly and needs a splitter matched to the actual document type rather than one generic setting for everything.

Semantic search finds chunks whose embeddings are closest to the query's embedding, based purely on meaning. Metadata filtering restricts the candidate set by exact attributes attached to each document at load time, source, date, author, document type, before or alongside the similarity search. They solve different problems: semantic search alone can't reliably restrict results to "only documents from this quarter" or "only the HR handbook, not the engineering wiki," because those attributes aren't represented in the meaning of the text itself.

python
retriever = vectorstore.as_retriever(
  search_kwargs={"k": 5, "filter": {"source": "hr_handbook", "year": 2026}}
)

Loaders that carry rich, accurate metadata through the whole pipeline, chunking, embedding, storage, are what make this filtering possible later. Skip populating metadata at load time and you lose the ability to filter cleanly at query time, no matter how good the embedding model is.

Plain similarity search returns the k nearest vectors to the query, full stop, which means if your top five chunks all happen to say almost the same thing, you get five near-duplicates instead of five distinct pieces of context. MMR re-ranks candidates to balance relevance to the query against diversity from what's already been selected, penalizing a candidate that's too similar to a result already chosen.

python
retriever = vectorstore.as_retriever(
  search_type="mmr",
  search_kwargs={"k": 5, "fetch_k": 20, "lambda_mult": 0.5},
)

MMR is worth the extra compute when your source documents have a lot of near-duplicate or heavily overlapping content, a knowledge base with several versions of similar FAQ articles, for instance, where plain similarity search would happily return five copies of the same answer.

The retriever fetches relevant chunks for the incoming question, those chunks get formatted into the prompt alongside the question itself, and the model generates an answer conditioned on both.

python
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser

def format_docs(docs):
  return "nn".join(d.page_content for d in docs)

rag_chain = (
  {"context": retriever | format_docs, "question": RunnablePassthrough()}
  | prompt
  | model
  | StrOutputParser()
)
answer = rag_chain.invoke("What does the refund policy say about digital goods?")

RunnablePassthrough() is doing the unglamorous but important job here: it passes the raw question straight through into the prompt template's question slot while the retriever branch independently turns that same question into formatted context, both landing in the same dict the prompt template expects.

2-step RAG retrieves once, then generates, a fixed pipeline with no decision-making in between, which makes it predictable and cheap but unable to adapt when a single retrieval pass doesn't surface the right context. Agentic RAG hands the decision of when and how to retrieve to the model itself, it can decide to retrieve, look at what came back, and decide to retrieve again with a refined query before answering, which the official docs frame as trading predictability for flexibility on genuinely complex queries.

Hybrid RAG sits between the two, retrieving, then validating or critiquing what came back, and iterating if the retrieved context doesn't look sufficient, without handing the model full open-ended control over the whole loop (LangChain documentation, retrieval). Picking between them is a real design decision, not just a knob, a straightforward FAQ bot rarely needs agentic RAG's overhead, but a research assistant answering open-ended questions across a large, messy corpus usually needs more than one fixed retrieval pass.

More chunks means more tokens the model has to weigh against each other, and past a certain point that dilutes attention rather than adding useful signal, especially if several of the extra chunks are only tangentially relevant. There's also a well-documented tendency for models to pay less attention to information buried in the middle of a long context compared to the beginning or end, so padding the prompt with marginal chunks can bury the one genuinely relevant chunk exactly where the model is least likely to weight it heavily.

The fix usually isn't retrieving fewer chunks blindly, it's retrieving better ones, tuning k, using MMR to cut near-duplicates, or re-ranking the retrieved set before it ever reaches the prompt, so the chunks that do make it in are the ones actually worth the model's attention.

create_agent is a minimal, configurable constructor for the common agent shape, model plus tools plus a system prompt, that returns something ready to run without you hand-building the graph nodes for the model call, the tool-calling decision, and the loop back to the model with tool results. It leans on LangGraph underneath for the actual execution and durability, you get that for free rather than reimplementing it.

You'd drop to raw LangGraph directly when your control flow doesn't fit that shape, multiple agents that need to hand off to each other, custom branching based on intermediate state, a human approval step injected mid-run. create_agent is the fast path for the common case, not a ceiling, since the same LangGraph runtime is right there underneath it when you outgrow the defaults.

Middleware wraps around the agent's core loop, letting you inject logic before or after the model runs without rewriting the loop itself, things like logging every step, rewriting or trimming the prompt before it reaches the model, or intercepting a tool call for approval before it executes. It's the mechanism that keeps create_agent simple by default while still leaving room to bolt on production concerns without forking the agent implementation.

A realistic use: adding a piece of middleware that summarizes older messages once the conversation crosses a token threshold, so the agent's context doesn't grow unbounded, without touching the core model-plus-tools loop at all.

The older memory classes bolted conversational state onto a chain as a separate object you had to manage and pass around yourself, which worked for a simple back-and-forth chat but didn't generalize to more complex agent state, tool call history, intermediate reasoning, multi-turn tool results, or to needs like pausing an agent mid-run and resuming it later. LangGraph's checkpointing treats state as a first-class part of the graph itself, automatically persisted at every step, so conversation history is just one field in a broader state object that can also hold whatever else the run needs to track.

Practically, this means state, and the ability to resume a run after an interruption, comes for free with the graph's execution model rather than being a separate memory object you wire in and maintain by hand.

Short-term memory is the state of the current run, the conversation so far, intermediate tool results, everything relevant to answering the question in front of you right now, and it typically disappears once the run ends unless you explicitly persist it. Long-term memory persists across separate runs and separate sessions entirely, facts about a specific user's preferences, a summary of a decision made in a conversation from last week, something that should still be available the next time that user starts a brand new session.

The distinction matters because they need different storage and retrieval strategies. Short-term state lives naturally in the graph's checkpointed state for that run. Long-term memory needs its own store, often something that looks a lot like the retrieval stack, embed and search over past interactions, because you generally don't want to stuff someone's entire interaction history into every prompt going forward.

Every model call in the chain is a network round trip plus generation time, and an agent that calls the model, calls a tool, then calls the model again to interpret the tool's result is paying for at least two full model calls per user-facing answer, sometimes several more if it takes multiple tool-calling turns to settle on a final answer. Retrieval adds its own latency too, embedding the query and searching the vector store, though that's usually smaller than a model call unless the index is enormous or poorly tuned.

The biggest lever is almost always reducing the number of sequential model calls on the critical path, using RunnableParallel for independent branches, choosing a smaller or faster model for steps that don't need frontier reasoning, and caching retrieval or even full responses for genuinely repeated queries. Streaming doesn't reduce total latency, it improves perceived latency by getting the first tokens to the user sooner, which is a real win but a different lever than actually cutting total time.

Conversation history grows every turn, and if it's fed back into the model unpruned, later turns in a long conversation pay for re-sending the entire history as input tokens on every single call, which scales quadratically with conversation length rather than staying flat. Agentic tool-calling loops compound this: each round trip through the model, tool, model cycle resends the growing history plus tool results, so a multi-step agent run on a long conversation can burn through tokens far faster than the same number of turns in a simple, memoryless chain.

A demo rarely surfaces this because demo conversations are short and get restarted often. Production conversations run long, and without some form of summarization or windowing of older history, the cost curve that looked flat in testing turns out to have been hiding its actual shape the whole time.

Hard questions

12

A model called directly streams tokens as soon as it produces them, that part doesn't change. What changes inside a chain is what happens to those tokens on the way out. If the next step in the chain is something like StrOutputParser, it can pass tokens through as they arrive. But if a step needs to see the whole output before it can do anything, a step that parses structured JSON out of the response, for example, LCEL has to buffer until that step's input is complete, which collapses the streaming back down to a single chunk at that point in the pipeline.

The practical lesson: streaming isn't a property of the model, it's a property of every step in the chain, and the first non-streaming-friendly step becomes the bottleneck for anything downstream of it, even if everything before it streamed token by token.

Different vector stores default to different distance metrics, cosine, dot product, Euclidean, and if a store's default doesn't match the metric the embedding model was actually trained and evaluated with, similarity rankings shift even though the raw vectors are identical. Index type matters too: an approximate nearest neighbor index (HNSW is common) trades a little recall for speed, and its recall depends on tunable parameters that differ store to store, so migrating from one store's default settings to another's can quietly change what counts as a top match.

The failure mode is nasty precisely because nothing throws an error, the system keeps returning results, just slightly worse ones, and that's easy to miss without an eval set that measures retrieval quality directly instead of just watching for outright failures.

Nothing in the retrieval step catches it. A retriever's only job is finding chunks that are semantically close to the query, it has no concept of factual consistency between the chunks it returns, so both contradictory chunks get handed to the model as equally valid context. What the model does next depends entirely on the model and the prompt, sometimes it picks one and ignores the other, sometimes it hedges and mentions both, sometimes it blends them into something that's wrong in a new way.

Catching this is a data and pipeline problem, not a retrieval problem: deduplicating and versioning your source documents before they're ever chunked and embedded, so contradictory or stale copies never make it into the index in the first place. By the time you're debugging it at the retrieval layer, the actual fix is usually further upstream.

Most hosted embedding models aren't guaranteed to be perfectly deterministic across API calls, minor floating-point differences from batching, hardware, or backend versioning can produce a vector that's extremely close to, but not bit-for-bit identical to, a previous embedding of the same text. For similarity search this rarely matters in practice, since cosine similarity barely moves for such tiny numeric noise.

It matters more if you're doing exact-match deduplication by comparing embeddings directly rather than by comparing the source text, or if the provider silently updates the underlying embedding model version, which can shift vectors by more than noise and effectively require re-indexing everything to keep the whole index internally consistent.

A few likely culprits, and a candidate who names more than one is showing real production experience. First, embeddings are static once generated, if you update the source document but don't re-embed and re-index it, the vector store is still serving the old chunk's embedding pointing at old text. Second, caching anywhere in the pipeline, at the retriever, at the model call layer, can serve a stale answer for a query it's seen before. Third, and easy to miss, if the update added a new document but an old, now-superseded document with similar content wasn't removed, both can still get retrieved and the model has no inherent way to know which one is current unless timestamps or versioning are part of the metadata and the prompt actually surfaces that.

The fix is boring but necessary: treat re-indexing as part of your deployment or data pipeline, not a background process you fire and forget, and put recency metadata directly in front of the model when staleness genuinely matters for the answer.

Start with the tool's description and name. If the docstring is vague, or the tool's name doesn't obviously relate to the kind of question being asked, the model may simply never recognize it as relevant, tool selection runs entirely on what the model reads about the tool, not on what the tool actually does internally. Next, check whether the system prompt is quietly discouraging tool use, an overly cautious prompt that says something like "only use tools when absolutely necessary" gives the model an easy excuse to answer from its own knowledge instead.

Also worth checking: whether the model provider actually supports tool calling in the mode you're using, and whether you're seeing the same failure with a completely different, more capable model. If a smaller or older model consistently skips tool calls that a stronger model in the same setup handles fine, that's a strong signal the model's tool-calling reliability, not your tool definition, is the actual bottleneck.

A few layers, and a strong answer stacks more than one instead of picking a single silver bullet. Set an explicit recursion or step limit on the agent run so a genuinely stuck loop terminates instead of burning tokens indefinitely. Make tool errors legible to the model, return a clear error message as the tool's result rather than letting an exception propagate silently, so the model actually has the information it needs to try something different on the next step instead of repeating the same failing call blind.

Beyond that, middleware or a wrapping check that tracks whether the same tool has been called with the same arguments more than once or twice in a run is a reasonable circuit breaker, forcing a different path, asking the user for clarification, giving up gracefully, once repetition crosses a threshold.

The model decides when to call a tool based on its own reasoning about the conversation, which means a tool capable of deleting data, sending an email, or executing arbitrary code will eventually get called in a situation you didn't anticipate, a misread instruction, an adversarial prompt injected through retrieved content, or just an edge case the model handles differently than expected. Trusting the model's judgment as the only safeguard on a destructive action is a real production risk, not a hypothetical one.

Mitigation stacks a few things: scope tools as narrowly as possible instead of one broad "run any shell command" tool, add a human-in-the-loop approval step before anything destructive actually executes, LangGraph's interrupt mechanism is built for exactly this, and validate or sandbox tool inputs server-side rather than trusting whatever arguments the model generated. The model choosing to call the tool should never be the only gate between a request and an irreversible action.

The most likely break is silent, not an error: conversation history that used to accumulate automatically inside the memory object stops being wired into the prompt at all, because the prompt template was reading from a memory object's specific interface that no longer exists in the new setup. The agent runs, answers every message, and just has no idea what was said three turns ago, which is a much harder bug to notice than a crash, since nothing throws until a user notices the agent has apparently forgotten the conversation.

The actual fix is making sure the graph's state schema explicitly includes a messages field, and that the node responsible for calling the model is reading from that state and appending to it, rather than assuming persistence is automatic just because you're now using LangGraph.

Evaluating the final answer alone conflates two failure modes that need different fixes: the model can write a fluent, confident-sounding answer from bad or irrelevant context just as easily as from good context, so a plausible-sounding output tells you almost nothing about whether retrieval actually worked. Separating retrieval evaluation from generation evaluation means building a small set of questions with known correct source documents, then checking whether the retriever actually surfaces those documents, hit rate or ranking position, independent of what the model does with them afterward.

Only once retrieval quality is verified on its own does it make sense to evaluate the end-to-end answer for correctness and grounding, whether the generated answer actually reflects what the retrieved context said rather than the model's own prior knowledge filling gaps. Skipping straight to end-to-end evaluation is the most common mistake teams make, and it hides exactly which half of the pipeline is actually broken when quality drops.

Check temperature and other sampling settings first, most chat models default to a nonzero temperature, so identical prompts against the same model can legitimately produce different text every single call, that's expected behavior, not a bug. Next, check for silent prompt drift, one teammate on a slightly different version of a shared prompt template, or a different model version string, "latest" tags on hosted models can point to a genuinely different underlying model over time even with no code change on your end.

After that, look at retrieval, if the chain includes a retriever and the underlying index was updated between the two runs, different chunks getting retrieved is a real and common source of different downstream answers. Only after ruling out those three would non-determinism in the model provider's own inference itself, which does exist even at temperature zero for some providers, be the most likely remaining explanation.

The framework's own docs describe deep agents as a "batteries-included" option layered on top of the same underlying agent primitives, adding things like automatic context compression when a run's history grows large and the ability to spawn subagents for a portion of a task rather than handling everything in one long-running loop. A standard create_agent setup gives you the minimal model-plus-tools-plus-prompt shape and leaves those extras out unless you build them yourself.

This comes up when an interviewer wants to know if you can reason about tradeoffs rather than just naming APIs, deep agents save real engineering effort for long, complex, multi-part tasks, but that convenience comes with more moving parts and less direct control than composing a simpler agent by hand, which is often the right call for a narrowly scoped task that doesn't need any of it.

How to prepare for a LangChain interview

Skip re-reading the getting-started tutorial for the fourth time. Build one small thing that forces you past the happy path: a RAG chain over a handful of your own documents, wired with LCEL, that also has to handle a query where the retrieved chunks contradict each other, and watch what the model actually does with that. Then take that same setup and rebuild the agent portion with create_agent instead of hand-rolling the loop, so you can speak to both the high-level API and what it's actually doing underneath with LangGraph.

In mock interviews run through LastRoundAI for AI engineering roles, the memory and state question trips up more candidates than the LCEL question does, mostly because candidates who learned LangChain a year or two ago built a mental model around the old memory classes and never revisited it once the docs moved on. We don't have a precise number to put on that gap, it's a pattern we see often enough in review to flag here, not something we've measured to a specific percentage.

One more thing worth internalizing before the interview: LangChain in 2026 is really three things stacked on each other, langchain-core's Runnable interface, LangGraph's execution and persistence layer underneath, and langchain's higher-level constructors like create_agent on top. An interviewer who's kept current will notice quickly if your answers only describe the top layer and treat the other two as implementation detail you've never actually looked at.

Get the reps in before the real thing

Explaining how a RAG chain is supposed to work on paper is not the same as defending a design choice out loud when an interviewer asks what happens if two retrieved chunks disagree. LastRoundAI's mock interview mode runs live technical rounds with real-time follow-up questions in your browser, and the free plan includes 15 credits a month that reset monthly rather than piling up unused. Starter is $19/mo if a handful of sessions isn't enough runway.

Once your answers hold up under a follow-up, the slower part of the job hunt is usually just getting in front of enough AI engineering roles that actually test LangChain instead of listing it as a buzzword in the job description. Auto-Apply queues tailored applications for your review, 10 a month on the free plan, up to 400 a month on the Ultimate plan, and nothing goes out until you approve it.

Questions about either product go to contact@lastroundai.com. That's the only inbox we check.

How this list was built

Worth being straight about where these questions come from, because plenty of pages in this category are not. The set was compiled from a research pass across official documentation, vendor release notes, published engineering writing and public discussion of hiring processes, then cross-checked against the current version of each technology so nothing here describes behaviour that has since changed.

What that means in practice: these are the questions the material supports as reasonable and current for this role, not a transcript of any one company's loop. We have not sat in on your interview and we are not going to claim we have. Treat the list as well-sourced preparation rather than a leaked question bank, and expect your panel to phrase things their own way.

If you spot something out of date, tell us at contact@lastroundai.com and we will fix it.

Frequently asked questions

Is LangChain still worth learning in 2026?

For interview purposes the question is really whether the teams you are targeting use it, which is worth checking against their actual job postings rather than general popularity rankings. Where it is in use it tends to be deeply embedded and slow to replace.

Should I memorise LangChain syntax for the interview?

Rarely worth it. Most interviewers care that you know what to reach for and why, and will not fail you for forgetting an exact flag. Being confidently wrong about behaviour costs far more than admitting you would check the documentation.

What is the most common mistake in LangChain interviews?

Answering the question that was asked and stopping there. The strongest candidates add the trade-off or the failure mode without being prompted, which is what signals real use rather than revision.

How long does it take to prepare for a LangChain interview?

If you already work with LangChain day to day, a focused week on the areas you avoid in practice is usually enough. Coming in cold, expect three to four weeks. The gap is rarely knowledge; it is being able to explain something you normally just use.

What LangChain topics come up most often?

Interviewers concentrate on the parts that cause production incidents rather than the parts that are pleasant to learn. Expect the fundamentals to be assumed and the follow-up questions to sit one layer below what a tutorial covers.

Leave a Reply

Your email address will not be published. Required fields are marked *