# Callbacks

## 1. Definition

A **callback** is a function passed into another function to be invoked later — either synchronously, during the outer function's execution, or asynchronously, at some future point (a timer firing, data arriving, a user clicking).

```js
function greetUser(name, callback) {
  const message = `Hello, ${name}`;
  callback(message); // invoking the callback synchronously
}
greetUser("Ana", msg => console.log(msg));
```

Every callback is a higher-order-function argument (see the Higher-Order Functions topic) — "callback" is the specific name used when the function is invoked *later*, not necessarily immediately.

## 2. Synchronous Callbacks

`map`, `filter`, `sort`, `forEach` all take synchronous callbacks — the callback runs to completion before the outer function returns.

```js
console.log("before");
[1, 2, 3].forEach(n => console.log("item:", n));
console.log("after");
// prints: before, item:1, item:2, item:3, after — strictly in order
```

## 3. Asynchronous Callbacks

`setTimeout`, DOM event listeners, and Node's older-style APIs (`fs.readFile`) take callbacks that run **later**, after the current synchronous code has finished — handed off to the event loop (covered fully in the async topics).

```js
console.log("1: start");
setTimeout(() => console.log("3: timeout callback"), 0);
console.log("2: end");
// prints: 1: start, 2: end, 3: timeout callback — even with a 0ms delay
```

The callback never runs *during* the current synchronous block, no matter how short the delay.

## 4. The Node.js Error-First Callback Convention

Before Promises were standard, Node.js established a convention: the callback's **first parameter is always reserved for an error** (or `null` if none occurred).

```js
const fs = require("fs");

fs.readFile("data.txt", "utf8", (err, contents) => {
  if (err) {
    console.error("Failed to read file:", err.message);
    return;
  }
  console.log(contents);
});
```

Always check `err` **first** and `return` early — forgetting this check is one of the most common Node.js bugs.

## 5. Callback Hell

Nesting multiple async callbacks produces deeply indented, hard-to-follow code — the classic motivation for Promises and `async`/`await` (covered in the async topics):

```js
getUser(id, (err, user) => {
  if (err) return handleError(err);
  getOrders(user.id, (err, orders) => {
    if (err) return handleError(err);
    getOrderDetails(orders[0].id, (err, details) => {
      if (err) return handleError(err);
      console.log(details); // 3 levels deep, and growing with each new step
    });
  });
});
```

## 6. Passing Arguments to a Callback (Common Bug Source)

```js
function delayedGreet(name, callback) {
  setTimeout(callback, 1000, name); // WRONG-looking but actually correct:
                                      // setTimeout's 3rd+ arguments ARE passed to the callback
}

// The classic mistake — calling the function immediately instead of passing a reference:
setTimeout(sayHi(), 1000);  // BUG: sayHi() executes immediately, its RETURN VALUE
                              // (probably undefined) is passed to setTimeout instead

setTimeout(sayHi, 1000);     // correct — passes the function reference itself
setTimeout(() => sayHi(), 1000); // also correct — wraps the call in a new function
```

## Under the Hood: Where a Pending Callback Actually Lives

Passing a callback to `setTimeout` or an event listener doesn't hand the function off to some abstract "later" — it stores a real reference to that function object inside a concrete runtime data structure, where it waits until conditions are met.

### Node.js: callbacks live in libuv's data structures

```js
setTimeout(() => console.log("hi"), 5000);
```

![setTimeout registers the callback in libuv's timer heap; the event loop pushes it onto the call stack once the timer expires](diagrams/01-callback-in-timer-heap.svg)

Node's underlying C library, **libuv**, maintains an actual **timer heap** (a min-heap ordered by expiration time) — calling `setTimeout` inserts an entry into this heap holding a pointer back to your JavaScript callback. The event loop's "timers" phase (see the Event Loop topic) checks this heap on each iteration and fires any callback whose time has come. The callback function object — and everything it closes over (see the Closures topic) — is kept alive in memory by this heap entry for the entire waiting period, even if nothing else in your program still references it.

### Why an uncleared timer/interval is a real memory-leak vector

```js
function startPolling(largeDataset) {
  const id = setInterval(() => {
    console.log(largeDataset.length); // captures largeDataset via closure
  }, 1000);
  // if `id` is lost and clearInterval is never called, this callback —
  // and the ENTIRE Context holding largeDataset — stays reachable
  // and alive in memory forever, because libuv's timer structure still
  // references it, even though nothing else in the program does
}
```

This connects directly to the Closures topic's memory section: the callback's `Context` object stays reachable (and therefore un-garbage-collectible) for as long as the timer/interval registration itself exists — `clearTimeout`/`clearInterval` isn't just "stopping a timer," it's removing the last reference keeping that closure (and whatever it captured) alive.

### Browsers: the same idea, different concrete queue

A browser's callback queues (per the Event Loop topic) work on the same principle — `addEventListener`'s callback reference is stored in the DOM node's internal listener list; `setTimeout`'s callback is stored in the browser's own timer/task-queue implementation. The exact data structures differ by engine, but the governing idea is identical: a "pending callback" is a real, retained reference sitting in a runtime structure, not a conceptual promise that resolves itself.

## 7. Common Pitfalls

1. **Forgetting to check the error argument first** in Node-style callbacks — silently proceeding with `undefined` data.
2. **Calling a function instead of passing its reference** — `setTimeout(fn(), 1000)` vs `setTimeout(fn, 1000)`.
3. **Losing `this` inside a callback** — a callback passed as a bare reference loses implicit binding (see the `this` keyword topic); fix with an arrow function or `.bind()`.
4. **Assuming callbacks always run asynchronously** — some APIs (like `Array.prototype.forEach`) call back synchronously; mixing sync and async callback conventions inconsistently causes subtle ordering bugs.
5. **Calling a callback more than once by accident** — especially in error-handling paths where both a success path and an error path might each call it.

## 8. Quick Reference

```
node-style:  callback(err, result)  → always check err first
setTimeout(fn, ms, ...args)          → args are forwarded to fn when it fires
passing fn                            → correct: reference
passing fn()                          → wrong: calls immediately, passes return value
```
