Skip to content
Laviyx Learn

Try

JavaScript course menu

JavaScript Callbacks and Higher-Order Functions

Learn callbacks and higher-order functions in JavaScript: passing functions as arguments, returning functions, writing your own higher-order functions, synchronous and asynchronous callbacks, callback hell, and composing small functions.

Beginner6 min readUpdated
On this page

A callback is a function you pass to another function, to be called later. A higher-order function is a function that takes a function as an argument or returns one. Together they are one of the most common patterns in JavaScript.

Passing a function#

JavaScript
function repeat(times, action) {
  for (let i = 1; i <= times; i++) {
    action(i);
  }
}

repeat(3, (n) => console.log(`Push-up ${n}`));
Output
Push-up 1
Push-up 2
Push-up 3

repeat does not know what to do on each step; the caller decides by passing action. That makes repeat reusable for anything.

Built-in higher-order functions#

You will use these all the time. Each takes a callback:

JavaScript
const scores = [72, 95, 58, 88];

scores.forEach((s, i) => console.log(`Student ${i + 1}: ${s}`));
console.log(scores.filter((s) => s >= 70));
console.log(scores.map((s) => s + 5));
console.log([...scores].sort((a, b) => b - a));
Output
Student 1: 72
Student 2: 95
Student 3: 58
Student 4: 88
[ 72, 95, 88 ]
[ 77, 100, 63, 93 ]
[ 95, 88, 72, 58 ]

They are covered in detail in Array Methods.

Writing your own#

Writing a simple version of a built-in method shows how callbacks work:

JavaScript
function myFilter(array, test) {
  const result = [];
  for (const item of array) {
    if (test(item)) result.push(item);
  }
  return result;
}

const words = ["sun", "planet", "galaxy", "moon"];
console.log(myFilter(words, (w) => w.length > 4));
console.log(myFilter([1, 2, 3, 4, 5, 6], (n) => n % 2 === 0));
Output
[ 'planet', 'galaxy' ]
[ 2, 4, 6 ]

Returning functions#

A higher-order function can also return a function, often using a closure:

JavaScript
const greaterThan = (limit) => (n) => n > limit;

const prices = [99, 250, 499, 1200];
console.log(prices.filter(greaterThan(200)));
console.log(prices.filter(greaterThan(1000)));
Output
[ 250, 499, 1200 ]
[ 1200 ]

Composing functions#

Small functions can be combined into bigger ones:

JavaScript
const pipe = (...fns) => (value) => fns.reduce((acc, fn) => fn(acc), value);

const trim = (s) => s.trim();
const lower = (s) => s.toLowerCase();
const slugify = (s) => s.replaceAll(" ", "-");

const toSlug = pipe(trim, lower, slugify);
console.log(toSlug("  Learn JavaScript Today  "));
Output
learn-javascript-today

Synchronous and asynchronous callbacks#

The callbacks above run immediately, during the call (synchronous). Other callbacks run later, when something happens:

JavaScript
console.log("1. Start");
setTimeout(() => console.log("3. Timer finished"), 0);
console.log("2. End of script");
Output
1. Start
2. End of script
3. Timer finished

Even with a 0 millisecond delay, the timer callback waits until the current code has finished. Event handlers, timers and network requests all use asynchronous callbacks. Asynchronous Code and the Event Loop explains why.

Callback hell#

When asynchronous steps depend on each other, nested callbacks pile up:

JavaScript
getUser(id, (user) => {
  getOrders(user, (orders) => {
    getInvoice(orders[0], (invoice) => {
      sendEmail(invoice, () => {
        console.log("Done");
      });
    });
  });
});

This "pyramid" is hard to read and to handle errors in. Promises and async/await solve it (Promises), so modern code writes the same steps one after another.

Practice#

Practice exercise

Write countWhere(array, test) that returns how many items pass the test, and use it to count words longer than 3 letters and numbers divisible by 5.

Show solution
JavaScript
function countWhere(array, test) {
  let count = 0;
  for (const item of array) {
    if (test(item)) count++;
  }
  return count;
}

console.log(countWhere(["a", "tree", "is", "green"], (w) => w.length > 3));
console.log(countWhere([5, 12, 20, 33, 45], (n) => n % 5 === 0));
Output
2
3

The built-in equivalent is array.filter(test).length.

Key takeaway#

Finished this lesson?