Functions are reusable blocks of code. In JavaScript, functions are first-class values: they can be assigned, passed as arguments, and returned from other functions.

Ways to Create Functions

1. Function declaration

function add(a, b) {
return a + b;
}

2. Function expression

const add = function (a, b) {
return a + b;
};

3. Arrow function

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

Arrow functions are shorter, but they also behave differently with this.

Parameters and Return Values

Default parameters

function greet(name = "Guest") {
return `Hello, ${name}`;
}

Rest parameters

function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}

Spread syntax

const values = [1, 2, 3];
console.log(sum(...values));

Return values

A function can return any value, including objects and other functions.

function createUser(name) {
return { name };
}

this and Arrow Functions

Regular functions get their own this depending on how they are called. Arrow functions do not create their own this; they capture it lexically from the outer scope.

const user = {
name: "Tom",
normal() {
console.log(this.name);
},
arrow: () => {
console.log(this.name);
},
};

In object methods, regular functions are usually the safer choice.

Scope and Closures

A function can access variables from its outer scope. That behavior is called a closure.

function createCounter() {
let count = 0;
return function () {
count += 1;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2

Closures are extremely useful for encapsulation and state preservation.

Callbacks, Promises, and async/await

Functions are the foundation of asynchronous JavaScript:

  • callbacks
  • promise handlers
  • async functions
function fetchData(callback) {
setTimeout(() => callback("done"), 1000);
}

Modern code more often uses:

async function loadData() {
const result = await Promise.resolve("done");
return result;
}

Higher-Order Functions

A higher-order function takes a function as an argument or returns one.

const numbers = [1, 2, 3];
const doubled = numbers.map((n) => n * 2);

This style is common in modern JavaScript.

Currying

const multiply = (a) => (b) => a * b;
const double = multiply(2);

Composition

const double = (x) => x * 2;
const increment = (x) => x + 1;
const composed = (x) => increment(double(x));

Alternative to Function Overloading

JavaScript does not support TypeScript-style overload signatures in plain runtime code. The usual approach is to inspect arguments manually:

function format(value, options) {
if (typeof value === "number") {
return value.toFixed(options?.digits ?? 2);
}
return String(value);
}

Performance and Best Practices

  • Keep functions focused on one responsibility
  • Prefer clear naming
  • Avoid unnecessary side effects
  • Use const for function references by default

Common Pitfalls

arguments vs rest parameters

Prefer rest parameters in modern code:

function sum(...values) {
return values.reduce((a, b) => a + b, 0);
}

Losing this

const user = {
name: "Tom",
say() {
console.log(this.name);
},
};
const fn = user.say;
fn(); // `this` is not `user`

You may need bind, a wrapper function, or a different call site.

Summary

Functions are one of the most important parts of JavaScript. To use them well, you need more than syntax:

  • understand this
  • understand closures
  • understand asynchronous usage
  • understand higher-order function patterns