We almost always conflate them, and that’s understandable: evaluation strategy and execution strategy both speak to the moment when “things happen” in a program. Yet they are two perfectly orthogonal axes. One answers the question when does an expression produce its value?, the other how does the program work through its instructions?. Lumping them together leads to false intuitions: typically believing that “asynchronous” means “deferred”, or that a Promise does nothing until you await it. Let’s untangle the two.
Two dimensions not to confuse
It all rests on a distinction in vocabulary:
- The evaluation strategy of an expression: it can be eager (immediate) or lazy (deferred).
- The execution strategy of an instruction: it can be synchronous (blocking) or asynchronous (non-blocking).
The first describes at what instant an expression is turned into a value. The second describes in what manner the engine carries out the work once it’s started. These are two independent decisions, and that’s exactly what makes all four combinations possible.
Evaluation Strategy: eager or lazy
The evaluation strategy concerns when and how an expression is used to produce a value. This is the territory of lazy evaluation (deferred evaluation) and eager evaluation (immediate evaluation).
Eager: immediate evaluation
In eager mode, the expression is evaluated as soon as it is encountered. This mode allows a direct style: you get a value, or trigger a side effect, without ceremony. The trade-off is that you lose control over when that work starts: it begins on its own.
The canonical example in JavaScript is the Promise: it is eagerly evaluated. As soon as a Promise is created, its executor immediately enters the execution phase, without waiting for any await or .then().
// The body of the Promise runs AS SOON AS it's created,
// even before we subscribe to the result.
const promise = new Promise((resolve) => {
console.log("immediate evaluation (eager)");
resolve(42);
});
// "immediate evaluation (eager)" is already printed here,
// regardless of what follows.
promise.then((value) => console.log(value));
Lazy: deferred evaluation
In lazy mode, the evaluation of the expression is pushed back so it only happens at the moment we actually need it. The advantage is total control over the trigger: nothing happens until we explicitly ask for it.
The canonical example in JavaScript is the Generator: it is lazily evaluated. Creating a generator produces no value; the body only advances when the underlying iterator is prompted via next().
function* numbers() {
console.log("deferred evaluation (lazy)");
yield 1;
yield 2;
}
// No value produced, the body hasn't run yet.
const iterator = numbers();
// It's ONLY here that "deferred evaluation (lazy)"
// is printed and the first value is produced.
iterator.next(); // { value: 1, done: false }
Execution Strategy: synchronous or asynchronous
Once the expression has been evaluated, whether immediately in eager mode or in deferred fashion in lazy mode, the execution proper can take two forms.
Synchronous: blocking
Synchronous execution is said to be blocking: control is not returned to the program until the operation has finished. In other words, the program is entirely committed to the synchronous steps, executed one after another, in order.
// Each line waits for the previous one: control returns
// only once the instruction has finished.
const a = compute(1);
const b = compute(2);
console.log(a + b);
Asynchronous: non-blocking
Asynchronous execution is said to be non-blocking: the operation is launched and then carried on in the background, for instance via the Event Loop, and control is immediately returned to the program. The result will be reported as soon as it’s available, and in the meantime other operations can make progress.
// The operation is delegated; control returns right away
// and the rest of the program continues.
setTimeout(() => console.log("notified later"), 0);
console.log("executed first");
// Prints: "executed first" then "notified later".
The two axes combine
The essential point is that these two dimensions are independent. The evaluation strategy determines when an expression is evaluated; the execution strategy defines how the program runs its instructions. Neither dictates the other.
This is what makes possible combinations that intuition tends to rule out:
- Lazy and synchronous: a generator whose values you consume in a blocking loop. Evaluation is deferred, but each step executes synchronously.
- Eager and asynchronous: a Promise, precisely. Its body starts immediately (eager), but the result is delivered in non-blocking fashion (async).
An evaluation can therefore be lazy while being executed synchronously, or be eager while executing asynchronously. When and how are two distinct settings, free to combine.
Conclusion
Eager/lazy and synchronous/asynchronous are not two ways of naming the same thing: they are two orthogonal axes. The first answers when an expression is evaluated, the second how it is executed. Keeping this distinction in mind avoids stubborn misreadings, like expecting a Promise to behave lazily, or assuming that asynchronous implies deferred. The moment you reason about the two dimensions separately, the behavior of Promises, generators and the Event Loop stops being surprising and becomes perfectly predictable.