Back to Tutorials
Beginner

Functions Fundamentals

6 min read | Functions

Functions Fundamentals

Functions are reusable blocks of code — the single most important building block in JavaScript. Understanding the different ways to define them, how arguments flow in and values flow out, and how this behaves will carry you through every framework and library you ever touch.

Step 1: Function Declarations

function greet(name) {
  return Hello, ${name}!;
}
greet("World"); // "Hello, World!"

Why it works: a declaration is hoisted — the whole function is registered before any code runs, so you may call greet() on a line above its definition. The return statement hands a value back to the caller; without it, the function returns undefined.

Step 2: Function Expressions

const greet = function (name) {
  return Hello, ${name}!;
};

Here the function is a value assigned to a variable. Expressions are not hoisted the way declarations are: calling greet() before this line throws a ReferenceError. Treating functions as values is what lets JavaScript pass them around like any other data — the foundation of callbacks.

Step 3: Arrow Functions (ES6)

// Full form
const add = (a, b) => {
  return a + b;
};
// Implicit return — no braces means "return this expression"
const addShort = (a, b) => a + b;
// One parameter needs no parentheses
const double = n => n * 2;

Why arrows are more than short syntax: arrow functions do not create their own this, arguments, or super — they inherit this from the surrounding code. That makes them perfect for callbacks inside methods, where a regular function would suddenly have a different this:

const counter = {
  count: 0,
  startRegular() {
    setInterval(function () {
      this.count++;        // BROKEN: "this" is not counter here
    }, 1000);
  },
  startArrow() {
    setInterval(() => {
      this.count++;        // works: arrow inherits counter's "this"
    }, 1000);
  }
};

The flip side: arrows are the wrong choice for object methods themselves and for anything needing its own this.

Step 4: Default and Rest Parameters

function greet(name = "World") {
  return Hello, ${name}!;
}
greet();        // "Hello, World!"
greet("Alice"); // "Hello, Alice!"
// Rest gathers any number of arguments into a real array
function sum(...numbers) {
  return numbers.reduce((acc, n) => acc + n, 0);
}
sum(1, 2, 3, 4); // 10

Why it works: a default kicks in only when the argument is undefined (missing, or explicitly undefined) — passing null or 0 does not trigger it, which surprises many beginners. The rest parameter ...numbers must come last and gives you a genuine array, unlike the old arguments object.

Step 5: Higher-Order Functions

A higher-order function takes a function as input or returns one — and this pattern is everywhere: map, filter, event listeners, middleware.

// Takes a function
function repeat(n, action) {
  for (let i = 0; i < n; i++) {
    action(i);
  }
}
repeat(3, console.log); // 0, 1, 2
// Returns a function
function multiplier(factor) {
  return n => n * factor;
}
const triple = multiplier(3);
triple(5); // 15

Why triple remembers factor: the returned arrow function forms a closure — it keeps access to the variables of the scope where it was created, even after multiplier has finished running. Closures are how JavaScript functions carry private state, and they power everything from debouncing to React hooks.

Common Beginner Errors and Fixes

  • Calling instead of passing: button.addEventListener("click", handle()) runs handle immediately and passes its return value. Fix: pass the reference — handle — or wrap it: () => handle(id).
  • Missing return: const f = (n) => { n * 2 } returns undefined because braces need an explicit return. Fix: (n) => n * 2 or add return.
  • Returning an object literal from an arrow: () => { name: "x" } is parsed as a code block. Fix: wrap in parentheses — () => ({ name: "x" }).
  • Expecting a default for null: greet(null) prints "Hello, null!". Fix: normalize inputs, or use name ?? "World" inside the function.
  • Losing this in a callback: extracting a method (const fn = obj.method) detaches it from obj. Fix: obj.method.bind(obj) or an arrow wrapper.

Practice Exercise

Build a tiny formatting toolkit:

  • Write a function declaration capitalize(word) that upper-cases the first letter.
  • Write an arrow function exclaim with an implicit return that appends "!".
  • Write a higher-order function compose(f, g) that returns a new function applying g first, then f.
  • Create const shout = compose(exclaim, capitalize) and test shout("hello") — it should print "Hello!".
  • Bonus: extend compose with rest parameters so it accepts any number of functions.
  • function capitalize(word) {
      return word.charAt(0).toUpperCase() + word.slice(1);
    }
    const exclaim = s => s + "!";
    function compose(f, g) {
      return x => f(g(x));
    }
    const shout = compose(exclaim, capitalize);
    console.log(shout("hello")); // "Hello!"
    

    If the bonus stumps you: reduce over the functions array, threading the value through each call.