Facebook Pixel

The Good and Bad Parts of Callbacks in JavaScript

Callbacks have good parts (flexibility) and bad parts (hell, inversion of control). Here is the balance.

The Good and Bad Parts of Callbacks in JavaScript

Callbacks have good parts and bad parts. Understanding both helps you decide when to use them and when to use promises/async-await.

The Good Parts

1. Simplicity

Callbacks are simple: pass a function, it gets called later. No special syntax needed.

2. Universality

Callbacks work everywhere: browsers, Node.js, old engines. No polyfills needed.

3. Synchronous Use

Callbacks are not just for async. map, filter, reduce, forEach, sort all use callbacks synchronously.

4. Event Handling

Event listeners are callbacks. addEventListener is callback-based and works well.

5. Closures

Callbacks are closures. They can access outer variables, which is powerful for stateful handlers.

The Bad Parts

1. Callback Hell

Deeply nested callbacks (pyramid of doom) are hard to read and maintain.

2. Inversion of Control

You trust the function to call the callback correctly. It might call it multiple times, not at all, or with wrong arguments.

3. Error Handling

Each callback must handle errors separately (repetitive). The Node.js convention (first arg is error) is easy to forget.

4. Hard to Run in Parallel

Running multiple callbacks in parallel requires a manual counter. Promises have Promise.all.

5. Hard to Cancel

There is no standard way to cancel a callback-based operation. Promises have AbortController (via fetch).

6. Unclear Stack Traces

Error stack traces in nested callbacks are unclear. Async/await has clearer stack traces.

When to Use Callbacks

  • Simple one-off calls: setTimeout(cb, 1000).
  • Event listeners: addEventListener.
  • Synchronous array methods: map, filter, reduce, forEach.
  • Old codebases: that do not support promises.
  • Node.js APIs: that are callback-based (fs.readFile with callback).

When to Use Promises/Async-Await Instead

  • Chained async operations: fetch -> parse -> render.
  • Parallel async operations: Promise.all.
  • Complex error handling: try/catch is cleaner.
  • Modern code: that supports ES6+.

The Takeaway

Callbacks are simple, universal, and great for events and synchronous array methods. But they cause callback hell, inversion of control, repetitive error handling, and hard parallel execution. Use promises/async-await for chained async operations, and callbacks for events and synchronous iteration.

Simplicity (pass a function, it gets called later), universality (work everywhere), synchronous use (map, filter, reduce), event handling (addEventListener), and closures (access outer variables).

Callback hell (deep nesting), inversion of control (trust the function to call correctly), repetitive error handling (each callback must check), hard to run in parallel (manual counter), hard to cancel, and unclear stack traces.

Use callbacks for simple one-off calls (setTimeout), event listeners, synchronous array methods (map, filter), and old codebases. Use promises/async-await for chained async operations, parallel operations (Promise.all), and complex error handling.

Yes. When you pass a callback, you give up control of when and how it is called. The function might call it multiple times, not at all, or with wrong arguments. Promises solve this by guaranteeing once-only settlement.

No. Callbacks are also used synchronously: map, filter, reduce, forEach, sort, find, some, every all take callbacks that run synchronously. Callbacks are just functions passed as arguments, whether sync or async.

Ready to master React completely?

Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course to dive deeper with high-quality video tutorials, solve interview questions, and a premium community.

Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.