JavaScript Functions — The Basics

Introduction to JavaScript functions — function declarations, function expressions, arrow functions, parameters, default values, rest/spread, return values, and the difference between calling and referencing.

Functions are the unit of work in JavaScript

In JavaScript, functions are first-class — they are values you can store in variables, pass to other functions, return from functions, and attach as properties of objects. Everything from a single utility to an entire framework is built out of functions composing together.

This is the first of three posts on functions. It covers the foundations — how to define them and how to call them. The next two go deeper into closures, this, and higher-order patterns and modern function features such as arrow functions, destructured parameters, and async/await.

Three ways to create a function

1. Function declaration

The classic form. Hoisted in full, so it can be called above its definition.

1greet("Ali");
2
3function greet(name) {
4  return `Hello, ${name}`;
5}

2. Function expression

Assigns an anonymous (or named) function to a variable. Behaves like any other value — not hoisted.

1const greet = function (name) {
2  return `Hello, ${name}`;
3};

A named function expression is useful for stack traces and self-reference:

1const factorial = function fact(n) {
2  return n <= 1 ? 1 : n * fact(n - 1);
3};

3. Arrow function (ES2015)

The modern default for anonymous functions. Shorter syntax, no own this, no arguments object.

1const greet = (name) => `Hello, ${name}`;
2
3// Multi-statement body needs braces and an explicit return
4const shippingFee = (weight) => {
5  if (weight < 1)  return 5;
6  if (weight < 10) return 10;
7  return 25;
8};

Declaration vs expression at a glance

FeatureDeclarationExpression / arrow
Hoisted (callable above definition)YesNo (binding is hoisted in TDZ for let/const)
Has its own thisYesExpression: yes. Arrow: no
Has an arguments objectYesExpression: yes. Arrow: no
Usable as a constructor with newYesExpression: yes. Arrow: no
name property defaults toThe declared nameThe variable it is assigned to

Calling a function

A function value and a function call are not the same thing.

1function speak() { return "hi"; }
2
3speak;       // [Function: speak]   ← the function itself
4speak();     // "hi"                ← the result of calling it

This trips up developers when passing callbacks:

1button.addEventListener("click", handleClick);    // pass the function
2button.addEventListener("click", handleClick());  // BUG: calls now, passes the result

Parameters and arguments

A parameter is the name listed in the function signature; an argument is the value supplied at the call site.

Default parameter values (ES2015)

1function fetchPage(url, { method = "GET", retries = 3 } = {}) {
2  // ...
3}
4
5fetchPage("/users");                           // method=GET, retries=3
6fetchPage("/users", { retries: 1 });           // method=GET, retries=1

A default expression is evaluated every time the function is called, so you can use it for non-constant defaults:

1function log(msg, time = new Date()) {
2  console.log(`[${time.toISOString()}] ${msg}`);
3}

Rest parameters

Collect any number of trailing arguments into a real array:

1function sum(...numbers) {
2  return numbers.reduce((a, b) => a + b, 0);
3}
4
5sum(1, 2, 3, 4);   // 10

Rest must be the last parameter. There can be only one.

Spread at the call site

The same ... syntax, used as an argument, expands an iterable into individual arguments:

1const points = [3, 7, 2];
2Math.max(...points);   // 7
3
4const all = [1, 2, ...points, 9];   // [1, 2, 3, 7, 2, 9]

Return values

Every function returns something. If you omit return, the function returns undefined:

1function noop() {}
2noop();   // undefined

Mental model: functions as values

Because functions are values, every pattern that works for values works for functions:

 1// Store in an array
 2const ops = [
 3  (x) => x + 1,
 4  (x) => x * 2,
 5];
 6
 7// Pass as an argument
 8[1, 2, 3].map((x) => x * x);
 9
10// Return from another function
11function adder(by) {
12  return (x) => x + by;
13}
14const add5 = adder(5);
15add5(10);   // 15

That last example — a function returning a function that “remembers” the outer variable — is a closure. Closures, plus higher-order functions, plus the this binding, are the subject of the next post.

Summary

  • Three forms: function declaration (hoisted), function expression (not hoisted), arrow function (no own this).
  • A bare function name is the function value; only () calls it.
  • Use default parameters, rest parameters, and spread for clean, modern signatures.
  • An omitted return produces undefined. Beware automatic semicolon insertion after return.
  • Functions are values — they fit into every collection, callback, and pipeline.

Next: Functions in depth — closures, this, and higher-order patterns.