Skip to main content

Command Palette

Search for a command to run...

Declarative vs Imperative APIs in JavaScript (and Why Chainable Code Feels So Different)

Updated
4 min read
A

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:

QuestionDeclarativeImperative
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.