Skip to content
elephantoo

Functions

Lesson 7 of 34 16 min read

Declarations, expressions, arrow functions, default and rest parameters, spread and callbacks.


Functions package logic so you can reuse it, name it and test it. They're the most important building block in JavaScript — and in JavaScript, functions are also values: you can store them in variables, pass them to other functions and return them.

Function declarations#

JavaScript
function add(a, b) {
  return a + b;
}

console.log(add(2, 3));   // 5
console.log(add(10, -4)); // 6
  • a and b are parameters — placeholders in the definition.
  • 2 and 3 are arguments — the actual values you pass in.
  • return sends a value back and immediately exits the function. A function without return returns undefined.

Declarations are hoisted: you can call them above the line where they're written.

JavaScript
console.log(square(4)); // 16 – works thanks to hoisting

function square(n) {
  return n * n;
}

Function expressions#

A function can also be created as a value and assigned to a variable:

JavaScript
const multiply = function (a, b) {
  return a * b;
};

console.log(multiply(3, 4)); // 12

Function expressions are not hoisted — calling multiply before this line throws a ReferenceError.

Arrow functions#

Arrow functions are a shorter syntax, and the most common style for small functions and callbacks:

JavaScript
const divide = (a, b) => {
  return a / b;
};

// Single expression? Skip the braces and `return`:
const cube = (n) => n ** 3;
const greet = () => "Hello!";
const isEven = n => n % 2 === 0; // parentheses optional for one parameter

// Returning an object literal? Wrap it in parentheses:
const makeUser = (name) => ({ name, createdAt: 2026 });

console.log(divide(9, 3), cube(2), greet(), isEven(7));
console.log(makeUser("Ada"));
Output
3 8 Hello! false
{ name: 'Ada', createdAt: 2026 }

Arrow functions differ from regular functions in a few ways: they don't have their own this or arguments, and they can't be used with new. That makes them ideal for callbacks, while object methods that use this should use regular method syntax. The this lesson explains why.

Default parameters#

JavaScript
function greetUser(name = "friend", punctuation = "!") {
  return `Hi, ${name}${punctuation}`;
}

console.log(greetUser());             // Hi, friend!
console.log(greetUser("Ada", "."));   // Hi, Ada.
console.log(greetUser(undefined, "?")); // Hi, friend?
console.log(greetUser(null));         // Hi, null! – null is not "missing"

Defaults apply when the argument is missing or undefined — but not for null.

Rest parameters#

... in a parameter list gathers "the rest" of the arguments into a real array:

JavaScript
function sum(...numbers) {
  return numbers.reduce((total, n) => total + n, 0);
}

console.log(sum(1, 2, 3, 4)); // 10
console.log(sum());           // 0

function logWithLevel(level, ...messages) {
  console.log(`[${level}]`, messages.join(" "));
}
logWithLevel("INFO", "Server", "started", "on", "port", 3000);
Output
10
0
[INFO] Server started on port 3000

The rest parameter must be last. It replaces the old arguments object, which isn't a real array and doesn't exist in arrow functions.

Spread arguments#

Spread is the mirror image of rest: it expands an array into individual arguments.

JavaScript
const scores = [72, 95, 88];
console.log(Math.max(...scores)); // 95
console.log(sum(...scores, 5));   // 260 (using sum from above)

Functions as values: callbacks#

Because functions are values, you can pass one into another. A function passed this way is a callback:

JavaScript
function repeat(times, action) {
  for (let i = 0; i < times; i++) {
    action(i);
  }
}

repeat(3, (i) => console.log(`Round ${i + 1}`));
Output
Round 1
Round 2
Round 3

You'll use callbacks constantly — with array methods, timers and events:

JavaScript
const prices = [100, 250, 40];
const withTax = prices.map((p) => p * 1.18);
console.log(withTax); // [ 118, 295, 47.199999999999996 ]

setTimeout(() => console.log("one second later"), 1000);

A function that takes or returns another function is called a higher-order function. map, filter and setTimeout are all higher-order functions.

Returning functions#

JavaScript
function makeMultiplier(factor) {
  return (n) => n * factor;
}

const double = makeMultiplier(2);
const triple = makeMultiplier(3);
console.log(double(5), triple(5)); // 10 15

The inner function "remembers" factor even after makeMultiplier has finished. That memory is called a closure — the subject of the next lesson.

Pure functions and side effects#

A pure function always returns the same output for the same input and changes nothing outside itself:

JavaScript
// Pure
const totalPrice = (items) => items.reduce((sum, i) => sum + i.price * i.qty, 0);

// Impure: depends on and changes outside state
let runningTotal = 0;
function addToTotal(price) {
  runningTotal += price;
}

const cart = [{ price: 100, qty: 2 }, { price: 50, qty: 1 }];
console.log(totalPrice(cart)); // 250

Pure functions are easier to test and reason about. Real programs need side effects (saving data, updating the page), but keep them at the edges and the core logic pure.

Parameters are passed by value — but objects are shared#

JavaScript
function rename(user) {
  user.name = "Changed"; // mutates the caller's object!
}
function reassign(user) {
  user = { name: "New" }; // only changes the local variable
}

const person = { name: "Ada" };
rename(person);
console.log(person.name); // Changed
reassign(person);
console.log(person.name); // Changed

The function receives a copy of the reference, so changing properties affects the original object. Prefer returning a new object: return { ...user, name: "Changed" }.

Immediately-invoked and named function expressions#

You might see these in older code:

JavaScript
(function () {
  const secret = "hidden from the global scope";
  console.log("IIFE ran");
})();

const factorial = function fact(n) {
  return n <= 1 ? 1 : n * fact(n - 1); // name usable inside for recursion
};
console.log(factorial(5)); // 120

Modern code uses modules and block scope instead of IIFEs, but recursion (a function calling itself) remains useful for trees and nested data.

Good habits#

  • Name functions with verbs: calculateTotal, formatDate, fetchUser.
  • Keep each function small and focused on one job.
  • Prefer returning values over changing outside variables.
  • More than three parameters? Pass an object instead: createUser({ name, email, role }).

Common mistakes#

  • Forgetting return in a function with braces: (n) => { n * 2 } returns undefined.
  • Calling instead of passing a callback: setTimeout(sayHi(), 1000) runs sayHi immediately. Pass sayHi.
  • Returning an object literal from an arrow without parentheses.

What's next#

Next: where variables are visible — scope — plus hoisting and the powerful idea of closures.

Check your understanding

Quick quiz

0/3 answered
  1. 1.Which is a valid arrow function that doubles a number?

  2. 2.Given function greet(name = "friend") { return "Hi " + name; }, what does greet() return?

  3. 3.What does const f = () => { value: 1 }; f() return?

Finished reading?

Mark this lesson complete to track your progress.