Free Handbook · Runs in your browser

Functions

Declarations, expressions and arrow functions; parameters, defaults and rest; scope and hoisting with the temporal dead zone traced; closures explained with a counter; what this is and why arrows differ; recursion; and the higher-order functions map, filter and reduce.

0 / 111 lessons🔥 0 day streak
ShareXLinkedIn

Module 04 · what you'll be able to do

  • Write functions three ways and know when each is right
  • Use default, rest and destructured parameters
  • Explain scope, hoisting and the temporal dead zone
  • Build and recognise closures — the idea behind every callback and hook
  • Predict what this is in a method, a plain call, a callback and an arrow
  • Transform data with map, filter, reduce, find, some and every
01

Three ways to define a function

A function packages steps under a name so you can run them many times with different inputs. JavaScript has three syntaxes: the declaration (hoisted, has a name, has its own this), the expression (a function stored in a variable) and the arrow function (short, no own this, the default for callbacks). All three are values — you can pass them around like numbers.

javascriptEdit it. ⌘/Ctrl + Enter runs.
You should see
Hello, Ada!
HI!
16 5 { name: 'Ada', age: 36 } undefined
10 [ 1, 4, 9 ] function
logging a
undefined
Your turn
Write isEven as an arrow function, then use it with filter on [1, 2, 3, 4, 5, 6].
Error you will hit

Arrow returning an object: () => { name: "Ada" }

javascript
const makeUser = name => { name: name }
console.log(makeUser("Ada"))
undefined
Why the engine said that

No error, wrong result. Braces after => start a block, not an object. Inside it, name: name is parsed as a label followed by an expression, and the function returns nothing.

The fix

Wrap the object literal in parentheses.

javascript
const makeUser = name => ({ name })
console.log(makeUser("Ada"))   // { name: 'Ada' }
Which one?
Top-level named functions: declarations (readable stack traces, hoisting lets you put helpers at the bottom). Callbacks and one-liners: arrows. Methods on objects and classes: the shorthand greet() {}, because they need this. The classic function expression is now rare.
02

Parameters: defaults, rest, destructuring

javascriptEdit it. ⌘/Ctrl + Enter runs.
You should see
1 undefined
1 2
localhost:5432 ssl=false db.internal:443 ssl=true localhost:8080 ssl=false
0 6 9
Ada (viewer) 1 tags Bob (admin) 0 tags undefined (viewer) 0 tags
5
tick 0
tick 1
tick 2
changed
changed
Your turn
Write formatPrice(amount, { currency = "USD", decimals = 2 } = {}) and call it three ways.
More than two positional parameters? Use an options object
createUser("Ada", true, false, 3) is unreadable at the call site. createUser({ name: "Ada", active: true, retries: 3 }) names every value, allows any order, and defaults the rest. Every mature library does this.
03

Scope, hoisting and the temporal dead zone

Scope is where a name is visible. Every { } block, every function and the file itself create a scope, and an inner scope can read outer names but not the reverse. Hoisting means declarations are processed before any code runs: function declarations are fully hoisted (usable anywhere), var is hoisted as undefined, and let/const are hoisted but locked until their line — the temporal dead zone.

javascriptEdit it. ⌘/Ctrl + Enter runs.
You should see
inner sees outer
top sees global
undefined undefined 3
function undefined
ReferenceError
now fine
[ 3, 3, 3 ] [ 0, 1, 2 ]
VisualizeWhy var gives [3, 3, 3] and let gives [0, 1, 2]Step 1 / 8
var fns = []
for (var i = 0; i < 3; i++) {
fns.push(() => i)
}
console.log(fns.map(f => f()))
Line 2

var i is ONE variable for the whole function — not one per iteration.

Variables now
i0
All 8 steps as a table
StepLineWhat happenedVariables now
12var i is ONE variable for the whole function — not one per iteration.i = 0
23Push a function that will read i later. It does not copy the value; it remembers the variable.fns = [f]
32Increment the same i.i = 1
43Another function remembering the same i.fns = [f, f]
52And again.i = 2
63Third function, same i.fns = [f, f, f]
72i becomes 3; the loop ends.i = 3
85Now each function reads i — which is 3. With let, each iteration gets a fresh j, so the functions remember 0, 1 and 2.
Error you will hit

ReferenceError: Cannot access 'total' before initialization

javascript
function report() {
  console.log(total)
  const total = 42
}
report()
Uncaught ReferenceError: Cannot access 'total' before initialization
    at report (your code:2)
    at your code:5
Why the engine said that

total is declared in this scope with const, so it exists from the top of the function — but it is in the temporal dead zone until line 3. Note the message differs from "is not defined": the engine knows the name, it just is not ready.

The fix

Declare before use. If you see this with a function, you wrote it as a const arrow and called it above its definition — a declaration would have been hoisted.

04

Closures

A closure is a function that remembers the variables of the scope it was created in, even after that scope has finished. Every function in JavaScript is a closure; the term matters when a function outlives its creator — a returned function, a callback, an event handler. Closures are how you get private state without classes, and they are the mechanism behind React hooks, debounce, memoize and every "factory" function.

javascriptEdit it. ⌘/Ctrl + Enter runs.
You should see
1 2 3
1 4
150 undefined
10 15
initialising
42 42
16 16 25 computed 2 times
Your turn
Write makeIdGenerator(prefix) that returns a function producing "user-1", "user-2"… Then create two independent generators.
VisualizemakeCounter: the scope survives the returnStep 1 / 4
function makeCounter() {
let count = 0
return () => ++count
}
const next = makeCounter()
console.log(next())
console.log(next())
Line 5

Call makeCounter. A scope is created with count.

Variables now
makeCounter scope{ count: 0 }
All 4 steps as a table
StepLineWhat happenedVariables now
15Call makeCounter. A scope is created with count.makeCounter scope = { count: 0 }
23Create an arrow function inside that scope and return it. Normally the scope would be discarded now — but the returned function references count, so the scope is kept alive.next = (arrow, closes over count)
36Call next(). It finds count in its remembered scope, increments it.makeCounter scope = { count: 1 }
47Same scope, same variable — the value persisted between calls. That persistence is the closure.makeCounter scope = { count: 2 }
Mid-levelWhat is a closure, and give a practical use.

A function bundled with the lexical scope it was defined in, so it can access those variables after the outer function has returned. Practical uses: private state (a counter or cache that nothing else can modify), function factories (multiplier(2)), and callbacks that need context (an event handler remembering which item it belongs to). React's useState is closures all the way down.

What they are really testing: Whether you understand that functions capture variables, not values — the var-loop bug is the follow-up.

05

this: four rules

this is the one JavaScript feature that confuses everyone, and it reduces to four rules about how the function was called, not where it was written. (1) obj.method()this is obj. (2) A plain call fn()undefined in strict mode (the global object in sloppy mode). (3) new Fn() → the new object. (4) Arrow functions have no this — they use the one from the surrounding scope. Plus the override: call, apply and bind set it explicitly.

javascriptEdit it. ⌘/Ctrl + Enter runs.
You should see
hi, I am Ada
arrow sees object
[ 'Ada-1', 'Ada-2' ]
hi, I am undefined
hi, I am Bound hi, I am Called hi, I am Applied
undefined Save Save
Your turn
Add a setTimeout inside later() that logs this.name after 10 ms — first with a function callback, then with an arrow. Which one works?
Error you will hit

TypeError: Cannot read properties of undefined (reading 'name')

javascript
"use strict"
const user = { name: "Ada", greet() { return this.name } }
const fn = user.greet
console.log(fn())
Uncaught TypeError: Cannot read properties of undefined (reading 'name')
    at greet (your code:2)
    at your code:4
Why the engine said that

Calling fn() is a plain call — rule 2 — so this is undefined, and undefined.name throws. The method was written with this but called without an object in front of it. Event handlers and setTimeout callbacks hit this constantly.

The fix

user.greet.bind(user), or wrap: () => user.greet(), or define callbacks as arrows / class-field arrows.

javascript
const user = { name: "Ada", greet() { return this.name } }
const fn = () => user.greet()
console.log(fn())   // Ada
Arrow functions as methods
greetArrow: () => this.name on an object literal does NOT see the object — arrows take this from the scope they were written in, which here is the file. Use the method shorthand for methods and arrows for callbacks inside them.
06

Recursion

A function that calls itself. Every recursive function has a base case (the smallest input, answered directly) and a step that shrinks the input and trusts the function to handle the rest. It is the natural fit for anything nested: folder trees, JSON, the DOM, divide-and-conquer algorithms. Module 13 goes deep; here is the shape.

javascriptEdit it. ⌘/Ctrl + Enter runs.
You should see
120
36
[ 1, 2, 3, 4, 5 ]
5
36
Your turn
Write fib(n) recursively, print fib(25), then add a cache (a Map) and print fib(80).
Error you will hit

RangeError: Maximum call stack size exceeded

javascript
function countDown(n) {
  console.log(n)
  countDown(n - 1)      // no base case
}
countDown(3)
3
2
1
0
-1
…
Uncaught RangeError: Maximum call stack size exceeded
    at countDown (your code:2)
Why the engine said that

Each call adds a frame to the call stack and nothing ever returns. V8 gives up at roughly 10,000 frames. The same error appears when the base case exists but the step does not move toward it.

The fix

Write the base case first, and make sure every step makes the input strictly smaller. Deep but finite recursion (a 50,000-node list) needs the loop-with-a-stack form instead.

javascript
function countDown(n) {
  if (n < 0) return
  console.log(n)
  countDown(n - 1)
}
countDown(3)
07

map, filter, reduce and friends

A higher-order function takes a function or returns one. The array methods are the ones you will use hourly: map transforms each item, filter keeps some, reduce boils everything down to one value, and find/some/every answer questions. They read as a pipeline of what, not a loop of how — and they never mutate the source.

javascriptEdit it. ⌘/Ctrl + Enter runs.
You should see
[ 120, 80, 45, 200 ]
[ 1, 3 ]
445
ada 3
true true
{ ada: 165 }
[ 4, 1 ]
4
{ '1': 'ada', '2': 'bob', '3': 'ada', '4': 'cy' }
[ 1, 2, 3 ] [ '0:a', '1:b' ]
hello-world
Your turn
From orders, produce { paid: 2, pending: 1, refunded: 1 } with one reduce. Then do it with Object.groupBy.
Error you will hit

map returning undefined: forgot the return

javascript
const doubled = [1, 2, 3].map(n => { n * 2 })
console.log(doubled)
[ undefined, undefined, undefined ]
Why the engine said that

Braces make a block body, and a block body needs an explicit return. n * 2 was computed and thrown away.

The fix

Either drop the braces for an expression body, or add return.

javascript
const doubled = [1, 2, 3].map(n => n * 2)
console.log(doubled)   // [ 2, 4, 6 ]
Quick check

Which method would you use to turn an array of orders into their total revenue?

JuniorWhat is the difference between map and forEach?

map returns a new array of the callback's results and leaves the original alone — use it to transform. forEach returns undefined and exists for side effects like logging or DOM updates. If you find yourself pushing into an outer array inside forEach, you wanted map (or filter/reduce).

Finish the JavaScript handbook, then get hired

Sit the exam for your certificate, run your resume through the ATS checker, and see the jobs that ask for exactly this.

Check my resume
Found this course useful? Share it.
ShareXLinkedIn

Comments

0

Join the conversation. Sign in to leave a comment — we'd love to hear your thoughts.