Declarative vs Imperative APIs in JavaScript (and Why Chainable Code Feels So Different)
Building stuff
When people talk about “declarative” APIs, they often mean something vague like cleaner, functional, or React-style. But the real difference between declarative and imperative (non-declarative) APIs is much simpler:
Imperative code tells the computer how to do something.
Declarative code tells the computer what you want.
This distinction matters a lot when designing or choosing APIs, especially in JavaScript/Typescript, where chainable calls, callbacks, and fluent interfaces are everywhere.
Let’s break it down.
The core idea: how vs what
Imperative (non-declarative)
You control every step of the process:
let result = [];
for (let i = 0; i < users.length; i++) {
if (users[i].active) {
result.push(users[i].name.toUpperCase());
}
}
You explicitly manage:
loops
conditions
intermediate state
execution order
You’re describing the procedure.
Declarative
You describe the desired result:
const result = users
.filter(u => u.active)
.map(u => u.name.toUpperCase());
Now:
no explicit loop management
no mutation
no temporary variables
no control flow
You express intent, and the runtime figures out execution.
APIs can be declarative too
This idea applies not only to syntax, but to API design.
Imperative API
db.connect();
db.selectTable("users");
db.addFilter("age > 18");
db.sort("name");
const result = db.execute();
You orchestrate every step.
Declarative API
db.query({
table: "users",
where: "age > 18",
orderBy: "name"
});
You describe the outcome. The system decides how to achieve it.
What about chainable APIs?
Chaining does not automatically mean declarative. It depends on what the chain represents.
Declarative chaining
users
.filter(u => u.active)
.map(u => u.name)
.sort();
This builds a transformation pipeline:
each call describes a rule
nothing mutates external state
no side effects
execution is abstracted
You’re defining what should happen to the data.
Imperative chaining
request
.open()
.setHeader("Authorization", token)
.send()
.onSuccess(handleSuccess);
Here:
each call performs an action immediately
order matters operationally
side effects occur
you are commanding steps
It’s still imperative even though it looks fluent.
Callbacks don’t decide “declarativity”
Callbacks themselves are neutral. What matters is who controls execution.
Imperative callback
fs.readFile("file.txt", (err, data) => {
process(data);
});
You start the action and provide instructions.
Declarative callback registration
router.get("/users", {
auth: true,
handler: (req, res) => { ... }
});
You’re declaring behavior, not triggering execution.
Immediate execution vs building a description
A useful mental model:
If method calls execute immediately → more imperative
If method calls build a description to run later → more declarative
Example:
anim.move(10, 0);
anim.rotate(45);
anim.scale(2);
anim.play();
Imperative: actions happen now.
Versus:
anim
.move(10, 0)
.rotate(45)
.scale(2)
.build();
Declarative: steps are recorded, not executed yet.
Real-world JavaScript examples
Mostly declarative
React JSX
SQL query builders (Prisma, Knex)
CSS
GraphQL queries
Array methods (
map,filter,reduce)RxJS pipelines
Lodash chaining
Mostly imperative
DOM manipulation
Canvas drawing
Node.js file system APIs
Fetch with manual control flow
jQuery classic chaining
A quick checklist
Ask yourself:
| Question | Declarative | Imperative |
| Do I manage loops? | ❌ | ✅ |
| Do I manage state? | ❌ | ✅ |
| Do I describe intent? | ✅ | ❌ |
| Is execution hidden? | ✅ | ❌ |
Why this matters
Declarative APIs:
are easier to reason about
reduce bugs from state management
compose better
are easier to optimize internally
are more readable at scale
Imperative APIs:
offer fine control
are sometimes necessary for performance
fit low-level tasks
Most production systems combine both, but understanding the difference helps you design cleaner APIs and recognize why some code “feels” simpler.
Final takeaway
Declarative: “Here is what I want.”
Imperative: “Here is how to do it.”
Once you start seeing this distinction, you’ll notice it everywhere, from React components to database queries to method chains.