14. Callbacks and Timers
Synchronous code runs line by line — the next line waits for the previous one to finish. Asynchronous code schedules "something to run later" and moves on right away.
What you'll learn
- 1Understand sync vs async.
- 2Use `setTimeout`, `setInterval`, `clearTimeout`, `clearInterval`.
- 3Build a high-level mental model of the event loop.
- 4Recognize callback hell and feel why Promises (next lecture) exist.
- 5Track timer IDs to avoid leaks.
Overview
Synchronous code runs line by line — the next line waits for the previous one to finish. Asynchronous code schedules "something to run later" and moves on right away.
Core Concepts
1. Sync vs async
Synchronous code runs line by line — the next line waits for the previous one to finish. Asynchronous code schedules "something to run later" and moves on right away.
console.log("A");
setTimeout(() => console.log("B"), 0);
console.log("C");
// output: A, C, BEven `setTimeout(fn, 0)` runs only after the current synchronous code finishes.
2. setTimeout and clearTimeout
`setTimeout(fn, ms)` runs the callback once after `ms` milliseconds. The return value is a timer ID; `clearTimeout(id)` cancels it.
const id = setTimeout(() => console.log("never runs"), 1000);
clearTimeout(id);`ms` is a "minimum delay", not an exact one. If the main thread is busy, the callback runs later.
3. setInterval and clearInterval
`setInterval(fn, ms)` repeats the callback at a fixed interval. Always clean it up with `clearInterval` once you're done.
let n = 0;
const id = setInterval(() => {
n += 1;
if (n >= 3) clearInterval(id);
console.log(n);
}, 500);4. The event loop (big picture)
JavaScript is single-threaded, but the host (browser/Node) provides timer and network APIs that run independently. Their callbacks queue up and only run when the call stack is empty. We'll look at this more closely starting in Lecture 16.
5. Callback hell
When async operations need to chain, callbacks nest into callbacks and the indentation explodes.
step1((a) => {
step2(a, (b) => {
step3(b, (c) => {
console.log(c);
});
});
});Lecture 15 introduces `Promise` to fix this, and Lecture 16 covers `async/await`.
Examples
| File | What it covers |
|---|---|
| 01_settimeout.js | setTimeout/clearTimeout basics |
| 02_setinterval.js | setInterval with an exit condition |
| 03_event_loop.js | Observe sync vs async order |
| 04_callback_hell.js | Demonstrate callback nesting |
src/01_settimeout.js
/**
* setTimeout basics.
* Run a message after one second; cancel another timer immediately.
*/
console.log("start");
setTimeout(() => {
console.log("ran after 1 second");
}, 1000);
const cancelId = setTimeout(() => {
console.log("this never prints");
}, 500);
clearTimeout(cancelId);
console.log("end of sync code");
src/02_setinterval.js
/**
* setInterval ticks every 0.5s and stops after 3 iterations.
*/
let count = 0;
const intervalId = setInterval(() => {
count += 1;
console.log(`tick ${count}`);
if (count >= 3) {
clearInterval(intervalId);
console.log("cleaned up");
}
}, 500);
src/03_event_loop.js
/**
* Watch how sync code and async callbacks interleave.
* Expected order: A -> D -> C -> B
*/
console.log("A");
setTimeout(() => console.log("B"), 100);
setTimeout(() => console.log("C"), 0);
console.log("D");
src/04_callback_hell.js
/**
* The callback-hell pattern.
* Compare this with the Promise version in the next lecture.
*/
function step(name, ms, callback) {
setTimeout(() => {
console.log(`${name} done`);
callback();
}, ms);
}
step("step 1", 200, () => {
step("step 2", 200, () => {
step("step 3", 200, () => {
console.log("all steps done");
});
});
});
Common Mistakes
- Writing `setTimeout(fn(), 1000)` — that immediately calls `fn` and schedules its return value.
- Forgetting to `clearInterval` and leaking timers.
- Expecting `setTimeout(fn, 0)` to run immediately.
- Trying to catch errors thrown inside an async callback with an outer try/catch.
- Forgetting that `this` inside a callback may differ from the enclosing scope — arrow functions help.
FAQ
Q1. What's the difference between setTimeout 0ms and immediate execution?
Even 0ms goes through the queue once, so it runs after all current sync code finishes.
Q2. setInterval vs recursive setTimeout?
setInterval tries to keep a fixed interval regardless of how long the callback takes; recursive setTimeout schedules the next timer only after the callback finishes.
Q3. Are browser and Node timers the same?
The API signatures match, but minimum delays and queue priorities differ.
Practice
- Write a function that counts down once per second.
- Write a chain that runs three callbacks in sequence.
homework/README.md
## 1. Countdown — Write `countdown(n)` that prints from n down to 0 once per second, then prints "Liftoff!".
## 2. Sequential — Write code that runs three async tasks one after another via callbacks. Each task is simulated with `setTimeout`.
See the `answer/` directory for solutions.
homework/answer/homework_01.js
/**
* Print n down to 0 once per second, then "Liftoff!".
* @param {number} n starting number
*/
function countdown(n) {
let current = n;
const id = setInterval(() => {
if (current < 0) {
clearInterval(id);
console.log("Liftoff!");
return;
}
console.log(current);
current -= 1;
}, 1000);
}
countdown(3);
homework/answer/homework_02.js
/**
* Run three async tasks sequentially.
* @param {string} name task name
* @param {Function} done completion callback
*/
function task(name, done) {
setTimeout(() => {
console.log(`${name} done`);
done();
}, 300);
}
task("A", () => {
task("B", () => {
task("C", () => {
console.log("all tasks complete");
});
});
});
Next Lecture
[15_Promise](../15_Promise/) — The Promise pattern that flattens callback hell.
All lecture materials and example code are openly available on GitHub.
View on GitHub ↗