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};
Note
Arrow functions are not just shorter syntax — they inherit
thisfrom the enclosing scope. That makes them ideal for callbacks but unsuitable as object methods or constructors. The advanced post covers this in depth.
Declaration vs expression at a glance
| Feature | Declaration | Expression / arrow |
|---|---|---|
| Hoisted (callable above definition) | Yes | No (binding is hoisted in TDZ for let/const) |
Has its own this | Yes | Expression: yes. Arrow: no |
Has an arguments object | Yes | Expression: yes. Arrow: no |
Usable as a constructor with new | Yes | Expression: yes. Arrow: no |
name property defaults to | The declared name | The 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
Warning
JavaScript inserts a semicolon after a bare
returnkeyword on its own line. The expression below returnsundefined, not the object:1function make() { 2 return 3 { value: 1 }; 4}Always keep
returnand the value it returns on the same line, or wrap the expression in parentheses.
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
returnproducesundefined. Beware automatic semicolon insertion afterreturn. - Functions are values — they fit into every collection, callback, and pipeline.
Next: Functions in depth — closures, this, and higher-order patterns.










