JavaScript Functions Explained from Basic to Advanced Concepts

Functions are the backbone of JavaScript. This article walks through every function concept from basic declarations to closures, callbacks, pure functions, and higher-order functions.

7 min read

A function in JavaScript starts as a simple idea: a named block of code you can call from anywhere. But functions in JavaScript are also first-class objects. That means you can assign them to variables, pass them as arguments, return them from other functions, and give them properties.

This article maps the full landscape, from the first function hello() you write to closures and pure functions. Each concept links to a dedicated article where you can go deeper.

The Function Landscape

Every function concept in this article builds on the same starting point: a function you can define and call. From there, the concepts branch into three layers that build on each other.

  • Syntax layer: declarations, expressions, and arrow functions, plus parameters and return values.
  • Scope layer: lexical scope and closures, which explain what a function can see and remember.
  • Pattern layer: callbacks, higher-order functions, pure functions, and recursion, which combine the first two layers into reusable techniques.

Each section below covers one layer at a time, starting with syntax.

Defining Functions: Three Syntaxes

All three syntaxes create a callable function. The differences are in hoisting, this binding, and verbosity.

Function Declaration

javascriptjavascript
function greet(name) {
  return `Hello, ${name}!`;
}

Hoisted, meaning JavaScript moves the whole definition to the top of its scope. This lets you call it before the line where it appears in the file. It works well for standalone utility functions used throughout a file.

Function Expression

javascriptjavascript
const greet = function(name) {
  return `Hello, ${name}!`;
};

Not hoisted, since the function only exists once this line actually runs. This form is useful for assigning functions conditionally or passing them inline as arguments.

Arrow Function

javascriptjavascript
const greet = (name) => `Hello, ${name}!`;

Shorter syntax, and no own this, which makes arrow functions a natural fit for callbacks and array methods like map and filter.

For a side-by-side comparison of all three styles, see the dedicated arrow functions guide.

Parameters and Arguments

Parameters are the named placeholders in the function definition. Arguments are the actual values passed during the call.

javascriptjavascript
// price, quantity, taxRate are parameters
function calcTotal(price, quantity, taxRate = 0.08) {
  return price * quantity * (1 + taxRate);
}
 
// 20, 3 are arguments. taxRate uses the default 0.08
calcTotal(20, 3); // 64.8

Default parameters let you set fallback values. When an argument is missing or undefined, the default kicks in.

Rest parameters collect extra arguments into an array:

javascriptjavascript
function sum(...numbers) {
  return numbers.reduce((total, n) => total + n, 0);
}
 
console.log(sum(1, 2, 3, 4)); // 10

Return Values

Every function returns something. If there is a return statement, that value is sent back. If not, the function returns undefined.

javascriptjavascript
function double(n) {
  return n * 2;
}
 
function logIt(msg) {
  console.log(msg);
  // No return -- implicitly returns undefined
}
 
const a = double(5);  // a is 10
const b = logIt("hi"); // prints "hi", b is undefined

Scope: Where Variables Live

A function creates its own scope. Variables declared inside a function are not visible outside it:

javascriptjavascript
function outer() {
  const secret = "hidden";
  console.log(secret); // "hidden"
}
 
outer();
console.log(secret); // ReferenceError: secret is not defined

The reverse is not true, however: a function nested inside another function can see and use the outer function's variables, because the inner function is written inside the outer one's scope.

javascriptjavascript
function outer() {
  const message = "hello";
 
  function inner() {
    console.log(message); // Can access outer's message
  }
 
  inner();
}
 
outer(); // prints "hello"

This is lexical scope: a function's visibility is determined by where it is written, not where it is called. For a deeper look, read JavaScript Lexical Scope: A Complete Tutorial.

Closures: Functions That Remember

A closure is created when a function accesses variables from an outer scope, and that inner function survives beyond the outer function's execution.

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

createCounter runs once and returns the inner function. Even though createCounter has finished, the inner function still remembers the count variable. That is a closure.

Closures are how JavaScript achieves data privacy and stateful functions without classes. Learn them thoroughly in JavaScript Closures Deep Dive: Complete Guide.

Callbacks: Functions as Arguments

A callback is a function passed into another function to be called later:

javascriptjavascript
function fetchData(callback) {
  setTimeout(() => {
    callback("Data loaded");
  }, 1000);
}
 
fetchData((message) => {
  console.log(message); // "Data loaded" (after 1 second)
});

Callbacks are the traditional way to handle asynchronous operations in JavaScript, but they show up constantly in synchronous code too. Array methods like forEach accept a callback and run it once for every item in the array:

javascriptjavascript
[1, 2, 3].forEach((n) => console.log(n * 2));

For a complete explanation, see the dedicated callback tutorial.

Higher-Order Functions

A higher-order function is a function that takes another function as an argument or returns a function. The array methods map, filter, and reduce are built-in higher-order functions:

javascriptjavascript
const numbers = [1, 2, 3, 4, 5];
 
const doubled = numbers.map(n => n * 2);
const evens = numbers.filter(n => n % 2 === 0);
const sum = numbers.reduce((acc, n) => acc + n, 0);
 
console.log(doubled); // [2, 4, 6, 8, 10]
console.log(evens);   // [2, 4]
console.log(sum);     // 15

You can also write your own. The createCounter example from the closures section is a higher-order function, since it returns a function.

Pure Functions

A pure function always returns the same output for the same input and has no side effects:

javascriptjavascript
// Pure: same inputs always produce the same output
function add(a, b) {
  return a + b;
}
 
// Impure: depends on external state
let taxRate = 0.08;
function addTax(price) {
  return price * (1 + taxRate); // taxRate can change
}

Pure functions are predictable, testable, and composable. They are a cornerstone of functional programming. Learn more in Pure vs Impure Functions in JavaScript Explained.

Recursion

A recursive function calls itself:

javascriptjavascript
function countdown(n) {
  if (n <= 0) return;
  console.log(n);
  countdown(n - 1);
}
 
countdown(3);
// 3
// 2
// 1

Recursion is useful for tree traversal, mathematical sequences, and any problem that naturally breaks into smaller identical subproblems. See How to Use Recursion in JavaScript: Full Tutorial.

Learning Path

If you are new to functions, follow this order:

  1. Declarations and calling -- learn to define and invoke a function.

  2. Parameters and return values -- pass data in, get data out.

  3. Scope -- understand where variables are visible.

  4. Closures -- functions that remember their outer variables.

  5. Callbacks and higher-order functions -- functions as values.

  6. Pure functions -- predictable, side-effect-free code.

Each concept builds on the one before it. You do not need to understand closures to write a useful function. Start simple and add concepts as you need them.

Rune AI

Rune AI

Key Insights

  • Functions are reusable blocks of code that can be declared, expressed, or written as arrows.
  • Parameters receive input. The return keyword sends output back.
  • Functions create their own scope. Inner functions can access outer variables, forming closures.
  • Callbacks and higher-order functions let you pass functions as values.
  • Pure functions always produce the same output for the same input and have no side effects.
RunePowered by Rune AI

Frequently Asked Questions

What is the most important function concept to learn first?

Start with function declarations and calling. Once you can define a function, pass arguments, and use return values, everything else -- closures, callbacks, higher-order functions -- builds on that foundation.

Are arrow functions always better than regular functions?

No. Arrow functions are shorter and do not have their own this, which is useful for callbacks. But regular functions are better when you need hoisting, a named function for stack traces, or access to the arguments object.

What is the hardest function concept for beginners?

Closures are widely considered the most challenging. The idea that a function remembers variables from its parent scope even after the parent finishes executing takes time to internalize.

Conclusion

JavaScript functions start simple -- a reusable block of code with a name. But they scale into a rich system: expressions, arrows, parameters, return values, scope, closures, callbacks, higher-order functions, and pure functions. You do not need to master everything at once. Build from declarations to calling, from calling to scope, from scope to closures, and from closures to functional patterns.