LCEL: Engineering Foundations¶
Declarative AI Systems¶
Goal of this Chapter
Before learning LCEL, understand the engineering principles that inspired it.
Frameworks evolve. APIs change. Good engineering principles rarely do.
By the end of this chapter, you should understand why LCEL exists, not just how to use it.
Learning Outcomes¶
After completing this chapter, you should confidently answer:
- What is Imperative Programming?
- What is Declarative Programming?
- Why is SQL declarative?
- Why is React declarative?
- Why is LCEL declarative?
- What is Composition?
- Why do modern systems prefer Composition over Inheritance?
- What is the Pipeline Pattern?
- How is LCEL simply another pipeline?
- Why does Functional Programming influence LCEL?
Imperative vs Declarative Programming¶
Why Should We Learn This?¶
One of the biggest architectural shifts in modern software engineering is the movement from Imperative Programming to Declarative Programming.
You'll notice this transition everywhere:
- SQL
- React
- Docker Compose
- Terraform
- Kubernetes
- GitHub Actions
- LangChain Expression Language (LCEL)
Understanding this paradigm shift is the key to understanding why LCEL exists.
What is Imperative Programming?¶
Imperative Programming is a programming paradigm where the developer explicitly tells the computer how to perform a task.
You control:
- execution order
- loops
- state
- variables
- conditions
- mutations
The computer simply follows your instructions.
Think of it like giving someone turn-by-turn driving directions.
"Go straight for 500 meters, turn left, continue until the traffic signal..."
The responsibility of execution belongs entirely to the programmer.
Example¶
Goal:
Take a list of numbers.
- Keep only even numbers.
- Double them.
Imperative Solution¶
numbers = [1, 2, 3, 4, 5]
result = []
for number in numbers:
if number % 2 == 0:
result.append(number * 2)
Notice how we explicitly manage:
- iteration
- condition checking
- mutation of
result - execution flow
The programmer owns every step.
What is Declarative Programming?¶
Declarative Programming focuses on what should happen instead of how it should happen.
Instead of describing the algorithm,
you describe the desired outcome.
The underlying runtime, engine, or framework decides how to execute it efficiently.
Think of giving a GPS a destination.
Instead of saying
"Turn left... turn right..."
you simply say
"Take me to Bangalore Airport."
The GPS decides:
- route
- traffic
- road closures
- alternate paths
Example¶
Using Python list comprehension:
numbers = [1, 2, 3, 4, 5]
result = [n * 2 for n in numbers if n % 2 == 0]
We no longer manually manage:
- mutable state
- loop indices
- append operations
Instead, we declare the transformation.
Imperative vs Declarative¶
| Imperative | Declarative |
|---|---|
| Focuses on How | Focuses on What |
| Programmer controls execution | Framework controls execution |
| Manual state management | Framework manages state |
| Explicit loops and conditions | High-level expressions |
| Greater control | Greater readability |
| More boilerplate | Less boilerplate |
Pros & Cons¶
Imperative¶
Advantages¶
- Full execution control
- Easier to understand step-by-step execution
- Easier to optimize low-level operations
- Straightforward debugging
Disadvantages¶
- Boilerplate code
- Harder maintenance
- Difficult to compose
- Easy to introduce state-related bugs
Declarative¶
Advantages¶
- Cleaner code
- Easier composition
- Easier maintenance
- Runtime optimizations
- Easier parallelization
- Easier streaming
- Better readability
Disadvantages¶
- Less control over execution
- Requires trust in framework/runtime
- Can be harder to debug framework internals
Why is SQL Declarative?¶
Consider:
SELECT *
FROM Users
WHERE age > 18;
Notice what you didn't write.
You never specified:
- how to scan the table
- whether to use an index
- whether to cache rows
- how to optimize joins
- how to parallelize execution
You simply describe what data you need.
The SQL Query Optimizer decides how to retrieve it.
This is declarative programming.
Why is React Declarative?¶
Suppose you want to display:
<h1>Hello Ankit</h1>
In React you simply write:
return <h1>Hello {user.name}</h1>
You never manipulate:
- DOM nodes
- appendChild()
- removeChild()
- repaint logic
React's reconciliation engine decides how to update the browser efficiently.
Again,
you describe
What
not
How.
Why is LCEL Declarative?¶
Suppose we write:
chain = prompt | model | parser
We never specify
- execution order
- nested function calls
- streaming logic
- batching logic
- async orchestration
We simply describe the pipeline. LCEL decides how to execute it.
This is exactly why LCEL is declarative.
Why did LangChain move from Helper Chains to LCEL?¶
Earlier versions of LangChain relied heavily on helper classes such as:
- LLMChain
- RetrievalQA
- SequentialChain
- StuffDocumentsChain
These abstractions worked well for simple workflows but struggled as AI systems became more complex.
Developers needed:
- Streaming
- Async execution
- Parallel execution
- Better observability
- Custom orchestration
- Reusable components
Creating a new helper class for every workflow was not scalable.
Instead,
LangChain introduced LCEL.
LCEL standardized every component into a common abstraction that could be freely composed together.
This dramatically improved:
- maintainability
- extensibility
- streaming
- tracing
- testing
Interview Insight β¶
When someone asks
"Why did LangChain introduce LCEL?"
Don't answer
"Because it uses Runnable."
Instead answer
"Helper chains became rigid as AI applications grew more complex. LCEL introduced a declarative composition model that supports reusable components, streaming, async execution, parallelism, and better observability."
That answer demonstrates architectural understanding.
Interview Questions¶
Beginner¶
- What is Imperative Programming?
- What is Declarative Programming?
- Difference between them?
Intermediate¶
- Why is SQL declarative?
- Why is React declarative?
- Why is LCEL declarative?
Advanced¶
- Why did LangChain move away from helper chains?
- When would you prefer imperative orchestration over declarative composition?
Composition¶
What is Composition?¶
Composition is a software design principle where complex systems are built by assembling many small, independent components rather than creating one large object responsible for everything.
Instead of asking
"What is this object?"
Composition asks
"What capabilities does this object have?"
This is commonly summarized as:
Has-A relationship
instead of
Is-A relationship.
LEGO Analogy¶
Imagine building a spaceship.
You don't manufacture one giant plastic spaceship.
Instead,
you combine:
- wings
- engines
- cockpit
- landing gear
Each component has one responsibility.
Together,
they create a larger system.
Modern software works the same way.
Why Compose Small Components?¶
Suppose we create one large helper function.
Process Everything
It now performs:
- validation
- formatting
- retrieval
- generation
- parsing
- logging
Eventually,
it becomes impossible to understand.
Instead,
we split responsibilities.
Validator
β
Retriever
β
Prompt Builder
β
LLM
β
Parser
Each component owns exactly one responsibility.
Advantages of Composition¶
Composition provides:
- Better readability
- Easier testing
- Easier replacement
- Better maintainability
- Better reuse
- Lower coupling
Changing one component should not require changing the entire system.
Composition over Inheritance¶
Modern software engineering strongly prefers:
Composition over Inheritance
Inheritance creates:
Dog
β
Animal
β
LivingThing
β
Object
As hierarchies grow, they become tightly coupled.
Composition instead assembles behaviour dynamically.
Example:
CustomerSupportAgent
Has
β
Retriever
β
PromptBuilder
β
LLM
β
Parser
Each component can be replaced independently.
Engineering Insight¶
Composition allows systems to evolve by adding new components rather than modifying existing ones.
This directly supports the Open/Closed Principle.
Interview Questions¶
Beginner¶
- What is Composition?
Intermediate¶
- Composition vs Inheritance?
Advanced¶
- Why do modern frameworks favour composition?
- How does LCEL demonstrate composition?
Pipeline Pattern¶
What is a Pipeline?¶
A Pipeline is an architectural pattern where data flows through a sequence of independent processing stages.
Each stage performs exactly one responsibility before passing the result to the next stage.
General form:
Input
β
Transform
β
Transform
β
Transform
β
Output
Why Pipelines?¶
Imagine writing one function responsible for:
- reading data
- validating it
- transforming it
- logging it
- saving it
The function quickly becomes difficult to understand and maintain.
Pipelines separate these concerns into independent stages.
Each stage becomes:
- reusable
- testable
- replaceable
Examples of Pipeline Pattern¶
Unix Pipes¶
cat file.txt
β
grep error
β
sort
β
wc -l
Java Streams¶
stream
.filter(...)
.map(...)
.collect(...)
Spring Security¶
Request
β
Authentication Filter
β
Authorization Filter
β
Controller
Middleware¶
Request
β
Logger
β
Validator
β
Business Logic
β
Response
LCEL¶
Prompt
β
LLM
β
Parser
LCEL is simply another implementation of the Pipeline Pattern.
Benefits¶
- Separation of Concerns
- Reusability
- Easy debugging
- Easy testing
- Easier extension
- Better readability
Interview Questions¶
- What is Pipeline Pattern?
- Advantages?
- Give real-world examples.
- Why is LCEL considered a pipeline?
Functional Programming Basics¶
Goal
Understand enough Functional Programming to appreciate LCEL.
We are not trying to become Functional Programming experts.
Pure Functions¶
A Pure Function follows two rules:
- Same Input β Same Output
- No Side Effects
Example:
def add(a, b):
return a + b
Calling
add(2, 3)
will always return
5
No external variables are modified.
Why Pure Functions Matter¶
Pure functions are:
- Predictable
- Easy to test
- Easy to parallelize
- Easy to reuse
This makes them ideal building blocks for pipelines.
Function Composition¶
Instead of writing one huge function, combine many small ones.
Example:
Trim
β
Lowercase
β
Validate
β
Store
Each function performs one task.
Together, they produce complex behaviour.
This idea is fundamental to LCEL.
Code Example¶
Imagine processing a raw username string (e.g., " ALICE " $\rightarrow$ "alice").
def trim(text):
return text.strip()
def lowercase(text):
return text.lower()
# Manual composition (calling one inside another)
raw_name = " ALICE "
clean_name = lowercase(trim(raw_name)) # "alice"
# Or using LCEL / Unix style pipe operator idea:
# clean_name = raw_name | trim | lowercase
Why it matters¶
It allows you to build complex pipelines using small, reusable building blocks (like snapping LEGO pieces together). This is the exact design pattern powering Unix commands cat file.txt | grep error | wc -l and LCEL prompt | model | parser.
Higher-Order Functions¶
A Higher-Order Function is a function that:
- accepts another function as an argument, or
- returns another function.
Examples:
- map()
- filter()
- reduce()
Higher-order functions make behaviour reusable.
Code Example¶
# 1. Function taking another function as an argument (map, filter)
numbers = [1, 2, 3, 4]
def double(n):
return n * 2
# `map` is a Higher-Order Function because it accepts `double` as a parameter
doubled_numbers = list(map(double, numbers)) # [2, 4, 6, 8]
# 2. Function returning another function (Factory pattern)
def make_multiplier(factor):
def multiply(number):
return number * factor
return multiply # Returns a function!
triple = make_multiplier(3) # `triple` is now a function
print(triple(5)) # Output: 15
Why it matters¶
Higher-order functions remove repetitive loop boilerplate (via methods like map, filter, and reduce) and let you dynamically create behavior on the fly (like custom decorators or event handlers).
Why Functional Programming Matters for LCEL¶
LCEL is heavily inspired by Functional Programming.
Its design emphasizes:
- Pure transformations
- Function composition
- Pipelines
- Small reusable units
- Declarative data flow
Understanding these ideas makes LCEL feel natural rather than magical.
Summary¶
Before studying LCEL itself, understand these four engineering ideas:
- Declarative Programming
- Composition
- Pipeline Pattern
- Functional Programming
LCEL is not a brand-new concept.
It is LangChain's implementation of engineering principles that have existed in software engineering for decades.
What You Should Remember Forever β¶
If you forget all implementation details, remember this:
LCEL did not invent a new programming paradigm.
It combines well-established software engineering principlesβ
- Declarative Programming
- Composition
- Pipeline Pattern
- Functional Programming
βand applies them to AI workflows.
Once you understand these principles, LCEL becomes intuitive rather than something to memorize.