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.
- 01Create function valuesWrite function expressions and compare them with declarations.
- 02Pass behavior aroundStore functions in variables, objects, and arguments without calling too early.
- 03Use callbacksTrace a function that receives another function and calls it later.
Functions are values
You already know that a function packages code behind a name. This lesson adds the next idea: in JavaScript, a function is also a value. You can store it in a variable, keep it in an object, pass it to another function, return it from a function, or put it in an array just like a number or a string.
That is why typeof a function is "function", as the Data types lesson teaches. JavaScript gives function values one special power: you can call them with parentheses. Without parentheses, you are moving the recipe card around. With parentheses, you are cooking the recipe now.
A function expression creates a function value. A callback is a function value you pass to another function so it can call you back at the right moment.
Imagine a recipe card for pancakes. You can file it in a folder, mail it to someone, or hand it to a cook. None of those actions make pancakes yet. Cooking happens only when someone follows the card. Function values work the same way: add is the card; add(2, 3) follows 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 object
- In real life: Handing the card to a friend
- In JavaScript: Passing it as an argument
- In real life: Cooking from the card
- In JavaScript: Calling it with parentheses
Where the analogy stops: A recipe card is paper and a function is executable code. The analogy is about moving instructions around before anyone follows them.
This unlocks most modern JavaScript style. Array methods, browser events, timers, and many libraries ask you to provide behavior as a function. The deeper Higher-order functions lesson returns to this idea later; here we focus on the beginner skill: recognizing whether a function is being stored, passed, or called.
Function expressions
CORE IDEAA function declaration starts a statement:
function add(a, b) {
return a + b;
}A function expression creates a function value inside another expression. Most beginners first see it assigned to a const variable:
const add = function (a, b) {
return a + b;
};
console.log(add(2, 3)); // 5The right side, function (a, b) { ... }, is the value. The left side, add, is the variable that stores it. This is the same const rule from the Variables lesson: the variable cannot be reassigned, but the function value can still run whenever you call it.
Function expressions often appear where a value is expected: after =, inside an object, inside an array, or between the parentheses of a function call. Sort the shapes below before you read the answer explanations.
function a() {};const b = function () {};setTimeout(function () {}, 0);const c = function named() {};(function () {})();const tools = { run: function () {} };
Sort each line by the function shape it uses. The explanations point to the clue.
Declarations vs expressions
STEP THROUGHDeclarations and expressions can both create callable functions, but they are prepared differently before the script runs. A declaration is hoisted with its body, so it can be called before its line. A const function expression follows const rules: the variable is in the temporal dead zone until its line runs. An older var function expression starts as undefined, so calling it too early throws a different error.
Choose a setup, then step through what happens when line 1 calls a function before the definition line.
script
sayHello(); console.log("hello from declaration");}| Question | Declaration | Expression |
|---|---|---|
| Call before its line | Usually works; declarations are hoisted with their function body | const/let expressions throw ReferenceError before initialization; var expressions start as undefined |
| Name | The function’s name is required | Can be anonymous, inferred from a variable, or explicitly named for debugging |
| Typical use | Reusable named action in a scope | Passing behavior, storing in objects, choosing one function from many |
| Style | Reads like a sentence: here is a function | Reads like a value: store this function in a variable |
In the test for this lesson, calling a const function expression before initialization throws ReferenceError: Cannot access 'sayHello' before initialization. Calling a var expression before assignment throws TypeError: sayHello is not a function. TypeScript may warn before you run similar code; JavaScript’s runtime errors are shown in the replay.
When should you choose which? Use a declaration when you want a named helper to read like a normal part of the surrounding code. Use a function expression when the function is being treated as data: stored in a lookup, passed to a function, or created only for one spot.
First-class functions
INTERACTIVEFirst-class means functions get the same basic rights as other values. A number can be stored in a variable, passed to a function, returned, and kept in an object. A function can too. That is not a fancy pattern; it is a normal language rule.
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 stores three function values. The selected value is passed into calculate, then calculate calls it with 8 and 3.
The object in the playground is a small version of a pattern you will use constantly: choose data, then choose behavior. A calculator can pick operations.add; a menu can pick a handler for the current command; a validation system can keep one function per rule.
calculate(add) passes the function value. calculate(add()) calls add immediately and passes its result. If the result is not a function, calculate cannot call it. Parentheses are the difference.
Callbacks
STEP THROUGHA callback is a function you pass to another function so that other function can call it. The receiving function may call it right away, during a loop, after a timer, or when a user clicks. The key idea is control: you hand over the recipe card, and the other function decides when to use it.
When a restaurant is full, you leave your phone number. You do not decide the exact minute they call; they call when the table is ready. A callback works like that: 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 the table is ready
- In JavaScript: The receiving function calls your callback when it chooses
- In real life: You do not call yourself from the host stand
- In JavaScript: Passing a function is different from calling it immediately
Where the analogy stops: A phone call happens later in real life. Some JavaScript callbacks, like the repeat example, run synchronously before the outer function returns.
A callback is a function you hand to someone else. Step through repeat to see that repeat chooses when to call it.
script
function repeat(times, action) { for (let count = 1; count <= times; count++) { action(count); }} console.log("Turn " + number);});The call stack shown in the replay says script → repeat → action. That means the main script called repeat, and repeat called the callback. This is still synchronous: the loop finishes before the next line after repeat(...) would run.
Arrays use the same shape. You will learn map, filter, and reduce in Stage 3, but this simple forEach already shows the callback idea:
const names = ["Ada", "Grace", "Linus"];
names.forEach(function greet(name) {
console.log("Hi, " + name);
});A timer callback runs later. This lesson is not the event loop lesson, so keep the honest summary small: setTimeout receives a function now, the host waits, then it calls that function later.
console.log("first");
setTimeout(function onTimer() {
console.log("later");
}, 0);
console.log("second");Browser events follow the same idea. A later lesson covers events in detail, but button.addEventListener("click", callback) literally means: when the click happens, call this function.
Anonymous functions and named function expressions
An anonymous function has no name after the function keyword. It is common when the function is short and used right where it is created:
repeat(3, function (number) {
console.log("Turn " + number);
});An anonymous function is like a recipe card without a title. That can be perfectly fine for a small one-off callback. But names help humans. They appear in stack traces and make recursion easier:
const factorial = function fact(n) {
if (n <= 1) return 1;
return n * fact(n - 1);
};
console.log(factorial(4));
console.log(typeof fact);In that code, the outer variable is factorial. The inner function name fact is visible inside the function body, so the function can call itself. Outside, typeof fact prints "undefined". When you are reading an error stack, a helpful name is easier to recognize than anonymous; that connects directly to the Reading error messages lesson.
Where you’ll use this
Function expressions and callbacks show up anywhere code needs to customize behavior without rewriting the whole tool. Here are three beginner-friendly patterns:
- Lookups: keep operations in an object, then choose one by name instead of writing a long
ifchain. - Repeated work: write
repeat(times, action)once, then pass a different action for printing, drawing, or saving. - Later work: pass a function to a timer or event listener so the host can call it when time passes or the user acts.
A function can also return another function. When the returned function keeps variables from where it was created, that is a closure. You only need the idea here: returning a function is allowed because functions are values.
Common misconceptions
“A function expression runs when it is assigned.”
Assignment stores the function value. The body runs only when something calls it with parentheses.
“Callbacks are always asynchronous.”
Timer and event callbacks run later, but forEach and the lesson’s repeat callback run synchronously during the current call.
“Function declarations and expressions hoist the same way.”
Declarations are callable before their line. A const expression is not initialized yet, and a var expression is only undefined early.
“Anonymous means unusable for debugging.”
Modern engines can infer many names from variables, but an explicit name still makes stack traces and recursion clearer.
“Passing a function and passing its result are close enough.”
They are opposite actions. calculate(add) hands over behavior; calculate(add()) runs add too soon.
Practice: function values
5 EXERCISESPredict the error name before running the snippet.
greet();
const greet = function () {
console.log("hi");
};This throws ReferenceError because greet is in the temporal dead zone until the const line runs. A declaration would be callable here, but this is a function expression stored in a const.
Write a function that takes a starting value and a callback, then applies the callback twice.
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 double(10) to get 20. The callback is the behavior that applyTwice reuses.
The code passes the result of add() instead of the function. Fix it so calculate receives 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 value. Then calculate calls it with 4 and 2, so the program prints 6.
Explain what changes when a hoisted declaration becomes a const function expression.
shout("ready");
const shout = function (word) {
return word.toUpperCase();
};After the helper becomes const shout = function (...) { ... }, the call must move below that line. Otherwise JavaScript throws ReferenceError because the const has not been initialized.
Create an object with add, subtract, and multiply functions. 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 stores three functions. operations.multiply retrieves one function value, and chosen(6, 7) calls it, printing 42.
Quiz: check your understanding
7 QUESTIONSRead each snippet carefully. Parentheses and declaration shape matter.
Question 1 of 7What is a function expression?
Choose an answer to see the explanation.
Question 2 of 7What does this declaration print?
Read the code, then predictsayHi(); function sayHi() { console.log("hi"); }Choose an answer to see the explanation.
Question 3 of 7What happens here?
Read the code, then predictconsole.log(typeof choose); var choose = function () {};Choose an answer to see the explanation.
Question 4 of 7What does the correct
calculate(add)call 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 callback 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 this print outside the named function expression?
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, so they can be stored, passed, returned, and kept in objects.
- A function expression creates a function value inside a larger expression.
- Declarations are callable before their line;
constfunction expressions are not. - Callbacks are function values passed to another function to be called at the right moment.
- Anonymous functions are fine for short inline callbacks; names help stack traces and recursion.
Remember the one-liner.
A function expression is a function value; a callback is that value handed to someone else to call.
Up next: Arrow functions, where you’ll learn shorter callback syntax and the important differences arrows bring.