LCEL and Runnables

The pipe operator that lets you write `prompt | model | parser` is not syntactic sugar — it's LangChain's core composition model, and everything you pipe together shares one standard interface that gives you streaming, batching, and async for free. Understanding Runnables and LCEL is understanding how LangChain applications are actually built.

The last post ended with prompt | model | parser. That pipe is LCEL (LangChain Expression Language), and the components it connects are Runnables — LangChain’s unifying composition abstraction. This post explains what a Runnable is, how LCEL composes them, and the capabilities you get automatically from the shared interface. This is the concept that ties LangChain together: nearly everything is a Runnable, and Runnables compose.

The Runnable: one interface for everything

The key abstraction is the Runnable — a standard interface that LangChain components implement. Models, prompts, output parsers, retrievers, and even whole chains are all Runnables, meaning they share a common set of methods:

This uniformity is the point. Because every component implements the same Runnable interface, they’re interchangeable in composition and they all get the same capabilities. A prompt, a model, and a parser are different things, but as Runnables they present the same invoke/batch/stream surface — so you can pipe them together and treat the result the same way. The Runnable is to LangChain what a common interface is to any well-designed system: the thing that makes disparate parts compose. Once you see that “it’s all Runnables,” LangChain’s structure clicks.

LCEL: composing Runnables with a pipe

LCEL (LangChain Expression Language) is the declarative way to compose Runnables, using the | (pipe) operator to chain them so the output of one becomes the input of the next:

# Illustrative shape — see the LangChain docs for exact API.
chain = prompt | model | output_parser

# because the chain is itself a Runnable, it has the full interface:
chain.invoke({"question": "..."})          # single
chain.batch([{"question": "..."}, ...])    # many
for chunk in chain.stream({"question": "..."}):   # streamed
    print(chunk, end="")

Read prompt | model | output_parser as “pipe the input through the prompt, then the model, then the parser.” It’s function composition for LLM components, expressed declaratively. The elegant consequence: a composed chain is itself a Runnable, so it has the same invoke/batch/stream interface as its parts, and can be composed further into bigger chains. Composition is closed — Runnables make Runnables — which is what lets you build complex applications from small piped pieces without special glue at each level.

The capabilities you get for free

Here’s why LCEL is more than nice syntax: because everything is a Runnable with the standard interface, composing with LCEL gives you significant capabilities automatically, without writing them:

This is the real payoff of the Runnable/LCEL design: the cross-cutting concerns you’d otherwise implement per application — streaming, batching, async, parallelism — come from the shared interface. Compose your logic once with LCEL, and you get all of these behaviors automatically, everywhere. That’s a substantial amount of engineering handed to you by the abstraction.

Beyond linear pipes

LCEL isn’t limited to straight-line pipes; it provides Runnables for the common shapes of composition:

These let LCEL express real application flows — retrieve-then-generate, parallel-fetch-then-combine, route-by-input — declaratively, while still being chains (LCEL’s sweet spot). The important boundary: LCEL is for composition of components into pipelines, including modest branching and parallelism. When you need genuinely stateful, cyclic, complex orchestration (loops, evolving state, human-in-the-loop), that’s LangGraph’s job (its own series) — LCEL handles the chain-shaped compositions, LangGraph the graph-shaped ones. Knowing that line keeps you using the right tool.

Why LCEL matters

Stepping back, LCEL and Runnables are what make LangChain a composition framework rather than a bag of components:

If the last post’s atoms (model, prompt, parser) are the what, LCEL and Runnables are the how — the mechanism that turns those atoms into applications. The rest of the series (retrieval, tools, memory) is largely about more Runnables to compose into your chains. The next post looks at chains and composition patterns in more depth.

Key takeaways

Further reading

Sources & References

LangChain Expression Language