LangChain composes LLM calls, tools and retrievers into pipelines that run from start to finish. LangGraph models your application as a state machine — explicit nodes, edges and shared state — so it can branch, loop, pause for a human and resume.
Use LangChain for linear work: a RAG pipeline, a summarisation chain, a single tool-calling step. Use LangGraph the moment the system needs to decide what to do next based on what just happened. They are built by the same team and compose together — this is not an either/or.
What LangChain is good at
LangChain is a component library plus a composition syntax. Its real value is the breadth of integrations — dozens of model providers, vector stores, document loaders and tools behind consistent interfaces — and the ability to wire them together compactly.
# a RAG chain: retrieve, format, generate, parse
chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
chain.invoke("What is the refund policy?")
That runs left to right, once. For a large share of real applications — search over documents, classify then route, summarise then extract — that is exactly the right shape, and reaching for anything heavier is over-engineering.
Where it gets uncomfortable is control flow. Expressing "retry with a rewritten query if retrieval returned nothing relevant, but give up after three attempts" inside a chain means conditionals scattered through composition syntax, and it becomes difficult to see what the program actually does.
What LangGraph adds
LangGraph reframes the application as a graph. You define a state object, functions that read and update it (nodes), and rules for which node runs next (edges). Control flow becomes data you can inspect rather than logic buried in composition.
class State(TypedDict):
question: str
docs: list
attempts: int
answer: str
graph = StateGraph(State)
graph.add_node("retrieve", retrieve)
graph.add_node("grade", grade_relevance)
graph.add_node("rewrite", rewrite_query)
graph.add_node("generate", generate)
graph.add_edge("retrieve", "grade")
graph.add_conditional_edges("grade", route, {
"good": "generate",
"retry": "rewrite", # cycles back to retrieve
"give_up": END,
})
graph.add_edge("rewrite", "retrieve")
Four capabilities follow from that structure, and they are the reason production agent work has moved this way:
- Cycles with termination control. An agent can retry, but you decide the bound. Unbounded loops are the classic way an agent burns a four-figure API bill overnight.
- Persistence. A checkpointer saves state after every node, so a run can survive a crash, resume tomorrow, or be replayed for debugging.
- Human in the loop. Interrupt before a node, surface the proposed action for approval, resume on response. Essential for anything that spends money or writes to production.
- Multi-agent structure. A supervisor node routing between specialised agents is just a graph with conditional edges — no special framework needed.
Side by side
| LangChain | LangGraph | |
|---|---|---|
| Shape | Pipeline, runs once through | State machine with explicit transitions |
| Branching | Awkward — conditionals inside composition | First-class conditional edges |
| Loops | Not really supported | Core feature, with bounds |
| State | Passed along the chain | Explicit, typed, shared, inspectable |
| Pause & resume | No | Yes, via checkpointing |
| Human approval | Hand-rolled | Built in — interrupt before a node |
| Debugging | Trace the chain | Inspect state at each node; replay from any point |
| Best for | RAG, summarisation, classification, single tool calls | Agents, multi-step workflows, multi-agent systems |
| Overhead | Low | More setup — worth it once control flow is real |
Why AgentExecutor gave way to graphs
LangChain's original agent abstraction, AgentExecutor, ran a hidden loop: call the model, parse a tool request, run the tool, feed the result back, repeat until it decided to stop. It was elegant to start with and painful in production, for one reason — the control flow was inside the framework, not in your code.
When an agent looped twelve times, called the wrong tool, or stopped early, you could observe that it happened but not easily change how the decision was made. There was no clean way to insert an approval step, bound a specific branch, or resume a failed run partway through.
LangGraph's answer is to make the loop yours. The cost is more code up front; the benefit is that every failure has a location. In a system where an LLM decides what happens next, that is not a nicety — it is the difference between something you can operate and something you can only restart.
The practical tell. If you cannot draw your application's control flow on a whiteboard, you should not be running it in production. LangGraph forces you to draw it before it will run at all — which is a feature disguised as friction.
When to use neither
Worth saying plainly, because framework enthusiasm costs teams real time: if your application is one LLM call, or a fixed two-step sequence with no branching, use the provider SDK directly.
# this needs no framework
response = client.messages.create(
model="claude-opus-5",
messages=[{"role": "user", "content": prompt}],
)
Fewer dependencies, no abstraction to learn, and a stack trace that points at your own code. Frameworks earn their overhead when there is genuine complexity to manage — many integrations, real control flow, state that must survive a restart. Below that threshold they add indirection and nothing else.
What to learn, in what order
- Raw API calls first. Understand messages, tokens, temperature and tool schemas without a framework in the way. Everything above this is a convenience layer over it.
- Retrieval and RAG. Embeddings, chunking, vector stores, evaluation. See RAG vs fine-tuning for where each belongs.
- LangChain. Components and composition. Build a real RAG pipeline with it.
- LangGraph. State, nodes, conditional edges, checkpointing, human-in-the-loop. Build something that loops and can be interrupted.
- Tracing and evaluation. LangSmith. Agents fail in ways single calls do not, and without tracing you are guessing.
That ordering is deliberate. Learning LangGraph before understanding retrieval and evaluation produces someone who can assemble a graph and cannot tell why it returns poor answers — because the problem is usually two layers below the graph. It is the sequence our Agentic AI course follows, after Generative AI and RAG are already solid.
Frequently asked questions
What is the difference between LangChain and LangGraph?
LangChain composes LLM calls, tools and retrievers into pipelines that run start to finish. LangGraph models an application as a state machine — explicit nodes, edges and shared state — which lets it branch, loop, pause and resume. LangChain suits linear workflows; LangGraph suits anything that needs to make decisions about what to do next.
Is LangGraph replacing LangChain?
No. They are built by the same team and compose together — LangGraph nodes routinely call LangChain retrievers, tools and models. LangGraph replaces LangChain's old AgentExecutor for agent control flow, but not LangChain's component library.
Do I need LangChain to use LangGraph?
No. LangGraph works with plain provider SDK calls inside its nodes. Most teams use LangChain components because the retrievers and tool abstractions save time, but it is not a requirement.
When should I not use a framework at all?
When your application is a single LLM call, or a fixed two-step sequence with no branching. A direct call to the provider SDK is clearer, has fewer dependencies and is easier to debug. Frameworks earn their overhead when there is real control flow to manage.
Which should I learn first?
LangChain first, for components and the mental model of chaining. LangGraph immediately after, because production agent work is converging on it. Learning LangGraph without understanding retrieval, tools and evaluation produces systems you cannot debug when they loop or stall.