Skip to the chapter
←JavaScriptGGroundworkJSJavaScript
JavaScript/Beginner/B10
10 / 41 written chapters
/

The cover
  1. B1Setup & mental model
  2. B2Execution context
  3. B3var, let & const
  4. B4Scope
  5. B5Single thread
  6. B6In the browser
  7. B7Types & values
  8. B8Strings & Math
  9. B9Operators & flow
  10. B10Functions
  11. B11Objects & arrays
  12. B12this
  13. B13Closures
  14. B14DOM & events
  15. B15Callbacks & promises
  16. B16Errors & tools
  17. B17Guided project
  18. ★Cheat page
  1. I1Scope & functions, properly
  2. I2Objects deeply
  3. I3Prototypes & OOP
  4. I4Async, properly
  5. I5Modules & tooling
  6. I6Regex & dates
  7. I7Error handling
  8. I8Real-time connections
  9. I9Offline & storage
  10. I10Browser APIs
  11. I11Modern JS
  12. ★Cheat page
  1. A1Engine & memory
  2. A2Advanced async
  3. A3Metaprogramming
  4. A4Types & data
  5. A5Patterns & architecture
  6. A6Performance
  7. A7Security
  8. A8Ecosystem
  9. A9Testing
  10. A10Observability
  11. A11Interview bank
  12. A12Build it yourself
  13. ★Cheat page

JSB10 · Part 1 · Beginner

Functions

A function is a value first, a block of code second.

7 min read8 sections2 exercises
10 of 41

In this chapter

  • Three ways to write one
  • Arrow functions — the concise cousin
  • Parameters, arguments, defaults
  • Rest parameters — the modern arguments
  • Return — and the newline that eats it

and 3 more

about 7 min
08On this page
  1. Three ways to write one
  2. Arrow functions — the concise cousin
  3. Parameters, arguments, defaults
  4. Rest parameters — the modern arguments
  5. Return — and the newline that eats it
  6. Where a function's variables live
  7. Hoisting, one level up
  8. What comes next

Three ways to write one

Same function, three spellings — and the differences between them aren't cosmetic. They change when the function exists and what it's allowed to do.

// 1. Declaration — a named statement
function add(a, b) {
  return a + b;
}

// 2. Expression — a value, happens to be a function, assigned like any other
const subtract = function (a, b) {
  return a - b;
};

// 3. Arrow — an expression too, but lighter and with no own "this"
const multiply = (a, b) => a * b;
Declaration Expression Arrow
Hoisted, fully usable early? yes no no
Has its own this yes yes no — inherits it
Has its own arguments yes yes no — inherits it
Works as a constructor (new) yes yes no
console.log(declared());     // what happens?
function declared() { return "I work before my own definition"; }

console.log(typeof viaVar);  // what happens?
viaVar();
var viaVar = function () { return "x"; };

declared() works — function declarations are hoisted completely: name and body, both ready before line 1 runs. viaVar is different: var hoists the name (pre-filled with undefined) but not the function it's later assigned. So typeof viaVar is "undefined", and calling it throws TypeError: viaVar is not a function — you're calling undefined(). Swap var for const and it's worse: a ReferenceError, because the name sits in the Temporal Dead Zone until its line runs.

Rule A function you need to call before its own line in the file must be a function declaration. An expression or arrow only exists from its own line onward — same as any other const.

Arrow functions — the concise cousin

Arrows drop the function keyword and, with exactly one parameter, the parentheses too. A one-expression body skips return entirely — the expression's value is the return value.

const square = n => n * n;                  // implicit return
const clamp = (n, lo, hi) => Math.min(Math.max(n, lo), hi);
const noisy = n => {                          // block body needs an explicit return
  console.log("squaring", n);
  return n * n;
};
⚠ Returning an object literal from a one-liner const make = () => { name: "a" }; does not return an object — the { is read as the start of a block body, and name: "a" is parsed as a label, not a key. Wrap it in parens: () => ({ name: "a" }).

The bigger difference isn't syntax, it's this and arguments. An arrow doesn't create either — it reads through to whatever function it's lexically written inside:

function outer(a, b) {
  const arrow = (x, y, z) => arguments.length;
  return arrow(1, 2, 3);
}
console.log(outer(10, 20));   // what happens?

2, not 3. The arrow's arguments isn't its own — it's outer's, which was called with two values. The same logic governs this inside an arrow, and it's the whole reason arrows became the default choice for callbacks: no more const self = this; workaround. The full mechanics of this get their own chapter later — for now, remember arrows borrow it rather than own it.

Dry run: the call stack, and whose arguments gets read

StepCallCall stackarguments refers toarguments.length
1outer(10, 20)[outer]outer's own — created fresh for this call2
2arrow(1, 2, 3), called inside outer[outer, arrow]arrow has none of its own — the lookup reads outer's, through the same lexical link scope usesstill 2, not 3
3arrow returns arguments.length[outer]—2
4outer returns that value[]—2

The three arguments passed to arrow are never read at all — an arrow function doesn't get its own arguments object to shadow the outer one, so the lookup walks straight past it to the nearest function that does: outer.

Parameters, arguments, defaults

A parameter is the name in the function's own definition. An argument is the actual value handed over at the call site. Extra arguments are silently dropped; missing ones become undefined — unless a default says otherwise.

function greet(name, greeting = "Hello") {
  return greeting + ", " + name + "!";
}
greet("Ana");              // "Hello, Ana!"
greet("Ana", "Hi");        // "Hi, Ana!"
greet("Ana", undefined);   // "Hello, Ana!" — undefined also triggers the default

Defaults aren't static values baked in once — they're expressions, evaluated fresh on every call that needs them, and they can reference earlier parameters:

function withDefault(a, b = a + 1) {
  return b;
}
console.log(withDefault(5));       // what happens?
console.log(withDefault(5, 100));  // what happens?

6, then 100 — the default only runs when the argument is missing (or explicitly undefined); supply anything else and the default expression never executes at all.

⚠ Defaults can only look left a can default from an earlier parameter, but not a later one — function f(a = b, b = 1) {} throws ReferenceError: Cannot access 'b' before initialization the moment a's default needs to run, because b is still in its own Temporal Dead Zone at that point.

Rest parameters — the modern arguments

...args in a parameter list collects every remaining argument into a real array — unlike the old arguments object, which looks array-ish but has no map/filter/reduce of its own. Arrows don't get arguments at all, so rest params are their only option for "however many args you send me."

function sum(...nums) {
  return nums.reduce((total, n) => total + n, 0);
}
sum(1, 2, 3, 4);   // 10

function logAll(label, ...rest) {   // rest must be LAST
  console.log(label, rest);
}

Return — and the newline that eats it

No return statement, or a bare return;, both give back undefined. That's not the interesting part — this is:

function makeUser() {
  return
  { name: "Ana" };
}
console.log(makeUser());   // what happens?

undefined — not the object. Automatic Semicolon Insertion sees a line break right after return and quietly inserts a semicolon there, turning it into return; followed by an unreachable, orphaned block. The object literal on the next line never has a chance to be returned.

Rule Never put a line break between return and the value. If the value is long, wrap it in parens and break inside them: return (
  { name: "Ana" }
);

Where a function's variables live

Every call gets its own scope: variables declared inside are invisible outside, and a nested function can see everything around it, never the reverse.

function outer() {
  let secret = 42;
  function inner() {
    console.log(secret);   // fine — inner can see outer's variables
  }
  inner();
}
console.log(typeof secret);   // "undefined" — outer can't be seen from here

That is Scope applied to functions — the lookup, shadowing, and why a function sees where it was written rather than where it was called are all there. What is new in this chapter is the part only functions have: parameters.

Hoisting, one level up

The Temporal Dead Zone from the execution context chapter applies the same way inside a function body. Parameters add one rule on top: they are created when the call starts and initialised left to right, each one ready before the next default runs. That is why b = a + 1 works and a = b throws, as shown above.

function rename(x) {
  let y = x;     // fine — a new name
}

function clash(x) {
  let x = 1;     // SyntaxError: Identifier 'x' has already been declared
}

function nested(x) {
  if (x) {
    let x = "inner";   // allowed — a new block, so this shadows the parameter
  }
}

A parameter and a let/const of the same name can't coexist in the function's top-level scope — JS won't let you silently replace an argument you probably still needed. Inside a nested block the new name is fine, and the parameter is visible again once the block ends.

What comes next

This is the mechanical half: how to write one, what the parameters do, where the name lives. The half that interviews spend their time on comes next: this with its four binding rules and call / apply / bind, then closures, and at the intermediate level currying, composition and recursion in Scope & functions, properly. Nothing here is superseded there; it is the same subject, one layer down.

Interview answer, one sentence

"Functions are first-class values you can store and pass, declarations are hoisted whole while expressions and arrows are not, arrows have no this or arguments of their own, and default and rest parameters replace most uses of the old arguments object."

Before you move on
  • Explain why a function declaration can be called above its own line but a const function expression cannot.
  • Trace why arrow(1, 2, 3) reports arguments.length as 2, not 3, when called from inside outer(10, 20).
  • Predict what makeUser() returns when return and the object literal are on separate lines, and name the mechanism responsible.
  • Explain why function f(a = b, b = 1) {} throws, in terms of parameter initialisation order and the TDZ.
  • Say what a rest parameter gives you that the old arguments object doesn't, and why arrow functions need it more than regular functions do.

02Practice this chapter

Opens in the editor: write it, run it, and check it against real tests.

  • 9 testsFind the line break that ate the return valuebeginner
  • 10 testsSum any number of argumentsbeginner
All 35 for this level →
B10

Finished reading?

0 of 41 chapters read in this series

→Next stop

  1. B9Previous · B9Operators & flow
  2. B10you are here
  3. Next · B11Objects & arraysThe two shapes almost everything you build is made of.8 min read

01On this page

8 sections · 0%

  1. 01Three ways to write one
  2. 02Arrow functions — the concise cousin
  3. 03Parameters, arguments, defaults
  4. 04Rest parameters — the modern arguments
  5. 05Return — and the newline that eats it
  6. 06Where a function's variables live
  7. 07Hoisting, one level up
  8. 08What comes next

[] chapters · t top