Skip to content

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:

  1. Same Input β†’ Same Output
  2. 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.