teachyou.ai academy
← All posts
LangChainLLM agentsconversational AIPythonLangGraph

LangChain Memory Types Compared

Pramod Dutta · Jul 2, 2026 · 12 min read

LangChain memory is the mechanism that lets a chatbot or agent remember what was said earlier in a conversation, and choosing the wrong type is one of the most common reasons a bot feels forgetful or burns through your token budget. There are half a dozen LangChain memory implementations, each trading off recall accuracy, latency, and cost differently, and none of them is a universal answer. This guide walks through every practical option, shows runnable code for each, and ends with the modern LangGraph-based approach that has replaced the original langchain.memory module in current versions of the framework.

Why LangChain Memory Exists in the First Place

A large language model call is stateless. Every request you send only "knows" what is inside that request's prompt. If you want a model to remember that the user said their name is Priya three turns ago, you have to put "the user's name is Priya" back into the prompt on every subsequent call. LangChain memory is the layer that automates this: it stores past turns (or a distilled version of them) and re-injects them into the prompt before the next call.

The trade-off is always the same triangle:

  • Fidelity: how much of the original conversation survives.
  • Token cost: how much of your context window and your bill gets eaten by history.
  • Latency: how much extra work (summarization calls, vector lookups) happens before the model can respond.

Every LangChain memory type sits at a different point on that triangle. Picking one without understanding the trade-off is how teams end up with agents that either forget critical details or cost ten times more than they should.

ConversationBufferMemory: The Simple Default

ConversationBufferMemory is the starting point most tutorials use. It keeps the entire conversation, verbatim, as a running string or list of messages, and hands it back on every call.

from langchain.memory import ConversationBufferMemory
from langchain_openai import ChatOpenAI
from langchain.chains import ConversationChain

llm = ChatOpenAI(model="gpt-4o-mini")
memory = ConversationBufferMemory()
conversation = ConversationChain(llm=llm, memory=memory)

conversation.predict(input="My name is Priya and I work in fintech.")
conversation.predict(input="What's my name?")

This works well for short-lived sessions: a support widget that resets every visit, a demo, a CLI tool used for a few minutes at a time. The problem shows up as the conversation grows. Buffer memory has no ceiling, so a two-hour conversation eventually blows past the model's context window, and every single call now pays for the full history in tokens, even if the user only cares about the last exchange.

Rule of thumb: use ConversationBufferMemory only when you can guarantee the conversation stays short, or when you are prototyping and correctness matters more than cost.

ConversationBufferWindowMemory: Bounding the Buffer

ConversationBufferWindowMemory fixes the unbounded growth problem by keeping only the last k exchanges and silently dropping everything older.

from langchain.memory import ConversationBufferWindowMemory

memory = ConversationBufferWindowMemory(k=5)

With k=5, the chain remembers the five most recent human/AI turn pairs and nothing before that. Token cost stays flat no matter how long the conversation runs, which makes this a safe default for chat UIs where users expect the bot to track the current thread but don't expect it to remember something from forty messages ago.

The failure mode is equally simple: anything outside the window is gone, permanently, with no summary or trace. If a user states a constraint early ("I'm allergic to shellfish") and the conversation runs long enough to push that message out of the window, the agent will cheerfully violate it later. Window memory is a good fit for task-focused bots (a coding assistant helping debug one file) and a poor fit for anything where an early detail needs to persist for the whole session (a customer support case, a medical intake flow).

ConversationSummaryMemory and Summary Buffer Hybrids

ConversationSummaryMemory takes a different approach: instead of storing raw turns, it asks the LLM to continuously rewrite a running summary of the conversation so far, and only that summary gets injected into the prompt.

from langchain.memory import ConversationSummaryMemory

memory = ConversationSummaryMemory(llm=llm)

This keeps token usage roughly constant regardless of conversation length, which solves the scaling problem that buffer memory has. The cost is an extra LLM call on every turn to regenerate the summary, which adds latency and a real dollar cost, and summarization is lossy: specific numbers, exact phrasing, and minor details tend to get smoothed away over multiple summarization passes.

ConversationSummaryBufferMemory splits the difference. It keeps a buffer of the most recent raw messages plus a running summary of everything older than that, with a token threshold that decides when to "roll" old messages into the summary.

from langchain.memory import ConversationSummaryBufferMemory

memory = ConversationSummaryBufferMemory(
    llm=llm,
    max_token_limit=500,
)

This is usually the better production choice over pure summary memory: recent context stays verbatim (so the model handles follow-up questions accurately), while older context gets compressed instead of dropped outright. The max_token_limit parameter is the knob you tune: lower it to save cost, raise it if you notice the agent losing recent details too quickly.

ConversationTokenBufferMemory: Budgeting by Tokens Directly

ConversationTokenBufferMemory is the buffer-window idea but measured in tokens instead of turn count, which is more precise when your real constraint is context window size or per-request cost.

from langchain.memory import ConversationTokenBufferMemory

memory = ConversationTokenBufferMemory(
    llm=llm,
    max_token_limit=1000,
)

A k=5 window can vary wildly in token count depending on how verbose each message is. Token buffer memory guarantees you never exceed a fixed token budget, which matters if you're running against a model with a smaller context window or you're optimizing for predictable per-call cost. Use this instead of window memory whenever you're tuning against a hard token ceiling rather than a "number of turns" intuition.

Entity Memory: Tracking Facts About People and Things

ConversationEntityMemory extracts and tracks structured facts about entities mentioned in the conversation (people, companies, projects) rather than storing the conversation itself.

from langchain.memory import ConversationEntityMemory
from langchain.memory.prompt import ENTITY_MEMORY_CONVERSATION_TEMPLATE

memory = ConversationEntityMemory(llm=llm)

Under the hood it runs an extraction prompt after each turn, pulling out entity names and updating a small knowledge store per entity ("Priya: works in fintech, prefers email over calls"). This is useful for CRM-style assistants or sales bots where the goal is less "remember the exact conversation" and more "remember what we know about this person," and it scales better than raw buffers because entity facts stay compact even as the conversation grows. It shares the summarization trade-off: an extra LLM call per turn, and it depends heavily on the extraction prompt actually catching the right entities, which is worth testing against your own transcripts before trusting it in production.

Vector Store Memory for Semantic Recall

None of the memory types above solve the "remember something from three weeks ago, in a different session" problem, because they're all scoped to a single conversation buffer. For that you need VectorStoreRetrieverMemory, which stores every past exchange as an embedding and retrieves only the semantically relevant ones at query time, regardless of when they happened.

from langchain.memory import VectorStoreRetrieverMemory
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma

embeddings = OpenAIEmbeddings()
vectorstore = Chroma(embedding_function=embeddings, collection_name="chat_memory")
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
memory = VectorStoreRetrieverMemory(retriever=retriever)

memory.save_context(
    {"input": "My team uses Postgres and Redis for the main stack."},
    {"output": "Got it, noted."},
)

This is what you reach for when you want long-term memory across sessions: a coding assistant that remembers your project's stack from a conversation last month, a support bot that recalls a customer's prior tickets. The trade-off is that retrieval is approximate. It returns the k most semantically similar past exchanges, not necessarily the most important ones, so a critical fact phrased unusually might not surface when you need it. It also needs a vector store running (Chroma, Pinecone, Weaviate, pgvector) which is infrastructure the simpler memory types don't require.

LangGraph Persistence: The Modern Replacement for LangChain Memory

The classes above (ConversationBufferMemory and its siblings from langchain.memory) are the legacy API. LangChain's own migration guidance has been steering people toward LangGraph's checkpointer-based persistence for anything built as an agent or graph, because it decouples "how state is stored" from "how state is structured," and it composes with tool calls, branching, and multi-agent setups in a way the old memory classes never did.

The core idea: a LangGraph graph has a state object, and a checkpointer persists that state after every step, keyed by a thread ID. Memory becomes "give me the state for thread X," not a special-cased memory class.

from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import StateGraph, MessagesState, START
from langchain_openai import ChatOpenAI

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

def call_model(state: MessagesState):
    response = llm.invoke(state["messages"])
    return {"messages": [response]}

graph = StateGraph(MessagesState)
graph.add_node("model", call_model)
graph.add_edge(START, "model")

checkpointer = InMemorySaver()
app = graph.compile(checkpointer=checkpointer)

config = {"configurable": {"thread_id": "user-priya-session-1"}}
app.invoke({"messages": [{"role": "user", "content": "My name is Priya."}]}, config)
app.invoke({"messages": [{"role": "user", "content": "What's my name?"}]}, config)

InMemorySaver is fine for local development, but it disappears when the process restarts. For anything real, swap in a persistent checkpointer:

from langgraph.checkpoint.postgres import PostgresSaver

with PostgresSaver.from_conn_string(DATABASE_URL) as checkpointer:
    checkpointer.setup()
    app = graph.compile(checkpointer=checkpointer)

There's also a Redis-backed checkpointer for teams that already run Redis for session state and want lower latency than Postgres round-trips. The pattern is identical: state persists per thread_id, and you get resumability, time travel (replaying from an earlier checkpoint), and multi-agent state sharing for free, none of which the old langchain.memory classes offered.

If you're starting a new project today, build on LangGraph persistence and skip the legacy memory classes entirely. If you're maintaining an existing chain built on ConversationBufferMemory or similar, it still works and there's no urgency to rip it out, but plan the migration when you next touch that code, because new LangChain features are being built against the graph/checkpointer model, not the old memory interface.

Choosing the Right LangChain Memory Type for Your Agent

Match the memory type to the actual failure mode you're trying to avoid, not to whichever one shows up first in the docs.

  • Short, single-session interactions where cost isn't a concern: ConversationBufferMemory, or just pass the raw message list in a LangGraph state.
  • Long-running chat UI where only recent context matters: ConversationBufferWindowMemory or ConversationTokenBufferMemory.
  • Long conversations where early context still matters but full verbatim storage is too expensive: ConversationSummaryBufferMemory.
  • CRM, sales, or support bots tracking facts about specific people or accounts: ConversationEntityMemory.
  • Cross-session recall, "remember this from last month": VectorStoreRetrieverMemory, or a LangGraph checkpointer plus a separate long-term memory store (LangGraph's Store API is built for exactly this, separate from per-thread checkpoints).
  • Anything involving multiple agents, tool calls, or branching logic: LangGraph persistence with a PostgresSaver or Redis checkpointer, full stop.

A practical pattern for production agents is to combine two layers: short-term memory via a LangGraph checkpointer scoped to the current thread, and long-term memory via a vector store or entity store that gets written to explicitly (not automatically) when the conversation reveals something worth remembering permanently, like a user preference or an account detail. Automatic long-term memory writes are a common source of noisy, low-quality recall, so gate them with an explicit "is this worth remembering" step, either a cheap classifier prompt or a rule (only persist facts the user stated directly, not inferred ones).

Common Mistakes When Implementing LangChain Memory

  • Using `ConversationBufferMemory` in production without a cap. It works fine in testing with a ten-turn conversation and then fails in production when a power user has a three-hundred-turn session. Set a window or token limit before shipping, not after a context-length error in the logs.
  • Summarizing on every single turn regardless of length. Running ConversationSummaryMemory's summarization call on a two-message conversation wastes a full LLM round-trip for no benefit. Gate summarization behind a token threshold, which is exactly what ConversationSummaryBufferMemory does.
  • Storing memory per-user instead of per-thread. If your memory key is the user ID and not a conversation or thread ID, two simultaneous conversations (a support ticket and a sales chat, say) bleed into each other. Always key memory by thread, and look up which threads belong to a user separately.
  • Treating vector store memory as authoritative. Semantic retrieval is probabilistic. If a fact absolutely must be available every time (a user's subscription tier, a compliance flag), store it as structured state, not as a retrievable memory that might not surface in the top-k results.
  • Forgetting to trim message history before it hits the model's tool-calling context. Agents that call tools tend to accumulate large tool outputs (search results, file contents) inside the message history. A window or token memory strategy sized for chat text alone will blow up once tool outputs start filling the same buffer. Budget separately for tool output size.

FAQ

What's the difference between LangChain memory and LangGraph persistence? LangChain memory (langchain.memory module: ConversationBufferMemory, ConversationSummaryMemory, and similar) is the original, chain-scoped API for storing conversation history. LangGraph persistence uses a checkpointer to save an entire graph's state, keyed by thread ID, and is the direction the framework has moved for anything built as an agent. New projects should default to LangGraph persistence; existing chains on the legacy memory classes don't need an urgent rewrite.

Which LangChain memory type is cheapest to run? ConversationBufferWindowMemory and ConversationTokenBufferMemory are the cheapest, because neither makes an extra LLM call to maintain the memory itself, they just truncate. Summary-based and entity-based memory both cost an additional LLM call per turn (or per rollover), which adds up on high-traffic bots.

Can I combine multiple LangChain memory types? Yes. A common combination is a window or token buffer for recent turns plus a vector store for long-term recall, so recent context stays exact while older, relevant facts can still surface through semantic search. ConversationSummaryBufferMemory is itself a built-in combination of a raw buffer and a summary.

Does LangChain memory work with streaming responses? Yes, memory classes and LangGraph checkpointers both operate on the final assembled message, not the token stream itself, so they're compatible with streaming output. You save context after the full response is generated, not mid-stream.

How do I persist LangChain memory across server restarts? Legacy memory classes need you to manually serialize their contents (most expose load_memory_variables and save_context, and you write the underlying dict to Redis, Postgres, or a file yourself). LangGraph checkpointers handle this natively: PostgresSaver or a Redis-backed checkpointer persist state per thread ID automatically, so a server restart just reconnects to the same thread and the conversation continues.

Is `ConversationSummaryMemory` accurate enough for compliance-sensitive conversations? No. Summarization is lossy by design, and an LLM summarizing its own conversation can drop or subtly reword details. For anything where exact wording matters (medical, legal, financial disclosures), keep the raw transcript in a separate durable log even if you're using summary memory to control prompt size for the model itself.