Key Takeaways
- JavaScript functions package reusable logic. Defining a function creates it; calling it executes its body.
- Parameters name the inputs; arguments supply their values. Default parameters apply when an argument is omitted or is undefined.
- Arrow functions offer concise syntax, but they also capture surrounding this and arguments rather than creating their own bindings.
- Choose declarations for named operations, arrows for concise callbacks, and object method shorthand when a method needs its receiver as this.
JavaScript functions let you define an operation once and call it with different inputs. You can write them as declarations, traditional function expressions, or arrow functions—but arrows aren't simply shorter regular functions.
ES6, also called ECMAScript 2015, introduced arrow functions, default parameters, and rest parameters. This tutorial explains those features alongside the traditional function forms you'll still use. The ECMAScript 2015 specification defines these additions.
Why Use JavaScript Functions?
Suppose you want to add 50 to two numbers and print the results:
let a = 4;
let b = 3;
a += 50;
console.log(a); // 54
b += 50;
console.log(b); // 53A function centralizes the repeated operation:
let a = 4;
let b = 3;
const addAndPrint = (x) => {
x += 50;
console.log(x);
};
addAndPrint(a); // 54
addAndPrint(b); // 53
console.log(a, b); // 4 3Both snippets print the transformed numbers, but they don't make the same updates. In the function, x is a local parameter binding: assigning to it doesn't change a or b. If the caller needs an updated value, return that value and assign it in the caller.
The maintainability benefit is centralizing behavior. If the operation changes, you update one function rather than finding every duplicated copy.
The Anatomy of a Function
Bindings, Definitions, and Calls
A variable can hold a function value just as it can hold a number:
let x = 4;
const y = () => {};
y(); // Calls the function stored in y.Here, y is a const binding holding an arrow function expression. The definition doesn't execute the body; the parentheses in y() invoke, or call, the function.
Parameters and Arguments
Parameters are the names in the function definition. Arguments are the values you pass when calling it:
const printSum = (a, b) => {
console.log(a + b);
};
printSum(2, 5); // 7In this example, a and b are parameters; 2 and 5 are arguments. During this call, the function adds those arguments and logs 7.
Default Parameters
A default parameter supplies a fallback when an argument is omitted or explicitly undefined:
const printSum = (a, b = 1) => {
console.log(a + b);
};
printSum(1); // 2
printSum(1, 2); // 3
printSum(1, undefined); // 2
printSum(1, 0); // 1The default for b doesn't replace every falsy value. Values such as 0, false, an empty string, and null don't trigger it. See the default parameter reference for the precise rules.
Returning Values Versus Performing Actions
Use return when the caller needs a result:
const myFunction = (x) => {
return x * 2;
};
const myValue = myFunction(2);
console.log(myValue); // 4The call myFunction(2) produces 4, which becomes the value of myValue. A return statement also ends that function call's execution.
A function can instead perform a side effect: an action outside the calculation, such as logging a message.
const myFunction = () => {
console.log("hello world");
};
const result = myFunction(); // Prints hello world.
console.log(result); // undefinedThe function must be called to print the message. When an ordinary synchronous function reaches the end of its body without returning a value, its result is undefined—not literally the absence of a value.
Rest Parameters for Multiple Inputs
A rest parameter collects the remaining arguments into an array:
const sum = (...numbers) => {
let total = 0;
for (const number of numbers) {
total += number;
}
return total;
};
console.log(sum(2, 5, 3)); // 10
console.log(sum()); // 0You can have only one rest parameter, and it must be last. Rest parameters work with arrows and ordinary functions; they're a clear way to express a variable number of inputs.
How to Define a Function in JavaScript
Function Declarations
A function declaration defines a named function directly:
function addDeclared(x, y) {
return x + y;
}
console.log(addDeclared(1, 2)); // 3Use declarations for named operations that deserve a clear place in your program. Their hoisting behavior also lets you call them before their declaration appears within the same scope.
Traditional Function Expressions
A function expression creates a function as part of an expression—for example, the initializer of a variable:
const addExpression = function (x, y) {
return x + y;
};
console.log(addExpression(1, 2)); // 3This is a function expression, not a function declaration or a special “ES5 mode.” It doesn't require var. Use this form when you want a function value with ordinary function behavior, including its own this and arguments bindings.
Arrow Functions and Implicit Returns
Arrow functions use the => syntax. An expression body implicitly returns its expression's value; a block body needs an explicit return to return a result:
const addArrow = (x, y) => x + y;
const addWithBlock = (x, y) => {
return x + y;
};
console.log(addArrow(1, 2)); // 3
console.log(addWithBlock(1, 2)); // 3To implicitly return an object literal, wrap it in parentheses so its braces aren't interpreted as a function body:
const makePoint = (x, y) => ({ x, y });
console.log(makePoint(2, 5)); // { x: 2, y: 5 }Prefer a block body when several statements make the operation easier to follow. Concision helps only while the function remains readable.
Object Method Shorthand
For a function attached to an object, method shorthand makes the relationship explicit:
const calculator = {
offset: 10,
add(x, y) {
return this.offset + x + y;
}
};
console.log(calculator.add(1, 2)); // 13In calculator.add(1, 2), calculator is the receiver—the object used for the method call—and this refers to it. Unlike a plain function expression, a shorthand method can't be used as a constructor. Explore the surrounding syntax in our JavaScript objects tutorial.
Function Forms at a Glance
| Form | Example syntax | this behavior | Available before definition? | Usable with new? |
|---|---|---|---|---|
| Declaration | function add(a, b) { return a + b; } | Determined by the call | Yes, within its scope | Yes |
| Traditional expression | const add = function (a, b) { return a + b; }; | Determined by the call | Not before const initialization | Yes |
| Arrow expression | const add = (a, b) => a + b; | Captured from enclosing scope | Not before const initialization | No |
| Object method shorthand | const obj = { add(a, b) { return a + b; } }; | Receiver in obj.add() | Not before object initialization | No |
The constructor column describes the plain synchronous functions shown here, not async functions or generators. Constructor support comes from the function form, not from choosing const or let.
Scope and Hoisting: Declarations Versus Expressions
Scope is the region where a name is accessible. Hoisting means a declaration is processed before the statements in its scope execute; it doesn't mean JavaScript physically moves your source code.
console.log(addDeclared(1, 2)); // 3
function addDeclared(x, y) {
return x + y;
}
// Calling addExpression here would throw ReferenceError.
const addExpression = function (x, y) {
return x + y;
};
console.log(addExpression(1, 2)); // 3A function declaration is initialized before execution reaches its position in the scope. A const or let binding is inaccessible until initialization: the earlier period is its temporal dead zone, and accessing it then throws ReferenceError. This applies whether the initializer contains an arrow or a traditional function expression.
Don't turn this into the blanket rule that expressions “aren't hoisted.” With var, the binding initially contains undefined, but the function value still isn't available before assignment. The function expression reference demonstrates that distinction.
Use const when you don't intend to reassign the binding, not as a general solution to naming conflicts. Manage names through clear naming and appropriate scopes. Our comparison of var, let, and const and MDN's temporal dead zone explanation cover the binding rules.
Arrow Functions Versus Regular Functions
Arrows Capture the Surrounding this
An ordinary function's this depends on how it is called. An arrow instead uses this from its enclosing lexical scope—the surrounding code where the arrow was created.
const user = {
name: "Ada",
makeGreeting() {
return () => `Hello, ${this.name}`;
}
};
const greet = user.makeGreeting();
console.log(greet()); // Hello, AdaCalling user.makeGreeting() gives the method user as this. The returned arrow retains access to that surrounding this even when greet() is called without a receiver. This is a closure: a function keeping access to its surrounding environment.
Arrows Don't Have Their Own arguments
Ordinary functions provide an arguments object containing the arguments passed to that call. Arrows don't create their own arguments binding; a reference to arguments resolves through the surrounding scope instead. Use explicit parameters or rest parameters when an arrow needs its own inputs.
Arrows Aren't Constructors
Calling an arrow with new throws TypeError. Arrows also have no own .prototype property by default, unlike the plain traditional functions shown earlier. Assigning an arrow to a variable or an object property doesn't change those restrictions.
Functions as Values: Passing a Callback
A callback is a function you pass to another function for it to call. For example, map() builds a new array using a callback's returned values:
const double = (number) => number * 2;
const doubled = [1, 2, 3].map(double);
console.log(doubled); // [2, 4, 6]Pass double, the function value—not double(), which would call it immediately. You can also write the arrow directly inside map():
const doubled = [1, 2, 3].map((number) => number * 2);
console.log(doubled); // [2, 4, 6]The map() reference explains its callback contract. For more practice, continue with JavaScript arrays and array methods.
Common Mistakes
- Defining without calling: storing a function in a variable doesn't execute its body.
- Forgetting return after adding braces: changing an expression-bodied arrow to a block body requires an explicit return to preserve its result.
- Using an arrow as a receiver-based method: lexical this doesn't automatically become the object containing the arrow.
- Confusing local assignment with caller updates: changing a numeric parameter doesn't change the caller's variable.
A practical starting rule: use declarations for named operations, arrows for small callbacks, and method shorthand for receiver-based behavior. Use traditional function expressions when you need a function value with ordinary function semantics.
This tutorial focuses on synchronous functions. Async functions always return promises; they and generators need additional concepts beyond these basics. Continue with our JavaScript async/await example when you're ready, or practice reusable operations inside JavaScript loops.
FAQ
What is the difference between an arrow function and a regular function in JavaScript?
Arrow functions offer concise syntax and capture this and arguments from their enclosing scope. Ordinary functions create their own bindings, and plain traditional functions can be constructors; arrows cannot.
When should I use an arrow function in JavaScript?
Use arrows for concise callbacks and functions that should retain the surrounding this. Prefer ordinary method syntax when this must refer to the object used for the method call.
What is the difference between a function declaration and a function expression?
A function declaration defines a named function that can be called before its declaration within its scope. A function expression creates a function value during expression evaluation; when stored in const or let, its binding cannot be accessed before initialization.
Can an arrow function be used as a constructor?
No. Calling an arrow function with new throws TypeError, and arrows have no own .prototype property by default. Use a class or a constructible traditional function when you need constructor behavior.
What does a JavaScript function return if there is no return statement?
An ordinary synchronous function that reaches the end of its body returns undefined. An expression-bodied arrow implicitly returns its expression's value, even without a written return statement.
Further Reading
- MDN: Functions guide — definitions, calls, parameters, and closures.
- MDN: Arrow function expressions — concise bodies, lexical bindings, and limitations.
- MDN: Function declarations — declaration syntax, scope, and hoisting.
- MDN: Function expressions — function values and initialization behavior.
- MDN: Default parameters — omitted arguments and undefined.
- MDN: Rest parameters — collecting arguments into an array.
- MDN: Method definitions — object method shorthand and constructor restrictions.
- MDN: this — receivers and lexical this.
