- Learn
- Computer Science & IT
- Programming
- JavaScript Programming
- JavaScript Functions
JavaScript Functions: Scope and Closures
return. Predict where a variable can be used with block, function and global scope, use closures to give functions their own memory, and pass functions to other functions as callbacks to write flexible higher-order code.BeginnerLesson 3 of 9 in JavaScript Programming Course
What you will learn
- Package a reusable piece of code as a function, call it with arguments, and choose between a function declaration, a function expression and an arrow function.
- Write and call a function declarationDefine a named function with the
functionkeyword, call it, and explain why functions make code shorter, clearer and easier to change. - Store a function in a variable with a function expressionTreat functions as values: create a function expression, assign it to a variable, and explain how it differs from a declaration in hoisting and naming.
- Write arrow functionsUse the short arrow syntax, know when an arrow function returns a value automatically, and explain how arrow functions differ from functions written with
function.
- Write and call a function declaration
- Design what goes into a function and what comes out: match arguments to parameters, set defaults, accept any number of arguments, return results and follow how calls run one inside another.
- Match arguments to parametersExplain how the values in a call reach the function's parameters, what happens with too few or too many arguments, and which of the caller's data a function can change.
- Set default values and accept any number of argumentsGive parameters default values, collect any number of arguments with a rest parameter, and spread an array into separate arguments.
- Return results from a functionSend a value back with
return, use the result in further code, and tell returning a value apart from printing it or changing something outside the function. - Follow function calls through the call stackTrace the order in which nested calls run and return, and write a simple recursive function with a base case.
- Match arguments to parameters
- Predict where a variable is visible by applying the rules for block, function and global scope and for functions written inside other functions.
- Tell block, function and global scope apartExplain the scope of a variable declared with
let,constorvar, and use local variables to keep functions independent of each other. - Predict which variable a name refers toFollow the scope chain to find the variable behind a name, recognize shadowing, and explain hoisting and the temporal dead zone.
- Tell block, function and global scope apart
- Explain how a function keeps access to the variables around it after the outer function has finished, and use that to build function factories and private state.
- Explain what a closure remembersDescribe how a function keeps access to the variables of the scope in which it was created, even after the outer function has returned.
- Use closures for function factories and private stateBuild function factories, keep data private behind functions, and avoid the classic closure-in-a-loop mistake.
- Explain what a closure remembers
- Use functions as values: hand them to other functions as callbacks, use and write higher-order functions, and build new functions from existing ones.
- Pass a callback to a functionGive a function to another function as an argument, write functions that call a callback, and pass the function itself rather than its result.
- Use and write higher-order functionsRecognize higher-order functions, use the array methods that take callbacks, and write your own function that takes a function as input.
- Build new functions from existing onesWrite higher-order functions that wrap, combine or pre-fill other functions, using closures to remember the function they were given.
- Pass a callback to a function
JavaScript Functions study guide
1Define and call functions in three forms
Functions turn repeated instructions into named tasks. You will read and write three common forms, then decide whether a line creates a function, stores one, or calls one. Small changes in syntax matter: a declaration is available early, while a stored expression must be initialized first, and an arrow body can change whether a result is returned. These distinctions prepare you to pass functions around as ordinary values.
1.1 Write and call a function declaration
A function gives a task a name and stores the instructions for doing it. Defining it does not run those instructions. A call runs them, and another call runs them again with new inputs.
Write the definition
A declaration has the keyword function, a name, parentheses for parameters, and curly braces for the body:
function greet(name) {
console.log("Hello, " + name + "!");
}
Here name is a parameter: a local name for the input each call supplies. The body joins that input to the surrounding text and prints the result. The closing brace marks the end of the definition; execution continues with the next statement outside it.
A function with no inputs still needs parentheses. For example, function sayHi() { console.log("Hi"); } defines a task with an empty parameter list. The braces belong to the definition; the parentheses after a name belong in both definitions and calls, with different jobs.
Run the task
greet("Amira");
greet("Li");
The first call gives name the value "Amira" and prints "Hello, Amira!". The second starts a new call with name equal to "Li" and prints "Hello, Li!". The definition is shared; the input belongs to the individual call.
greet by itself refers to the function value. It does not run the body. This distinction matters when a line seems to do nothing: sayHi; reads a function value and discards it, while sayHi(); prints "Hi".
Replace copied instructions
Suppose several reports need a heading surrounded by lines:
function printHeading(title) {
console.log("-----");
console.log(title);
console.log("-----");
}
printHeading("Report");
printHeading("Totals");
The first call prints a line, "Report", and another line. The second prints the same frame around "Totals". To change the line style, edit the two line-printing statements in this one definition. Without the function, every copied heading would need its own repair.
Choose a name that says what one call does. printHeading describes an action; isValidEmail names a yes-or-no check. Use camelCase, with a lowercase first word and uppercase starts for later words. If a proposed name joins two separate jobs with "and", consider giving each job its own function.
Read code that calls a declaration early
Declarations are set up before the surrounding code runs. This behavior is called hoisting:
sayHi();
function sayHi() {
console.log("Hi");
}
The call works within the declaration's scope even though the written definition comes later. Hoisting does not make the body run early; it makes the function available for a call.
Check spelling and capitalization when a call fails. Greet() and greet() are different names. Calling an undeclared greeet() raises a ReferenceError; omitting the call parentheses can fail silently because merely reading an existing function is valid code.
Key Takeaways
- Defining a function stores instructions; calling it runs them.
- Call with parentheses, even when there are no arguments.
- A declaration is available before its written position within the same scope.
- Name one clear task so repeated code has one place to change.
1.2 Store a function in a variable with a function expression
Functions are values. You can keep one in a variable, copy its reference, pass it to another function, or return it. A function expression creates that value where an expression is expected.
Store a function
const square = function (n) {
return n * n;
};
const answer = square(4);
The expression after = creates a function. The assignment stores it in square; the later call returns 16. This statement ends with a semicolon, following the formatting convention used here.
The position of function distinguishes the forms. A definition written as a standalone function square(n) { ... } is a declaration. A function written after = or directly as a call argument is an expression. Both ordinary function forms can accept parameters and return values.
Use const for a function variable when its value should stay the same. Calling square(5) is allowed; assigning square = 5; raises a TypeError. The constant protects the variable from replacement. It does not prevent repeated calls.
Keep a function or keep its result
const sq = square;
const result = square(3);
sq refers to the same function as square. Calling sq(3) returns 9. result holds the number 9 that one call returned. It cannot be called as a function. typeof square and typeof sq are "function"; typeof result is "number".
Reading an assignment from right to left helps: first decide whether its right side creates a function, refers to an existing function, or calls one. The variable on the left receives that value. Copying the reference does not create a second independent implementation.
Wait for the assignment to run
A declaration and a stored expression become callable at different times:
| Form | Before the written definition or assignment |
|---|---|
function square(n) { return n * n; } | Callable within its scope |
const square = function (n) { return n * n; }; | Reading square raises a ReferenceError |
var square = function (n) { return n * n; }; | square is undefined; calling it raises a TypeError |
With const or let, the name cannot be used before initialization. With var, the name exists early, but the function value is not assigned until execution reaches the expression. Hoisting the variable does not hoist the assignment.
Give an expression an internal name
const factorial = function fact(n) {
return n <= 1 ? 1 : n * fact(n - 1);
};
The outside name is factorial. The name fact belongs inside the function and lets it call itself; outside code cannot use that internal name directly. The expression's name can also make stack traces easier to read. For example, factorial(3) computes 3 times 2 times 1 and returns 6.
Declarations work well for the main named tasks in a script. Expressions fit places where code needs a function value, such as a variable assignment or an argument. Choosing an expression changes how the value is created and stored, not how parentheses call it.
Key Takeaways
- A function expression creates a function value inside another expression.
- Assigning
squarecopies a function reference; assigningsquare(3)stores its result. - A function expression stored in
constis callable only after initialization. - A
varfunction variable holdsundefinedbefore its assignment runs. - A named expression can use its internal name for self-calls.
1.3 Write arrow functions
Arrow functions are function expressions with a shorter spelling. Their return behavior depends on what follows the arrow, so inspect the body before deciding what a call produces.
Choose the parameter form
const sayHi = () => "hi";
const double = n => n * 2;
const add = (a, b) => a + b;
No parameters require empty parentheses. One simple parameter may omit parentheses. Two or more require them. A default or rest parameter also needs parentheses: (n = 1) => n * 2 and (...nums) => nums.length are valid.
The arrow separates inputs from the body. const double = n => n * 2; creates the function and stores it; double(6) later calls it and returns 12. Like a const holding any function expression, double cannot be read before its initialization line runs.
Return an expression or write a block
| Body | Example | Result of the call with 2 and 3 |
|---|---|---|
| Concise expression | (a, b) => a + b | 5 |
Block with return | (a, b) => { return a + b; } | 5 |
Block without return | (a, b) => { a + b; } | undefined |
A concise body returns its expression automatically. Curly braces begin a block of statements. In a block, evaluating an expression does not send it back to the caller; write return explicitly.
Use a block when the task needs intermediate work:
const describe = (n) => {
const kind = n % 2 === 0 ? "even" : "odd";
return n + " is " + kind;
};
For describe(7), the condition is false, so kind becomes "odd". The return then joins the number and words into "7 is odd". Removing that return would leave the local calculation in place but give the caller undefined.
Return an object literal
An object literal also uses curly braces. Wrap it in parentheses so the parser treats it as an expression:
const makePoint = (x, y) => ({ x: x, y: y });
makePoint(2, 5) returns an object with x equal to 2 and y equal to 5. Without the parentheses, the first brace begins a block. The single-property form () => { value: 1 } returns undefined; the similar two-property form with a comma is a syntax error. The parentheses change how the body is read.
Know the limits of the shorter form
Arrow functions do not have their own this or arguments, and they cannot be called with new to construct objects. For a simple calculation that uses neither this nor arguments, an arrow and an ordinary function expression behave the same. Replacing every ordinary function with an arrow can change code that depends on those features.
Short arrows are especially readable as callbacks. [1, 2, 3].map(n => n * 10) passes a function that returns a transformed element, producing [10, 20, 30]. Changing it to n => { n * 10; } silently loses each returned value.
Key Takeaways
- A concise arrow body returns its expression automatically.
- A block arrow body needs an explicit
returnto return a computed value. - Use parentheses around an object literal returned by a concise arrow.
- Empty, multiple, default, and rest parameter lists need parentheses.
- Arrows have no own
thisorargumentsand cannot be constructors.
2Pass values into functions and get results back
A function call has an input side and an output side. Match arguments to parameters, handle optional or flexible inputs, and trace the value returned to the caller. You will also distinguish local changes from changes to a shared array, and follow nested calls as their work pauses and resumes. The same call-and-return model explains simple recursion, where several calls of one function wait for smaller calls to finish.
2.1 Match arguments to parameters
A function's parameters give names to the values supplied by a call. To trace a call, match inputs by position first, then follow the operations in the body.
Match positions and types
function area(width, height) {
return width * height;
}
area(4, 2.5);
width and height are parameters; 4 and 2.5 are arguments. During this call, width holds 4 and height holds 2.5, so the result is 10. Parameter names do not match caller variable names. With divide(a, b), swapping divide(10, 2) to divide(2, 10) changes the result from 5 to 0.2.
JavaScript does not enforce the number of arguments. area(4) leaves height as undefined; multiplying by it produces NaN. area(4, 2, 99) returns 8 because this body has no use for the third argument. A function can choose to collect extra arguments, but unused extras do not cause an automatic error.
Types also matter even when the call is accepted. area("4", 2) returns 8 because multiplication converts the text. An addition function behaves differently: add("4", 2) returns "42" when its body is return a + b;. If text represents a number, add(Number("4"), 2) returns 6.
Reassign a local parameter
Each parameter is local to its call. Assigning a new number to it does not reassign the caller's variable:
let score = 5;
function reset(s) {
s = 0;
}
reset(score);
The call copies the value 5 into s. The assignment changes s to 0, and score remains 5. Numbers, strings, and booleans follow this value-copy rule. Code outside cannot read s directly; a nested function can retain access to it through a closure.
Change an array through a copied reference
An array value is a reference to an array. Passing that value copies the reference, so the caller and parameter can reach the same array:
const list = [1, 2];
function addItem(arr) {
arr.push(3);
}
addItem(list);
list is now [1, 2, 3]. The function did not reassign list; it changed the array that both names reach. A const declaration prevents replacing its stored reference, not changing the array's contents.
Compare a local reassignment:
function clearLocal(arr) {
arr = [];
}
clearLocal(list);
This points only arr at a new empty array. list still reaches [1, 2, 3]. Ask whether an operation changes an existing array, as push does, or gives a name a different value, as = does.
Return a new array when the input should stay intact
function withItem(arr, item) {
return arr.concat(item);
}
withItem([1, 2], 3) produces [1, 2, 3] as a new array. The input array remains [1, 2]. This makes the function useful when the caller needs both the old list and the extended list. Document a function's intended input changes through its name and behavior so callers can use it predictably.
Key Takeaways
- Arguments match parameters by position.
- A missing argument supplies
undefined; unused extra arguments are allowed. - Reassigning a parameter does not reassign the caller's variable.
- Mutating an array through a parameter changes the shared array.
- Returning a new array can preserve the input.
2.2 Set default values and accept any number of arguments
Defaults make an input optional; rest parameters let a call supply many inputs. Spread syntax connects an existing array to a function that expects separate arguments.
Apply a default only for undefined
function greet(name = "friend") {
return "Hello, " + name;
}
greet() and greet(undefined) both return "Hello, friend". greet(null) returns "Hello, null", and greet("") returns "Hello, ". A default tests for undefined, not for every value that looks empty.
Defaults are evaluated when a call needs them. They may use earlier parameters: function makeBox(width, height = width) { return width * height; } returns 9 for makeBox(3) and 12 for makeBox(3, 4). The explicit second argument replaces the default for that call.
Put required inputs first. With function total(amount, rate = 0.25) { return amount + amount * rate; }, total(80) returns 100 and total(80, 0.5) returns 120. Reversing the parameters would make the common default-rate call require total(undefined, 80).
Older code may use level = level || 50;. This also replaces 0, false, and an empty string because they are falsy. A default parameter preserves those values: function setVolume(level = 50) { return level; } returns 0 for setVolume(0) and 50 for setVolume(undefined).
Gather arguments with rest
function sum(...numbers) {
let total = 0;
for (const n of numbers) {
total += n;
}
return total;
}
sum(1, 2, 3) puts [1, 2, 3] into numbers and returns 6. sum() gives it an empty array, so the loop adds nothing and returns 0. A rest parameter is a real array, ready for array methods or iteration.
A fixed input can come before rest: in function log(label, ...values) { console.log(label, values); }, log("scores", 7, 9) gives label the string "scores" and values the array [7, 9]. Only one rest parameter is allowed, and it must be last; otherwise JavaScript cannot assign the remaining arguments unambiguously using this syntax.
Expand arguments with spread
Location of ... | Job | Example |
|---|---|---|
| Definition's parameter list | Gather remaining arguments | function sum(...numbers) { ... } |
| Call's argument list | Expand array elements | sum(...values) |
Math.max(...[3, 7, 2]) receives three numbers and returns 7. Math.max([3, 7, 2]) receives one array and returns NaN. The three dots are identical on the page, but the surrounding syntax tells you which job they perform.
Ordinary functions also have an arguments object containing the arguments passed to them. It is array-like, with indexed entries and a length, but it is not a real array and has no own map or filter methods. Arrows have no arguments of their own. Prefer a rest parameter for new code that needs a flexible list of inputs.
Key Takeaways
- Defaults apply to missing arguments and explicit
undefined, while preserving 0 andnull. - A default expression is evaluated for each call that needs it.
- One rest parameter gathers the remaining arguments into an array and comes last.
- Spread expands an array into separate call arguments.
- The older
argumentsobject is array-like; rest produces a real array.
2.3 Return results from a function
A returned value is the output other code receives from a call. Printing can show the same value on screen, but it gives the caller a different result unless the function also returns it.
Use the call as a value
function double(n) {
return n * 2;
}
const x = double(5) + 1;
double(5) returns 10, so the assignment evaluates as 10 plus 1 and stores 11. A call can appear anywhere an appropriate value is needed. double(double(3)) first gets 6 from the inner call, then returns 12 from the outer one. Math.round(double(2.3)) doubles to 4.6 and rounds to 5.
return also ends the current call immediately. A guard clause handles a special case before the main work:
function safeDivide(a, b) {
if (b === 0) {
return null;
}
return a / b;
}
safeDivide(6, 0) returns null and never reaches the division. safeDivide(6, 2) skips the guard and returns 3. Put the condition before the operation it protects.
Separate output from display
function addLog(a, b) {
console.log(a + b);
}
addLog(2, 3) prints 5, but its return value is undefined. Consequently, addLog(2, 3) * 2 prints 5 and evaluates to NaN. A function that reaches its end without a return, or executes a bare return;, returns undefined. The console display is a side effect, not the call's usable numeric output.
If a caller needs a sum, write return a + b;. The caller can then choose to print it, store it, or use it in another calculation. That keeps display decisions out of a reusable calculation.
Keep the returned expression on the same line
function brokenAdd(a, b) {
return
a + b;
}
JavaScript ends the return statement at the line break. The function returns undefined; the later expression is never reached. For a long expression, start it or open a parenthesis on the return line, then continue below.
Package several results into one value
A call returns one value, but that value can be an array:
function minMax(nums) {
return [Math.min(...nums), Math.max(...nums)];
}
const [low, high] = minMax([4, 9, 1]);
The returned array is [1, 9]. Destructuring assigns its first entry to low and its second to high. There is still one returned value, containing two useful results.
A pure function computes its output from its arguments and changes nothing outside itself. double is pure. Printing, updating an outer variable, and mutating an input array are side effects. Pure calculations are easy to check with input-output examples; side effects belong in clearly named functions where the program needs visible changes.
Key Takeaways
returnsupplies the call's value and ends the current call.- Printing alone leaves the function's return value as
undefined. - Start the returned expression on the
returnline. - One returned array can carry several results.
- Pure functions depend on their arguments and make no outside changes.
2.4 Follow function calls through the call stack
When one function calls another, the caller pauses until that call returns. The call stack records the unfinished work so execution can resume in the right place.
Add and remove call frames
Each active call has a frame holding its parameters, local variables, and return location. A new call goes on top of the stack; a return removes that top frame.
function square(n) {
return n * n;
}
function sumOfSquares(a, b) {
return square(a) + square(b);
}
sumOfSquares(3, 4);
First, sumOfSquares begins with a equal to 3 and b equal to 4. It calls square(3), which returns 9 and finishes. Then square(4) runs and returns 16. The paused addition can now finish as 9 plus 16, and sumOfSquares returns 25. The two square calls do not overlap in this trace.
Each call gets fresh parameters and local variables. Calling the same function twice does not reuse a previous call's local numeric state. Even when a function calls itself, the new call has its own frame.
Reduce a problem with recursion
A recursive function calls itself. A base case finishes without another self-call; a recursive case takes a step toward that base case.
function factorial(n) {
if (n <= 1) {
return 1;
}
return n * factorial(n - 1);
}
For nonnegative integer inputs, this multiplies the integers down to 1. Trace factorial(4) in two directions:
factorial(4)waits forfactorial(3).- That call waits for
factorial(2). - That call waits for
factorial(1). - The base case returns 1.
- The waiting calls return 2, then 6, then 24.
The multiplication in the call with n equal to 4 happens after the smaller call returns. Its own n has stayed 4 while it waited.
A base case must be reachable. Using only n === 1 fails for a starting input of 0 if every recursive call subtracts 1: the values become negative and never reach 1. n <= 1 catches both 0 and 1. Without a stopping path, repeated calls exhaust the stack. Chrome and Node.js report "RangeError: Maximum call stack size exceeded"; Firefox uses "InternalError: too much recursion".
Recognize a smaller copy of the same task
function countItems(list) {
let count = 0;
for (const item of list) {
if (Array.isArray(item)) {
count += countItems(item);
} else {
count += 1;
}
}
return count;
}
countItems([1, [2, 3], [4, [5]]]) returns 5. Each nested array asks the same counting question of a smaller list. A loop is also suitable for a simple factorial; recursion is especially useful when the data itself is nested.
If a call fails, read the stack trace from the most recent call at the top. The following entries show which unfinished callers led there. Repeated entries for one function can reveal recursion that failed to stop.
Key Takeaways
- The call stack tracks unfinished calls; returns remove the most recent frame.
- Each invocation has its own parameters and local variables.
- Recursion needs a stopping case and progress toward it.
- Recursive results return through the waiting calls in reverse order.
- A stack trace shows the failing call and the callers that led to it.
- Nested data can contain smaller instances of the same task.
3Explain where a variable can be used
Variable names are meaningful within scopes. Block boundaries, function boundaries, and the place a function is defined determine which declarations it can reach. You will trace those boundaries to predict a value or an error, then distinguish shadowing from updating an outer variable. Hoisting and the temporal dead zone explain why a name can be registered before execution reaches its line yet still be unavailable to use.
3.1 Tell block, function and global scope apart
A variable's scope is the region where its name can be used. Scope lets functions keep their working data separate while still using inputs and values from surrounding code.
Draw boundaries around declarations
let and const are block-scoped. A block is a region inside curly braces, such as an if body, a loop body, or a function body:
if (true) {
const msg = "hi";
console.log(msg);
}
The print works inside the block. Adding console.log(msg); after it raises a ReferenceError because that outer code cannot reach this declaration. A different outer declaration with the same name would be a different variable.
Parameters and variables inside a function are local to that function. In function f() { let temp = 1; }, outside code cannot read temp. Two functions can each have a local total without sharing it. Each call starts with fresh local variables; a returned nested function can later preserve access through a closure.
Read older var declarations
| Declaration | Boundary |
|---|---|
let or const inside a block | That block |
var inside a function | The enclosing function, even when written in an inner block |
| Top-level declaration in a regular script | The script's global scope |
| Top-level declaration in a module | That module |
function test() {
if (true) {
var x = 1;
}
return x;
}
test() returns 1 because var ignores the if boundary. Replacing it with let x = 1; makes the return fail: the block's x is unavailable there. Similarly, for (let i = 0; i < 3; i++) {} keeps i within the loop, while a var i remains available in its enclosing function or script after the loop and holds 3.
Look outward from the function's written position
Scopes nest. An inner function can use names in its enclosing scopes, but outer code cannot search inward into an inner function's locals. This is lexical scope: where the code is written determines the available names.
const level = "global";
function show() {
return level;
}
function run() {
const level = "local";
return show();
}
run() returns "global". show was defined outside run, so its enclosing scope contains the outer level. Calling it from inside run does not give it access to that caller's local level.
Keep shared state deliberate
A top-level variable in a regular script is global and can be reached by functions within that script. Shared globals can make independent tasks affect one another; scripts on the same page can also clash over names. Passing inputs and returning outputs keeps dependencies visible.
Declare every variable. In strict mode, count = 5; throws a ReferenceError if no accessible declaration exists. Without strict mode, such an assignment can create an accidental global. A script can enable strict mode with "use strict"; at its start; modules enable it automatically. Prefer const for values that are not reassigned and let when reassignment is needed.
Key Takeaways
letandconstobey block boundaries;varobeys the enclosing function boundary.- Function parameters and local variables are unavailable to outside code.
- Lexical scope follows where a function is defined, not where it is called.
- Top-level module variables belong to the module.
- Declare names explicitly to prevent accidental global assignments.
3.2 Predict which variable a name refers to
The same name can occur in several scopes. JavaScript finds the relevant declaration by looking outward from the current code, stopping at the first match.
Follow the scope chain
let x = 1;
function f() {
let x = 2;
return x;
}
f() returns 2. The local declaration shadows the outer x, so the search stops inside f. The outer value stays 1. A parameter can shadow a name in the same way. If no scope on the chain declares a name, reading it raises a ReferenceError.
An assignment does not declare a new variable. With let count = 0; function add() { count = count + 1; }, add finds the outer declaration and changes it. Inserting let changes the meaning: it creates a new local name, which may hide the very value you meant to read.
Separate registration from initialization
Hoisting describes how declarations are registered before their scope runs. Their early behavior differs:
| Declaration | Before its initialization line |
|---|---|
| Function declaration | Its function is available |
var | Its value is undefined |
let or const | Access raises a ReferenceError |
For example, console.log(a); var a = 5; prints undefined. The assignment to 5 has not happened yet. console.log(b); let b = 5; fails instead. The interval when the lexical name exists but cannot be accessed is its temporal dead zone, often shortened to TDZ.
Trace shadowing before a declaration line
let n = 10;
function f() {
console.log(n);
let n = 20;
}
f();
The inner n shadows the outer one throughout the function body, including before the declaration's written line. At console.log(n), that inner name is still in its TDZ, so the call throws a ReferenceError. JavaScript does not skip an uninitialized local declaration and fall back to the outer value 10.
The same problem appears in let count = count + 1; inside add. The right side reads the new local count before it has been initialized. If the intention is to update an outer count, use an assignment without a new declaration. If the intention is to calculate a separate value, give it a different name and read the outer count explicitly.
Distinguish reassignment from mutation
let and const cannot redeclare the same name in one scope; doing so is a SyntaxError. An inner scope may declare its own name, which is shadowing. var allows repeated declarations in its scope, making accidental name reuse harder to notice.
const blocks reassignment of its variable. It does not freeze an array:
const list = [];
list.push(1);
The reference stored in list stays the same while the array changes to [1]. list = []; would try to replace the stored reference and would fail.
Keep a declaration in the smallest scope that needs it. Distinct names for distinct jobs reduce the need to mentally track shadowing, and initializing before use avoids TDZ failures.
Key Takeaways
- Name lookup stops at the nearest enclosing declaration.
- A local declaration shadows an outer name even before initialization.
varbegins asundefined;letandconstare inaccessible in their TDZ.- Assignment updates an existing binding; declaration creates one.
constprevents reassignment, while array contents can still change.- Redeclaration in one scope differs from shadowing in a nested scope.
4Use closures to give functions their own memory
A returned function can keep access to the variables around its definition. That retained connection gives a counter memory and lets a factory build functions with their own settings. You will trace separate factory calls, shared state, and later changes to captured variables, then use the same rules to explain functions created in a loop. Private state follows from ordinary lexical scope, even after the outer call has returned.
4.1 Explain what a closure remembers
A function keeps a link to the scope where it was created. The function and that retained access form a closure. The link becomes especially useful when a nested function is returned and called after its outer call has finished.
Give a returned function memory
function makeCounter() {
let count = 0;
return function () {
count = count + 1;
return count;
};
}
const counter = makeCounter();
The call to makeCounter creates count, creates an inner function, and returns that function. It does not run the inner body yet. counter now holds the function value. Its first call changes the retained count from 0 to 1 and returns 1. Its next call changes the same variable to 2 and returns 2.
The outer call's stack frame has finished, but the captured variable remains accessible. A local variable that no longer has any reachable use can be released; here the callable inner function still needs it. Closure access survives a return without making the variable global.
Create independent memory with separate calls
const a = makeCounter();
const b = makeCounter();
a();
a();
b();
The results are 1, 2, and 1. Each factory call created a different count. The two functions returned from separate calls have the same code but different retained environments.
Copying a returned function reference works differently. If const alias = a;, then alias() uses a's existing count; it does not call makeCounter and create another environment.
Keep variables, not snapshots
let greeting = "Hi";
const say = () => greeting;
greeting = "Hello";
say() returns "Hello". The closure reads the variable when it runs, so a later update is visible. It does not store a frozen copy of "Hi" at creation time.
Outside code cannot access the local count directly, and counter.count is undefined. Only code with access to that lexical environment can read or update it. Calling the counter from a different function does not change which count it uses.
Share state within one outer call
function makeWallet() {
let balance = 0;
return [
function deposit(n) { balance += n; return balance; },
function getBalance() { return balance; }
];
}
const [deposit, getBalance] = makeWallet();
deposit(50);
deposit(20);
getBalance() returns 70 because both returned functions were created in the same call and refer to the same balance. Another call to makeWallet() creates a separate balance.
Key Takeaways
- A closure retains access to the scope where its function was created.
- A captured variable can remain usable after its outer call returns.
- Separate factory calls create separate environments.
- Closures read current variable values, not creation-time snapshots.
- Functions from the same outer call can share private state.
4.2 Use closures for function factories and private state
A function factory uses closure access to create functions with settings or state of their own. The caller supplies the configuration once, then uses the returned function many times.
Configure a function with a parameter
function makeMultiplier(factor) {
return (n) => n * factor;
}
const double = makeMultiplier(2);
const triple = makeMultiplier(3);
double(5) returns 10 and triple(5) returns 15. Each returned arrow retains the factor from its own factory call. The outer function configures the behavior; the inner one performs the repeated work.
The compact form is const makeMultiplier = (factor) => (n) => n * factor;. Two arrows mean two function creations, not two consecutive calculations on one number. makeMultiplier(4)(5) first returns a function configured with 4, then calls it with 5 to get 20.
Enforce a rule around private state
function makeLimiter(max) {
let used = 0;
return function () {
if (used >= max) {
return false;
}
used = used + 1;
return true;
};
}
const tryLogin = makeLimiter(3);
The first three calls return true and increment used. The fourth returns false before incrementing. used is local to the factory call, so outside code cannot reset it through that name. The returned function controls which changes are available. Another limiter would have its own used variable and its own maximum.
Diagnose functions created in loops
const fns = [];
for (var i = 0; i < 3; i++) {
fns.push(() => i);
}
The loop creates three functions but only one var i. After the loop, that variable holds 3. Calling any stored function now returns 3 because every closure reads the same current binding. Creating a function does not evaluate its body or snapshot the current i.
Changing the header to let i = 0 gives the loop a separate binding for each iteration. The stored functions then return 0, 1, and 2. The same distinction explains delayed printing: a setTimeout callback that reads var i after the loop sees the final value, while the let version retains each iteration's value.
Recognize an immediately invoked expression
Older code often creates a private function scope and runs it immediately:
(function () {
var hidden = 1;
console.log(hidden);
})();
This is an immediately invoked function expression, or IIFE. The outer parentheses make a function expression; the final parentheses call it. hidden belongs to that function, keeping it out of the surrounding global scope. It is a common pattern in code written before block-scoped declarations and modules.
Retained access also affects memory. A callable closure that needs a large array keeps that data reachable. When the function and its captured data are no longer reachable, they can be released. Store long-lived closures deliberately when their memory is part of the task.
Key Takeaways
- Factories return functions configured by their arguments.
- Private variables can be changed through the returned operations.
varloop closures share one binding;letloop iterations get separate bindings.- An IIFE creates a function scope and runs it immediately.
- Reachable closures keep the captured data they need reachable.
5Pass functions to other functions
Passing a function lets a caller choose part of another function's behavior. You will inspect how receivers invoke callbacks, which arguments they supply, and how they use the result. Familiar array methods then become examples of reusable higher-order patterns. Finally, returned functions will wrap calls, connect outputs to inputs, or hold selected arguments ready for later use, combining function values with closure access.
5.1 Pass a callback to a function
A callback is a function supplied as an argument for another function to call. It lets the receiving function handle the overall task while the caller supplies a particular action or test.
Pass an action to a loop
function repeat(times, action) {
for (let i = 0; i < times; i++) {
action(i);
}
}
repeat(3, (i) => console.log(i));
repeat owns the loop and chooses the values 0, 1, and 2. The arrow owns what to do with each value: print it. Each action(i) calls the supplied function with that iteration's number.
You can pass an existing function or write one inline. repeat(2, sayHi) passes the named function. repeat(2, (i) => console.log("Round " + (i + 1))) prints "Round 1" and "Round 2". A callback may ignore arguments it does not need.
Pass the value, not an early call
repeat(3, sayHi()) evaluates sayHi() while preparing the arguments. If sayHi only prints, its return value is undefined, so repeat receives that value as action. The later action(i) fails with a TypeError because undefined is not callable.
Use repeat(3, sayHi) to hand over the function. If the eventual call needs chosen arguments, pass a wrapper such as () => sayHi() so the call happens inside the callback body. Function creation and function invocation are separate steps.
Check what arguments the receiver supplies
Array callbacks often receive more than one argument: element, index, and the whole array. Passing an existing function is safe only when those inputs fit its parameter meanings.
["1", "2", "3"].map(parseInt);
This returns [1, NaN, NaN]. parseInt accepts a second argument for the number base, so the array index changes its behavior. Use ["1", "2", "3"].map((s) => parseInt(s, 10)) to choose base 10 explicitly, or .map(Number) to convert these strings.
Return an answer to the receiver
function countIf(arr, test) {
let n = 0;
for (const x of arr) {
if (test(x)) {
n++;
}
}
return n;
}
countIf([3, 8, 12, 5], (x) => x > 4) returns 3. The callback answers a question for each item; countIf uses each answer to decide whether to increment. Compare repeat, whose loop ignores the callback's return value. The receiving function's code or documentation tells you both the arguments and the role of the result.
Callbacks can run immediately or later. repeat and array methods call them synchronously before returning. setTimeout(sayHi, 2000) schedules one for later while following code continues immediately. Being a callback describes how a function is supplied, not when it must run.
Key Takeaways
- A callback is a function passed for another function to call.
- The receiver controls callback arguments and whether its result is used.
- Adding call parentheses too early can pass a result instead of a function.
- Array callback arguments can change the behavior of an existing function.
- Callbacks can be synchronous or delayed.
5.2 Use and write higher-order functions
A higher-order function accepts a function, returns a function, or does both. The function value supplies behavior, while the higher-order function manages a reusable pattern.
Write the pattern once
function myMap(arr, transform) {
const result = [];
for (const item of arr) {
result.push(transform(item));
}
return result;
}
myMap([1, 2, 3], (n) => n * n) returns [1, 4, 9]. The loop and output-array construction stay the same; the callback decides how each entry changes. A matching filter pushes an item only when test(item) is truthy. With [4, 7, 10, 3] and (n) => n % 2 === 0, that pattern keeps [4, 10].
Choose the callback contract you need
| Method | Callback's job | Method's result |
|---|---|---|
map | Return each transformed value | A new array of the same length |
filter | Return a truthy value for items to keep | A new array of selected items |
find | Test for a matching item | First match, or undefined |
forEach | Perform an action for each item | undefined |
reduce | Return the next accumulator | The final accumulator |
map, filter, and find do not themselves modify the input array, but callbacks can still mutate it. Returning a new array from a method does not make every supplied callback pure.
Carry a result through a reduction
[5, 10, 15].reduce((total, n) => total + n, 0);
The starting accumulator is 0. The callback returns 5 for the first item, 15 for the second, and 30 for the third. Each return becomes the next call's total. The method returns 30 after the last item. An empty array without a starting accumulator makes reduce throw a TypeError; an explicit starting value also defines the useful empty-input result.
Returning arrays makes chaining possible. [3, 8, 12, 5].filter((n) => n > 4).map((n) => n * 2) first keeps [8, 12, 5], then transforms it into [16, 24, 10]. Read the chain in execution order from left to right.
Supply a numeric sort rule
Without a comparator, sort compares entries as strings. [10, 9, 1].sort() therefore produces [1, 10, 9]. For ascending numeric order, use .sort((a, b) => a - b). A negative return puts a first, a positive return puts b first, and zero leaves the pair equivalent for ordering. Unlike map and filter, sort rearranges its input array.
Check the returned values
[1, 2, 3].map((n) => { n * 2; }) yields [undefined, undefined, undefined] because the block has no return. Using forEach to build a new result by assignment also fails if you expect the method itself to return an array. Choose map for transformation and forEach for actions whose collected outputs are not needed.
A higher-order method states a familiar pattern clearly. A plain loop remains useful when work needs break or does not fit the method's contract.
Key Takeaways
- Higher-order functions take functions, return functions, or both.
map,filter, andfinduse callback results in different ways.forEachreturnsundefined;reducereturns the final accumulator.- A reduction callback must return the next accumulator.
- Numeric sorting needs a comparator and mutates the original array.
- A method that returns a new array can still run callbacks with side effects.
5.3 Build new functions from existing ones
A function can return a new function that remembers an existing one. Wrappers, composition, and partial application use this pattern to adapt behavior without copying the original function's body.
Wrap a call and preserve its result
function logged(fn) {
return (...args) => {
console.log("Calling with", args);
const result = fn(...args);
console.log("Result", result);
return result;
};
}
const add = (a, b) => a + b;
logged(add) creates a function that retains add as fn. Calling logged(add)(2, 3) gathers the arguments, forwards them to add, prints the returned value, and returns 5. Rest gathers the wrapper's inputs; spread forwards them. The final return keeps the output available to later calculations.
logged(add(2, 3)) passes the number 5 instead. Calling that wrapper later tries to call 5 and throws a TypeError. There are two stages: configure the wrapper with a function, then call the returned function with inputs.
Retain state around an operation
function once(fn) {
let done = false;
let result;
return (...args) => {
if (!done) {
done = true;
result = fn(...args);
}
return result;
};
}
For an operation that returns normally, the first call runs fn and stores its result. Later calls return that stored value without rerunning fn, even if their arguments differ. The boolean tracks whether the operation ran; checking the result's truthiness would be wrong when a valid result is 0 or false. Each call of once creates its own flag and stored result.
Feed one result into another function
const compose = (f, g) => (x) => f(g(x));
const addOne = (n) => n + 1;
const square = (n) => n * n;
compose(square, addOne)(3) runs addOne(3) first, then squares 4 to return 16. compose(addOne, square)(3) squares 3 first and then adds 1, returning 10. The function nearest the input runs first, so the order of the arguments to compose matters.
Fix some arguments in advance
function partial(fn, ...fixed) {
return (...rest) => fn(...fixed, ...rest);
}
const greetWith = (greeting, name) => greeting + ", " + name;
const hello = partial(greetWith, "Hello");
hello("Ana") calls greetWith("Hello", "Ana") and returns "Hello, Ana". This partial helper puts fixed arguments first and later arguments afterward; changing that order changes the underlying call.
Key Takeaways
- A wrapper retains a function and adds behavior around its call.
- Rest and spread let a wrapper forward separate arguments.
- Returning the wrapped result keeps it usable by the caller.
- A run-once wrapper retains both a flag and the first result.
compose(f, g)appliesgbeforef.- Partial application fixes selected arguments before later calls.
JavaScript Functions examples
1Create, store, and call a function
11 steps
- 1IntroductionSeparate the recipe from the call
A function stores instructions so several calls can reuse them. Its definition describes a task; a call performs that task with particular inputs.
Follow a greeting through three forms: a declaration, an expression stored in a variable, and an arrow. Watch two details as the syntax changes: when the function becomes available and whether its body sends a value back.
- 2ExampleDefine a greeting
function greet(name) { console.log("Hello, " + name + "!"); }The keyword, name, parameter list, and body form a declaration. Nothing has printed yet: the definition stores the instructions.
nameis a parameter whose value will come from a call. The body can be reused because it describes how to build the greeting without choosing one person's name in advance. - 3ExampleRun the stored instructions
greet("Amira"); greet("Li");The calls print "Hello, Amira!" and "Hello, Li!" on separate lines. Each starts the same body again with its own input.
The expression
greetalone is the function value. The parentheses ingreet("Li")request execution. Even a zero-input function needs call parentheses, as insayHi(). - 4ChallengePredict the early call
What prints when this script runs?
show(); function show() { console.log("Ready"); }---reveal---
It prints "Ready" once. The declaration is hoisted within its scope, so the function is available at the first line. Hoisting does not run the body by itself: the explicit
show()call does. A line containing onlyshow;would read the function without printing. - 5ExplanationKeep the function as a value
const square = function (n) { return n * n; }; const alias = square;The expression creates a function, and
squarestores it. The second assignment copies its reference intoalias; it does not calculate a square. Bothsquare(3)andalias(3)return 9.By comparison,
const answer = square(3);calls the function and stores the returned number. - 6ChallengeLocate the first failure
Does this script print 16?
console.log(square(4)); const square = function (n) { return n * n; };---reveal---
No. Reading
squarebefore itsconstinitialization raises aReferenceError. The assignment has not run, so the expression's function value is unavailable. Move the call below the assignment, or use a function declaration if the function needs to be callable earlier in this scope. - 7ExampleUse a concise arrow
const square = (n) => n * n; const add = (a, b) => a + b;A concise arrow body returns its expression, so
square(4)returns 16 andadd(2, 3)returns 5.One simple parameter may omit parentheses:
n => n * n. Empty lists, multiple parameters, defaults, and rest parameters need them. Arrows are expressions, so theseconstvariables still need initialization before use. - 8ChallengeRepair a missing result
Why does
add(2, 3)returnundefined, and what change makes it return 5?const add = (a, b) => { a + b; };---reveal---
The braces begin a block, so the expression is evaluated but not returned. Write
(a, b) => { return a + b; }, or remove the braces to use(a, b) => a + b. Both repairs return the sum. Merely adding a semicolon after the assignment does not change the body's return behavior. - 9TipReturn an object expression
const makePoint = (x, y) => ({ x: x, y: y });Parentheses make the object literal an expression returned by the concise body. An unparenthesized opening brace would begin a block instead.
Arrows have no own
thisorarguments, and they cannot be constructors. The short form works well for simple calculations, but those differences matter when converting other kinds of functions. - 10ChallengeFollow a function and its alias
What does
copy(5)return?const adjust = (n) => { return n + 2; }; const copy = adjust;---reveal---
It returns 7. The arrow's block contains an explicit return, and
copyholds the same callable function asadjust. The assignment did not call the function or store a numeric result. Replacingconst copy = adjust;withconst copy = adjust(5);would store 7, which is not callable. - 11RecapRead the operation on each line
A declaration creates a named task that is available early in its scope. An expression creates a function value when execution reaches it. An arrow's concise body returns automatically; its block body needs
return.When tracing code, distinguish a function definition, a reference assignment, and a call. Those are different operations even when they use the same name.
2Trace inputs, outputs, and waiting calls
16 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionTrack what enters and leaves a call
- 2ExampleMatch arguments by position
- 3ExampleDistinguish mutation from reassignment
- 4ChallengeKeep the original input
- 5ExplanationDefault means undefined
- 6ExampleGather, then expand
- 7ChallengeUse a default with rest
- 8ExampleReturn the number the caller needs
- 9ExampleStop a path with an early return
- 10ChallengeEvaluate from the inside out
- 11ExampleRemember the unfinished caller
- 12ExplanationGive recursion a stopping path
- 13ExampleUnwind the waiting products
- 14ChallengeRepair a recursive boundary
- 15ConnectionRead failure through the stack
- 16RecapFollow a value through each boundary
3Find the declaration behind a name
10 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionAsk where the name belongs
- 2ExampleCompare block and function boundaries
- 3ExampleUse the definition location
- 4ChallengeTrace a local and an outer value
- 5ExplanationStop at the nearest declaration
- 6Key InsightRegistered is not initialized
- 7ChallengePredict a shadowed read
- 8TipKeep reassignment separate from mutation
- 9ChallengeCombine a callback with lookup timing
- 10RecapTrace location before value
4Give returned functions their own memory
11 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionKeep access after a return
- 2ExampleCreate a counter without calling it
- 3ExampleUpdate the retained variable
- 4ChallengeCompare independent counters
- 5Key InsightRetain the binding, not a snapshot
- 6ExampleCreate a configured calculation
- 7ExampleKeep a private use count
- 8ChallengeExplain three equal results
- 9ConnectionRecognize private scope in older code
- 10ChallengeShare one configured operation
- 11RecapTrack how many environments exist
5Pass behavior and build adapted functions
15 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionLet a function supply the changing behavior
- 2ExampleCall the supplied action
- 3ChallengeUse a callback result
- 4TipInspect all callback arguments
- 5ExampleWrite a transformation pattern
- 6ExplanationChoose the result shape
- 7ChallengeTrace a selection and a transformation
- 8Key InsightKeep numeric order deliberate
- 9ExampleWrap and forward a call
- 10ExplanationRemember whether a call has run
- 11ExampleCompose from the inside out
- 12ExampleFix inputs before the later call
- 13ChallengePass a configured callback
- 14ChallengeRepair a lost wrapper result
- 15RecapCheck both stages of higher-order code
JavaScript Functions practice questions
Set 1: Choose inputs and trace retained state10 Practice Questions
Question 1 of 10
What happens when this script runs?
announce();
function announce() { console.log("Open"); }
Show answerHide answer
announce is already a function when the first line calls it. Its body prints "Open" once. The initialization timing of a function expression stored in const would be different, but this code uses a declaration.Question 2 of 10
value property equal to its input n?Show answerHide answer
{ value: n } an object expression, which the concise arrow body returns. Without those parentheses, the braces start a block and value: n is read as a labeled statement, leaving no returned object. A bare return; also returns undefined.Question 3 of 10
What does the call return?
function combine(first, ...rest) {
return first + rest.length;
}
const values = [8, 2, 6];
combine(...values);
Show answerHide answer
[2, 6], whose length is 2, so the function returns 10. Counting all three values in rest would give 11, but the first argument has already been assigned to first.Question 4 of 10
What does guarded(0) return?
function guarded(n) {
if (n === 0) { return "empty"; }
return "filled";
}
Show answerHide answer
Question 5 of 10
What does totalDown(3) return?
function totalDown(n) {
if (n === 0) { return 0; }
return n + totalDown(n - 1);
}
Show answerHide answer
totalDown(0), which returns 0. The waiting calls then return 1, 3, and 6. The base-case return does not replace every call's result with 0; each waiting frame still has its own n and must finish its pending addition.Question 6 of 10
What happens when read() is called?
let label = "outer";
function read() {
return label;
let label = "inner";
}
Show answerHide answer
label throughout read, including at the earlier return. That local is still uninitialized, so reading it throws a ReferenceError. The unreachable assignment does not make JavaScript skip the local declaration and use the outer string.Question 7 of 10
What does the final call return?
let word = "first";
const read = () => word;
word = "second";
function run() {
const word = "third";
return read();
}
run();
Show answerHide answer
read closes over the top-level word, whose current value is "second". It does not keep a frozen copy of "first", and calling it from run does not redirect its lookup to that caller's local "third". Both the definition location and the later assignment matter.Question 8 of 10
What array does the final expression produce?
const readers = [];
for (let i = 1; i <= 3; i++) {
let value = i;
readers.push(() => value);
value += 10;
}
readers.map((read) => read());
Show answerHide answer
value, and its function retains that binding. The later addition in that same iteration changes the value the function will read to 11, 12, or 13. [1, 2, 3] would treat closure creation as a snapshot and ignore those later updates.Question 9 of 10
What happens when the last line runs?
function speak() { console.log("Hi"); }
function twice(action) { action(); action(); }
twice(speak());
Show answerHide answer
speak immediately, so "Hi" prints once. Its return value is undefined, which becomes action; the first attempted action() then throws a TypeError. Passing speak without call parentheses would let twice invoke it two times.Question 10 of 10
What is the final result?
const compose = (f, g) => (x) => f(g(x));
const subtract = (n) => n - 2;
const triple = (n) => n * 3;
const first = compose(triple, subtract);
const second = compose(subtract, triple);
first(second(4));
Show answerHide answer
second(4) triples 4 and subtracts 2, returning 10. That becomes the input to first, which subtracts 2 and triples 8, returning 24. A result of 18 applies first and second in the opposite outer order; each composition must also preserve its own inner-first call order.Set 2: Read functions and follow their values10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
function showLabel() { console.log("Ready"); }, what statement calls the function?Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
What does copy hold after this code?
const triple = function (n) { return n * 3; };
const copy = triple;
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
What does adjust(8) return?
const adjust = (n) => { n - 3; };
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
What does difference(12, 5) return?
function difference(first, second) {
return first - second;
}
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
What does readLevel(0) return?
function readLevel(level = 40) { return level; }
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
What are the printed number and the value of result?
function displayTotal(a, b) { console.log(a + b); }
const result = displayTotal(6, 9);
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
What happens when getFlag() is called?
function getFlag() {
if (true) { let flag = "on"; }
return flag;
}
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
What values do the two calls return, in order?
function makeStep() {
let value = 4;
return () => { value += 2; return value; };
}
const step = makeStep();
step();
step();
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
What is the complete sequence of printed values?
function deliver(action) { action(7); }
console.log("start");
deliver((n) => console.log(n));
console.log("end");
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
What is the final value of result?
const values = [2, 7, 4, 9];
const result = values
.filter((n) => n > 3)
.map((n) => n - 1)
.reduce((total, n) => total + n, 5);
Show answerHide answer
Save this lesson to see this answer.
Set 3: Connect calls, scope, and callbacks10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
What happens when this script runs?
const saved = build;
const build = function () { return 9; };
console.log(saved());
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
What value is assigned to out?
const describe = (n) => {
const doubled = n * 2;
return doubled + 1;
};
const out = describe(6);
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
What are items and answer after this code?
const items = [5];
function change(list) {
list.push(8);
list = [1];
return list;
}
const answer = change(items);
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
What does countLeaves([0, [[], 2], [[3], 4]]) return?
function countLeaves(list) {
let count = 0;
for (const item of list) {
if (Array.isArray(item)) {
count += countLeaves(item);
} else {
count++;
}
}
return count;
}
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
What does readValue() return?
function readValue() {
if (true) { var value = 14; }
return value;
}
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
What are the two values printed?
let count = 2;
function update() {
let count = 7;
count += 1;
return count;
}
console.log(update());
console.log(count);
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
What values do the last three calls return?
function makeCounter(start) {
let n = start;
return () => { n++; return n; };
}
const a = makeCounter(5);
const alias = a;
const b = makeCounter(5);
a();
alias();
b();
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
What does scale(7) return?
const makeScale = (factor) => (n) => n * factor;
const scale = makeScale(4);
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
What are values and first afterward?
const values = [12, 3, 20];
const first = values.sort((a, b) => a - b)[0];
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
What do the calls read(0) and read(9) return, in order?
function once(fn) {
let done = false;
let result;
return (...args) => {
if (!done) {
done = true;
result = fn(...args);
}
return result;
};
}
const read = once((n) => n * 2);
Show answerHide answer
Save this lesson to see this answer.
Set 4: Diagnose function behavior in small programs10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
function printLine() { console.log("---"); } and then runs printline();. What happens?Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
What does the final call return?
const climb = function step(n) {
return n === 0 ? 0 : 1 + step(n - 1);
};
const saved = climb;
saved(2);
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
What are data and result at the end?
const data = [2, 5];
function extend(list, item) {
const next = list.concat(item);
list.push(9);
return next;
}
const result = extend(data, 7);
result.push(1);
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
What does the final call return?
function total(base = 4, ...extras) {
return extras.reduce((sum, n) => sum + n, base);
}
const values = [undefined, 3, 0, 8];
total(...values);
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
What value does this call produce?
function read() {
return;
}
read();
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
The input n is a nonnegative integer. Which replacement for the recursive return makes the function count down to 0 correctly for both odd and even inputs?
function countDown(n) {
if (n === 0) { return 0; }
// recursive return goes here
}
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
What does the final call return?
const mode = "shared";
function readMode() { return mode; }
function run() {
const mode = "local";
return readMode();
}
run();
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
What values do jobs[0]() and jobs[1]() return after the loop?
const jobs = [];
for (var i = 0; i < 2; i++) {
const offset = i * 10;
jobs.push(() => offset + i);
}
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
What result does this expression produce?
["6", "8", "10"].map((text) => parseInt(text, 10));
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
What are the printed values of out and seen?
let seen = 0;
const out = [3, 5].forEach((n) => {
seen += n;
return n * 2;
});
console.log(out, seen);
Show answerHide answer
Save this lesson to see this answer.
Set 5: Combine closures and higher-order functions10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
What does add("6", 4) * 2 evaluate to?
function add(a, b) { return a + b; }
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
What does the final expression evaluate to?
function choose(n) {
if (n < 0) { return 0; }
return n + 2;
}
choose(choose(-4));
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
What happens when build() is called?
let amount = 6;
function build() {
const read = () => amount;
const result = read();
let amount = 9;
return result;
}
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
What values do change(3), read(), and otherRead() return?
function makePair() {
let n = 1;
return [
(step) => { n += step; return n; },
() => n
];
}
const [change, read] = makePair();
const [, otherRead] = makePair();
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
What does the final expression return?
function makeLimit(max) {
let used = 0;
return () => {
if (used >= max) { return false; }
used++;
return true;
};
}
const allow = makeLimit(2);
[allow(), allow(), allow()];
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
What are result and calls after this code?
let calls = 0;
function choose(values, test) {
for (const value of values) {
calls++;
if (test(value)) { return value; }
}
return null;
}
const result = choose([3, 8, 10, 5], (n) => n > 6 && n % 2 === 0);
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
What are items and out afterward?
const items = [2, 4, 6];
const out = items.map((n, index, array) => {
array[index] = n + 1;
return n * 2;
});
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
What does doubleSaved(9) return after the earlier call shown?
function once(fn) {
let done = false;
let saved;
return (...args) => {
if (!done) { done = true; saved = fn(...args); }
return saved;
};
}
const compose = (f, g) => (x) => f(g(x));
const saved = once((n) => n + 3);
const doubleSaved = compose((n) => n * 2, saved);
saved(4);
doubleSaved(9);
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
What is the returned array?
const partial = (fn, ...fixed) => (...later) => fn(...fixed, ...later);
const offset = (base, n) => base + n;
const addFive = partial(offset, 5);
const compose = (f, g) => (x) => f(g(x));
const transform = compose((n) => n * n, addFive);
[1, 3].map(transform);
Show answerHide answer
Save this lesson to see this answer.
Stuck on your own question? Homework Help
Key Terms
A
Accumulator
[5, 10, 15].Anonymous function
function (n) { return n * n; } has no internal function name in its written syntax.Argument
area(4, 2.5), the arguments are 4 and 2.5, regardless of how the parameters were named.arguments object
arguments[0], but it cannot call arguments.map as an ordinary array method.Array destructuring
const [low, high] = minMax(values); unpacks the two entries of the returned array.Arrow function
(n) => n * 2 supplies a short transformation callback to an array method such as map.B
Base case
n <= 1 stops calls at either 0 or 1.Binding
Block body
(n) => { n * 2; } computes a product but returns undefined because it has no return statement.Block scope
let message declared in an if body cannot be read by a statement after that block ends.C
Call stack
sumOfSquares waits for square(3), the square call has a frame above its waiting caller.Callback
countIf helper calls its test callback for each item and counts those whose test results are truthy.camelCase
calculateTotal describes an action and separates its two words without spaces or underscores.Captured variable
greeting from "Hi" to "Hello" changes the value that a later call to a function reading greeting returns.Closure
count after the factory that created it has finished.Comparator
(a, b) => a - b sorts numbers in ascending order while sort rearranges the input array.Composition
f(g(x)), changing the order to g(f(x)) can change the result even when both functions accept numbers.Concise body
(a, b) => a + b directly returns the sum of the two supplied arguments.D
Default parameter
level = 50 keeps an explicit 0 but supplies 50 when the argument is missing.Delayed callback
sayHi to setTimeout lets following statements run before the scheduled greeting appears.E
Early return
null for a zero divisor before execution reaches the division statement.F
Falsy value
level || 50 replaces 0, while a parameter default preserves that supplied value.Function
printHeading("Report") and printHeading("Totals") to reuse one heading routine.Function body
printHeading body holds three printing statements, all three run again for every call to that function.Function call
sayHi() runs its body, while a line containing only sayHi reads the function value.Function declaration
greet("Li") can appear above the written declaration of greet in the same scope.Function expression
const square = function (n) { return n * n; };, the expression supplies the value stored in square.Function factory
makeMultiplier(3) returns a function that remembers 3 while accepting a later number to multiply.Function reference
const alias = square;, calling alias(4) invokes the function also reached through square.Function scope
var result assigned in an if block can be returned later from the enclosing function body.G
Global scope
count can become shared state when several functions read or update it.Guard clause
if (b === 0) { return null; } protects the division placed after it in safeDivide.H
Higher-order function
Hoisting
var before its assignment gives undefined; an early function-declaration call can run.I
IIFE
(function () { var hidden = 1; })(); invoke the newly created function.Inline function
repeat(3, (i) => console.log(i)) supplies the action without a separate named declaration.Iteration binding
let i iterations can return different values after the loop has ended.L
Lexical scope
Local variable
total without having their additions affect one another.M
Method chain
Mutation
arr.push(3) changes the same array that the caller supplied as its argument.N
Named function expression
const factorial = function fact(n) { /* body */ };, the outside variable is factorial, and the internal name is fact.O
Object literal
(n) => ({ value: n }) returns an object instead of treating its braces as a statement block.P
Parameter
width and height are parameters in function area(width, height) { return width * height; }.Partial application
Predicate
(n) => n > 4 selects which numbers a filter keeps without changing their values itself.Private state
used count inaccessible directly.Pure function
n * 2 is pure; printing the product would introduce a side effect.R
Reassignment
arr = [] redirects the parameter without replacing the caller's array reference.Recursion
Recursive case
n * factorial(n - 1) waits for the smaller factorial before multiplying by this call's n.Rest parameter
function log(label, ...values) { /* body */ }, the first argument is separate from the rest array.Return value
double(5) returns 10, the expression double(5) + 1 can immediately use that value to produce 11.Run-once wrapper
S
Scope chain
amount before considering a same-named variable in an outer scope.Shadowing
count hides an outer count inside that function without changing the outer binding.Shared state
balance.Side effect
Spread syntax
Math.max(...[3, 7, 2]) passes three numbers instead of passing one array to Math.max.Stack frame
n because they have separate frames.Stack trace
Strict mode
"use strict"; makes an undeclared count = 5; assignment raise a ReferenceError.Synchronous callback
T
Temporal dead zone
Transformation callback
(n) => n * n turns the entries of [1, 2, 3] into squares through a mapping operation.W
Wrapper
About this lesson
Make your own Study Materials
Turn your notes, slides and documents into Study Guides, Practice Questions, Flashcards and 6 more tools.