Function expressions & callbacks
Learn how JavaScript treats functions as values: store them in variables, pass them into other functions, return them, and use callbacks safely.
- 01Make function valuesWrite a function expression and tell it apart from a function declaration.
- 02Pass functions aroundStore a function in a variable or an object, or pass it to another function, without calling it too early.
- 03Use callbacksFollow a function that gets another function and calls it at the right moment.
Functions are values
You already know how to write a function and call it. This lesson adds one new idea. In JavaScript, a function is also a value, just like a number or a string. So you can:
- store a function in a variable,
- keep it in an object,
- pass it to another function,
- return it from a function,
- put it in an array.
That is why typeof a function gives "function". You met typeof in the Data types lesson. It tells you what kind of value something is.
A function value can do one thing other values can't: you can call it. You call a function by writing round brackets () after it. Without the brackets, you only move the function around. With the brackets, its code runs now.
A function expression makes a function value. A callback is a function value that you pass to another function. That other function calls it at the right moment.
Think of a recipe card for pancakes. You can put it in a folder, mail it to a friend, or hand it to a cook. None of these make pancakes. Pancakes happen only when someone cooks from the card. In the same way, add is the card, and add(2, 3) cooks from it now.
- In real life: A recipe card
- In JavaScript: A function value
- In real life: Putting the card in a folder
- In JavaScript: Storing it in a variable or an object
- In real life: Handing the card to a friend
- In JavaScript: Passing it to another function
- In real life: Cooking from the card
- In JavaScript: Calling it with round brackets
()
Many parts of JavaScript ask you for a function: array methods, button clicks, and timers. A later lesson, Higher-order functions, goes deeper. In this lesson you learn one skill: look at a function and tell whether it is being stored, passed, or called.
Function expressions
CORE IDEAA function declaration is a line that starts with the word function. It makes a function and gives it a name:
function add(a, b) {
return a + b;
}An expression is any piece of code that gives a value, like 2 + 3. A function expression makes a function in a place where a value goes. The most common place is after =, so the function is stored in a const variable:
const add = function (a, b) {
return a + b;
};
console.log(add(2, 3)); // 5Here is what each part does:
- The right side of line 1,
function (a, b) { ... }, is the function value. - The left side,
add, is the variable that stores it. - Line 5 calls the function with
add(2, 3)and prints5.
The const rule from the Variables lesson still applies. You can't put a different value in add later. You can still call the function as often as you like.
add is the function itself, stored in a variable. With round brackets, add(2, 3) runs the function and gives back 5.A function expression can sit anywhere a value can sit:
- after
=, - inside an object,
- inside an array,
- between the round brackets of a function call.
Sort the lines below. Each answer explains the clue.
function a() {};const b = function () {};setTimeout(function () {}, 0);const c = function named() {};(function () {})();const tools = { run: function () {} };
Sort each line by how it makes its function. Each answer explains the clue.
Declarations vs expressions
STEP THROUGHBoth shapes make a function you can call. The difference is when the name is ready to use. Before any line runs, JavaScript reads the whole script and sets up every name it finds. This setup step is called hoisting. Each shape is set up in a different way:
- A declaration is set up with its whole function. You can call it even above the line where it is written.
- A
constfunction expression is set up as a name that can't be used yet. It becomes usable only when its own line runs. The time before that is called the temporal dead zone. Calling the function there throws aReferenceError. - An older
varfunction expression is set up holdingundefined. Calling it early throws aTypeError, becauseundefinedis not a function.
Try all three in the replay below. Line 1 calls sayHello(). Line 3 makes the function.
Pick a shape, then step through. See what happens when line 1 calls the function before line 3 makes it.
script
sayHello(); console.log("hello from declaration");}sayHello when line 1 calls it. A declaration is ready with its function. A const name can't be used yet. A var name holds undefined, which can't be called.The table compares the two shapes. Read each row as a question, then read the answer for each shape.
| What you want to know | Function declaration | Function expression |
|---|---|---|
| Can I call it above its line? | Yes. Hoisting sets it up with its whole function before any line runs. | No. A const or let one throws ReferenceError. A var one still holds undefined, so calling it throws TypeError. |
| Does it need a name? | Yes. The name is required. | No. It can have no name, take its name from the variable, or have its own name for clearer errors. |
| When is it used? | For a named helper that you reuse in your code. | For passing a function, storing it in an object, or picking one function from many. |
| How does it read? | Like a statement: here is a function called add. | Like a value: store this function in the variable add. |
Calling a const function expression too early throws ReferenceError: Cannot access 'sayHello' before initialization. Calling a var function expression too early throws TypeError: sayHello is not a function. Your editor may warn you about this before you even run the code.
Which one should you use?
- Use a declaration for a named helper that reads like a normal part of your code.
- Use a function expression when you treat the function as data: you store it in an object, pass it to a function, or make it for one spot only.
First-class functions
INTERACTIVEFirst-class means functions are treated like any other value. A number can be stored in a variable, passed to a function, returned, and kept in an object. A function can do all of these too. This is a normal rule of the language, not a special trick.
In the playground, an object called operations holds three functions. Pick one, and calculate calls it with your two numbers.
function add(a, b) { return a + b; }function subtract(a, b) { return a - b; }function multiply(a, b) { return a * b; } const operations = { add, subtract, multiply };const chosen = operations.add; function calculate(operation, left, right) { return operation(left, right);} console.log(calculate(chosen, 8, 3));calculate(add, 8, 3)
Result: 11
The object holds three functions. The one you picked is passed to calculate. Then calculate calls it with 8 and 3.
You will use this pattern often: first pick the data, then pick the function that works on it. For example:
- A calculator picks
operations.add. - A menu picks the function for the command the user chose.
- A form checker keeps one function for each rule.
calculate(add) passes the function, so calculate can call it. calculate(add()) runs add first and passes its result, NaN, which can't be called.calculate(add) passes the function. calculate(add()) calls add first and passes its result. That result is NaN, not a function, so calculate can't call it. The only difference is the round brackets ().
Callbacks
STEP THROUGHA callback is a function you pass to another function, so that the other function can call it. The other function may call it:
- right away,
- once for each step of a loop,
- after a timer,
- when a user clicks.
The main point: you hand over the function, and the other function decides when to call it.
When a restaurant is full, you leave your phone number. You don't pick the minute they call. They call when your table is ready. A callback works the same way: you hand over a function, and the other code calls it at the right time.
- In real life: You leave your number at a restaurant
- In JavaScript: You pass a function as an argument
- In real life: The restaurant calls when your table is ready
- In JavaScript: The other function calls your callback when it chooses
- In real life: You don't phone yourself while you wait
- In JavaScript: Passing a function does not call it
A callback is a function you hand to other code. Step through to see that repeat decides when to call it.
script
function repeat(times, action) { for (let count = 1; count <= times; count++) { action(count); }} console.log("Turn " + number);});showTurn to repeat, where it is stored in the parameter action. Then repeat decides when to call it: once for each turn.While the callback runs, the replay's Running in row shows script → repeat → action. That means the main script called repeat, and repeat called the callback, action. All of this happens right now, in order. The line after repeat(...) waits until the loop is done. Code that runs in order like this is called synchronous.
Arrays work the same way. forEach is an array method that calls your function once for each item. You will learn map, filter, and reduce in Stage 3, but forEach already shows the callback idea:
const names = ["Ada", "Grace", "Linus"];
names.forEach(function greet(name) {
console.log("Hi, " + name);
});Line 3 passes the function greet to forEach. forEach calls it three times, once for each name. So it prints Hi, Ada, Hi, Grace, and Hi, Linus.
A button works the same way. In the next code, onClick acts like a tiny button: it gets a function and calls it.
function onClick(callback) {
callback();
}
function save() {
console.log("saved");
}
onClick(save);Line 9 passes the function save to onClick. On line 2, onClick calls it, so the code prints "saved". A real button uses the same shape: you pass a function now, and it is called later, when the click happens.
function onClick(callback) {
callback();
}
function save() {
console.log("saved");
}
onClick(save());Here line 9 has round brackets after save. So save runs first and prints "saved". It gives back undefined, and onClick gets that. Then line 2 throws TypeError: callback is not a function.
A timer callback runs later. setTimeout takes a function and a wait time in milliseconds. It keeps the function, waits, and then calls it. The event loop lesson explains the details. For now, remember this: the function is handed over now and called later.
console.log("first");
setTimeout(function onTimer() {
console.log("later");
}, 0);
console.log("second");This prints first, then second, then later. Even with a wait time of 0, the timer's function runs only after the rest of the script is done. Code that runs later like this is called asynchronous.
Browser events work the same way. An event is something that happens on the page, like a click. A later lesson covers events. For now, read button.addEventListener("click", callback) as: when a click happens, call this function.
Anonymous functions and named function expressions
An anonymous function is a function with no name after the word function. It is common for a short function that is used only where it is written:
repeat(3, function (number) {
console.log("Turn " + number);
});That is fine for a short callback. But a name after function helps in two ways:
- The function can call itself by that name. A function that calls itself is called recursive.
- The name shows up in error messages, so you can find the broken function faster.
A function expression with its own name is called a named function expression:
const factorial = function fact(n) {
if (n <= 1) return 1;
return n * fact(n - 1);
};
console.log(factorial(4));
console.log(typeof fact);Line by line:
- Line 1 stores the function in the variable
factorial. The function's own name isfact. - Line 2 stops when
nis 1 or less, and returns 1. - Line 3 uses the name
factto call the function again, with a number one smaller. - Line 6 prints
24, because 4 × 3 × 2 × 1 is 24. - Line 7 prints
"undefined". The namefactworks only inside the function, not outside. For a name that doesn't exist,typeofgives"undefined"instead of throwing an error.
A name also helps when something goes wrong. When an error is thrown, JavaScript records which functions were running. This list is called a stack trace, and you can read it in error.stack.
The next code uses try and catch. The try block runs some code. If that code throws an error, the catch block gets the error. Here, validateCart throws, and the catch block checks that the stack trace contains the name validateCart. It prints true.
const validate = function validateCart() {
throw new Error("missing price");
};
try {
validate();
} catch (error) {
console.log(error.stack.includes("validateCart"));
}A clear name in a stack trace is easier to spot than the word anonymous. The Reading error messages lesson shows how to read a whole stack trace.
Where you’ll use this
Function expressions and callbacks show up whenever code needs to change what a tool does, without rewriting the tool. Here are three common patterns:
- Lookups: keep functions in an object, then pick one by its name. This replaces a long chain of
ifstatements. - Repeated work: write
repeat(times, action)once. Then pass a different function to print, draw, or save. - Later work: pass a function to a timer or to a click listener. The browser calls it when the time is up or when the user clicks.
A function can also return another function. This works because functions are values. Sometimes the returned function keeps using variables from the place where it was made. That is called a closure, and it has its own lesson.
Common misconceptions
"A function expression runs when it is stored."
Storing a function does not run it. The code inside runs only when something calls it with round brackets.
"Callbacks always run later."
Timer and event callbacks run later. But forEach and this lesson's repeat call their callback right away, in order, before they finish.
"Declarations and expressions are set up the same way."
You can call a declaration above its line. A const expression can't be used before its line. A var expression is only undefined there.
"An anonymous function makes errors impossible to read."
JavaScript can often take a name from the variable, as in const greet = function () {}. But a name written after function still makes stack traces and recursive calls clearer.
"Passing a function and passing its result are almost the same."
They are opposites. calculate(add) hands over the function. calculate(add()) runs add too early and hands over its result.
Practice: function values
5 EXERCISESGuess the error name before you run the code.
greet();
const greet = function () {
console.log("hi");
};This throws ReferenceError. Line 1 runs while greet is still in the temporal dead zone, before its const line. A declaration would work here, but this is a function expression stored in a const.
Write a function that takes a starting value and a callback. It should call the callback twice, feeding the first result into the second call.
function applyTwice(value, action) {
return action(action(value));
}
function double(number) {
return number * 2;
}
console.log(applyTwice(5, double));function applyTwice(value, action) {
return action(action(value));
}
function double(number) {
return number * 2;
}
console.log(applyTwice(5, double));applyTwice(5, double) calls double(5) to get 10. Then it calls double(10) to get 20. The callback is the function that applyTwice uses twice.
This code passes the result of add() instead of the function itself. Fix it so calculate gets the function.
console.log(calculate(add()));function add(a, b) { return a + b; }
function calculate(operation) {
return operation(4, 2);
}
console.log(calculate(add));calculate(add) passes the function. Then calculate calls it with 4 and 2, so the program prints 6.
shout used to be a function declaration, so calling it on line 1 worked. Now it is a const function expression. What changes?
shout("ready");
const shout = function (word) {
return word.toUpperCase();
};Once the helper is const shout = function (...) { ... }, the call must move below that line. If it stays above, JavaScript throws ReferenceError, because the const is not ready yet.
Make an object with three functions: add, subtract, and multiply. Pick multiply and call it.
const operations = {
add: function (a, b) { return a + b; },
subtract: function (a, b) { return a - b; },
multiply: function (a, b) { return a * b; },
};
const chosen = operations.multiply;
console.log(chosen(6, 7));const operations = {
add: function (a, b) { return a + b; },
subtract: function (a, b) { return a - b; },
multiply: function (a, b) { return a * b; },
};
const chosen = operations.multiply;
console.log(chosen(6, 7));The object holds three functions. operations.multiply gets one of them without calling it. Then chosen(6, 7) calls it and prints 42.
Quiz: check your understanding
7 QUESTIONSRead each code example carefully. Watch for round brackets, and for how each function is made.
Question 1 of 7What is a function expression?
Choose an answer to see the explanation.
Question 2 of 7What does this code print? Line 1 calls the function before its declaration.
Read the code, then predictsayHi(); function sayHi() { console.log("hi"); }Choose an answer to see the explanation.
Question 3 of 7What does line 1 print?
Read the code, then predictconsole.log(typeof choose); var choose = function () {};Choose an answer to see the explanation.
Question 4 of 7What does
calculate(add)print?Read the code, then predictfunction add(a, b) { return a + b; } function calculate(operation) { return operation(4, 2); } console.log(calculate(add));Choose an answer to see the explanation.
Question 5 of 7What does this code print?
Read the code, then predictfunction repeat(times, action) { for (let count = 1; count <= times; count++) { action(count); } } repeat(2, function (n) { console.log("#" + n); });Choose an answer to see the explanation.
Question 6 of 7What does line 4 print, outside the function?
Read the code, then predictconst factorial = function fact(n) { return n <= 1 ? 1 : n * fact(n - 1); }; console.log(typeof fact);Choose an answer to see the explanation.
Question 7 of 7Which statement about callbacks is true?
Choose an answer to see the explanation.
Key takeaways
- Functions are values. You can store them, pass them, return them, and keep them in objects.
- A function expression makes a function value in a place where a value goes, like after
=. - You can call a declaration above its line. You can't call a
constfunction expression before its line. - A callback is a function you pass to another function, which calls it at the right moment.
- Anonymous functions are fine for short callbacks. A name helps with stack traces and with recursive calls.
Remember the one-liner.
A function expression makes a function value. A callback is that value, handed to other code to call.
Up next: Arrow functions. You'll learn a shorter way to write callbacks, and how arrow functions behave differently.