What do you like best about LangGraph?
I moved our agent work onto LangGraph because it stopped asking me to trust a loop I could not see.
The graph model is the part that changed how I write this kind of code. You declare a state schema, write nodes as ordinary functions that take state and return a partial update, and wire them together with edges. Conditional edges decide the next hop at runtime based on whatever is in state. It is a finite state machine with a model call sitting inside some of the nodes, and once that framing clicked for me, most of the guesswork went out of my agent code. I can open a graph definition six weeks after writing it and know exactly which paths a request can take, which is more than I could say for the agent executor pattern I was using before.
Checkpointing is the feature I would fight hardest to keep. State is persisted after each step against a thread_id, so a run that dies at the seventh node does not start over at the first. We have a document intake agent that processes batches overnight, and a provider timeout used to mean rerunning the whole thing and paying for every token a second time. Now I fix the node and resume the thread, and it picks up from where it stopped. The same mechanism gives you the ability to load an earlier checkpoint and fork from it, which is how I reproduce a bug against the exact state that caused it instead of trying to recreate the conditions by hand.
The interrupt primitive is what got our compliance reviewer to sign off.
Human-in-the-loop is not an afterthought bolted onto the side here. You call interrupt inside a node, execution pauses and persists, and the run sits there until something resumes it with a decision. For anything that writes to a customer-facing system, that pause is the difference between an agent we can deploy and a demo we keep in staging. The reviewer sees the proposed action, approves or edits it, and the graph continues from that point with the edited value in state. Building that flow on top of a framework that did not have durable pausing meant holding the whole run in memory and hoping the process survived, which is exactly the kind of thing that works until the day it does not.
Streaming is handled at the right level of granularity. I get token-by-token output for the parts users read, and separately I can subscribe to state updates as each node completes, which is what drives the progress indicators in our UI. Being able to show which step the agent is on, rather than a spinner and a promise, cut the number of "is it stuck?" messages from internal users to almost nothing.
Subgraphs are how we keep large workflows readable. A research agent, a drafting agent, and a validation agent each get their own compiled graph with their own state, and the parent graph treats them as nodes. Two benefits fall out of that:
- context stays scoped, so a long tool-calling sub-agent does not drag its entire message history into the parent's window
- a failure stays inside the subgraph where it happened, which makes it far quicker to localize
Memory is split in a way that matches how the work actually behaves. The checkpointer covers what happened inside a single thread, and there is a separate store for the things that should survive across conversations entirely, like a user's stated preferences or facts we learned in a previous session. Keeping those two apart meant I stopped bolting a side table onto the conversation history to hold anything durable. A node reads from the store, writes back to it, and the thread state stays about the run rather than about the user.
The pairing with LangSmith is close enough to be worth mentioning even though it is a separate product. Every node execution shows up as a span with the inputs and outputs it actually saw, and when a graph takes an unexpected branch I can see which conditional edge fired and what state it evaluated. Debugging a nondeterministic system without that trace view is not something I would want to go back to.
Two smaller things I appreciate. It is MIT licensed and free, so the orchestration layer we build on carries no vendor dependency, and nothing forces us onto the hosted platform if we would rather run our own infrastructure. And since the 1.0 releases, the higher-level agent constructor in LangChain runs on the same runtime, so a team can start with the prebuilt agent and drop down to a hand-written graph later without throwing away what they built. We took that exact path on two projects. Review collected by and hosted on G2.com.
What do you dislike about LangGraph?
The cost of all that explicitness is that you write a lot of code before anything runs.
Boilerplate is the honest first complaint. A graph that does something modest still needs a state schema, node functions, edge registrations, a compile step, and a checkpointer configuration. Compared to handing a list of tools to a prebuilt agent and getting a working loop in a dozen lines, the ceremony is real, and it is felt most on the small internal tools where the complexity is not warranted. My rule now is to start with the prebuilt agent and only rewrite as a graph when I need branching or durable state that the simpler path cannot give me. I have talked two colleagues out of building a StateGraph on day one for exactly this reason.
Reducers were the concept that cost me the most time. When two branches run in parallel and both write to the same key in state, you need to have declared how those writes combine, and if you have not thought about it, you get a last-write-wins result that looks correct until the day the ordering changes. The append-style reducer for message lists is easy enough. Writing custom merge logic for a dictionary that several nodes update concurrently took me a couple of frustrating afternoons and a lot of reading. It is a sound design, but it is not something you can pick up from the quickstart.
Version churn has been a real tax. This ecosystem has moved fast, and I have had upgrades break import paths and change the recommended way of doing something I had already built. The 1.0 line has calmed this down considerably compared to a year ago, but I still pin versions rather than tracking the latest, and I read release notes before upgrading in a way I do not bother with for most dependencies. Documentation has occasionally lagged the code too, and I have ended up in the repository reading source to settle a question the docs left open.
Visualization of a large graph is weaker than the graph metaphor implies. Rendering a diagram from a compiled graph is easy enough and it is fine for small workflows, but once you have subgraphs nested a couple of levels deep with conditional edges everywhere, the output stops being something you would put in front of a stakeholder. The studio tooling helps during development. I still keep a hand-drawn diagram in our repo for anything a non-engineer needs to understand, which feels like the wrong answer for a framework built around graphs.
Running the persistence layer in production is more work than the tutorials suggest. Local development with an in-memory checkpointer is painless. Pointing it at a real database means schema setup, connection pooling that behaves under load, and deciding what to do about checkpoint tables that grow steadily and are not going to prune themselves. We wrote our own retention job for old threads. None of it is difficult, it is just infrastructure work that arrives after you thought you were done. Review collected by and hosted on G2.com.