Functions: Declarations, Expressions, and Arrow Functions

Lesson 9 of 10

On this page

A function is a reusable, named block of code — write the steps once, then run them again and again with different inputs. They’re how you avoid repeating yourself, and how you break a big problem into smaller, readable pieces.

Arrow functions — the form you’ll write most

Arrow functions are the most common way to write a function in modern JavaScript, especially for short, focused pieces of logic — the kind you hand to .map, .filter, and event handlers:

const double = (n) => n * 2;
console.log(double(5)); // 10

const greet = (name) => {
    const message = `Hello, ${name}!`;
    return message;
};
console.log(greet("Ada")); // "Hello, Ada!"

Breaking down the syntax:

Parameters, arguments, and return values

The names inside the parentheses (name, n) are parameters — placeholders for the values, called arguments, that get passed in when the function is called. return sends a value back to wherever the function was called:

const add = (a, b) => a + b;

const total = add(2, 3); // total is 5

If a function never reaches a return, it implicitly returns undefined.

Default and rest parameters

Two small features make functions more flexible about the arguments they accept — both common enough to know well:

// Default parameters — used when an argument is missing
const greet = (name = "friend") => `Hello, ${name}!`;
greet("Ada"); // "Hello, Ada!"
greet();      // "Hello, friend!"

// Rest parameters — gather any number of extra arguments into an array
const sum = (...numbers) => numbers.reduce((total, n) => total + n, 0);
sum(1, 2, 3, 4); // 10

Function declarations — named, hoisted functions

A function declaration looks similar but uses the function keyword and a name directly, without assigning it to a variable:

function double(n) {
    return n * 2;
}

console.log(double(5)); // 10

The key difference from an arrow function assigned to a const is hoisting: a function declaration is fully available even before the line where it’s written, which makes it convenient for defining helpers near the bottom of a file while using them near the top. Arrow functions don’t get this treatment — they follow the normal “declare before use” rule that const and let follow.

Function expressions

A function expression is a function created and stored in a variable using the function keyword, rather than the arrow syntax:

const double = function (n) {
    return n * 2;
};

You’ll see this form throughout older code and some libraries. It behaves like an arrow function in most everyday situations — the main practical difference is how each one treats this, which the Scope and Closures lesson touches on.

Less common: immediately invoked function expressions (IIFEs)

Occasionally you’ll see a function defined and called in the very same breath — useful for running some setup code in its own private scope without leaving a named function lying around:

const result = (() => {
    const setupValue = computeSomethingOnce();
    return setupValue * 2;
})();

You’re unlikely to write many of these yourself, but recognizing the pattern — a function wrapped in parentheses, immediately followed by another set of parentheses to call it — helps when reading older code and certain libraries.

The old way of doing things

Before arrow functions arrived in ES2015, every function — short or long — was written with the function keyword, either as a declaration or an expression:

var numbers = [1, 2, 3];

var doubled = numbers.map(function (n) {
    return n * 2;
});

Arrow functions trim this down considerably — the same line becomes numbers.map((n) => n * 2) — which matters a lot when you’re passing small functions around constantly, as you do with .map, .filter, and friends.

There’s a deeper reason arrow functions caught on, too: the function keyword creates its own this, which famously caused confusion inside methods and callbacks. Older code worked around it with a workaround variable, often named self or that:

var counter = {
    count: 0,
    start: function () {
        var self = this; // capture `this` before it changes inside the callback
        setInterval(function () {
            self.count += 1;
        }, 1000);
    },
};

Arrow functions don’t create their own this — they use the this from where they’re written, which is almost always what you want. That single difference, combined with their shorter syntax, is why arrow functions are now the default choice for nearly every function you’ll write. The Scope and Closures lesson — the final one in this series — explores this and related ideas in more depth.

You may also encounter the old arguments object, an array-like value available inside function-style functions that holds every argument passed in, regardless of the declared parameters:

function sum() {
    var total = 0;
    for (var i = 0; i < arguments.length; i++) {
        total += arguments[i];
    }
    return total;
}

Rest parameters (...numbers, shown earlier) replace this with a real array that supports every array method you already know — another reason modern functions read so much more simply than their older counterparts.