Functions and Scope in JavaScript

· 12 min read · beginner
functions scope closures beginner
Part 2 of the javascript-fundamentals series

Functions are the building blocks of JavaScript. They let you group code that performs a specific task, making your programs modular, reusable, and easier to understand. In this tutorial, you’ll learn the different ways to define functions in JavaScript, how scope works, and why closures are so powerful.

Function Declarations

The most common way to create a function is with a function declaration. It starts with the function keyword, followed by the function name, parentheses for parameters, and a code block wrapped in curly braces.

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

const message = greet("Alice");
console.log(message); // "Hello, Alice!"

Function declarations are hoisted, meaning JavaScript moves them to the top of their scope before executing code. This lets you call a function before its actual declaration in the code:

// This works because of hoisting
const result = add(5, 3);
console.log(result); // 8

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

Function Expressions

A function expression assigns a function to a variable. Unlike declarations, expressions are not hoisted, so you can only call them after the assignment.

const multiply = function(a, b) {
  return a * b;
};

console.log(multiply(4, 5)); // 20

Function expressions are incredibly flexible. You can pass them as arguments to other functions, return them from functions, and store them in arrays or objects.

const operations = [
  function(a, b) { return a + b; },
  function(a, b) { return a - b; },
  function(a, b) { return a * b; }
];

console.log(operations[0](10, 5)); // 15 (addition)
console.log(operations[1](10, 5)); // 5  (subtraction)
console.log(operations[2](10, 5)); // 50 (multiplication)

Arrow Functions

ES6 introduced arrow functions, a shorter syntax for writing functions. They’re especially useful for callbacks and functional programming patterns.

// Full arrow function
const add = (a, b) => {
  return a + b;
};

// Shorthand when there's only one expression
const double = x => x * 2;

// Single parameter doesn't need parentheses
const square = x => x * x;

// No parameters
const getRandom = () => Math.random();

Arrow functions have a special behavior with this: they don’t create their own this binding. Instead, they inherit this from the surrounding scope. This makes them perfect for methods inside objects or classes.

const person = {
  name: "Bob",
  greet: () => {
    // Arrow function doesn't have its own 'this'
    console.log(`Hello, I'm ${this.name}`);
  },
  greetRegular() {
    // Regular function has its own 'this'
    console.log(`Hello, I'm ${this.name}`);
  }
};

person.greet();      // "Hello, I'm undefined"
person.greetRegular(); // "Hello, I'm Bob"

Understanding Scope

Scope determines where variables and functions are accessible in your code. JavaScript has three main types of scope: global, function, and block.

Global Scope

Variables declared outside any function or block are in the global scope. They’re accessible everywhere in your script.

const globalVar = "I'm everywhere";

function accessGlobal() {
  console.log(globalVar); // Accessible here
}

accessGlobal(); // "I'm everywhere"
console.log(globalVar); // Also accessible here

Function Scope

Variables declared with var inside a function are only accessible within that function. This is called function scope.

function demonstrateScope() {
  var functionVar = "I'm local to this function";
  console.log(functionVar); // Works
}

demonstrateScope(); // "I'm local to this function"
console.log(functionVar); // ReferenceError: functionVar is not defined

Block Scope

ES6 introduced let and const for block scope. Variables declared with these keywords are only accessible within the nearest set of curly braces {}.

if (true) {
  let blockVar = "I'm block-scoped";
  const alsoBlockScoped = "Me too";
  console.log(blockVar); // Works
}

console.log(blockVar); // ReferenceError
console.log(alsoBlockScoped); // ReferenceError

This matters particularly in loops:

// Using var (function-scoped)
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// Output: 3, 3, 3 (i is shared across all callbacks)

// Using let (block-scoped)
for (let j = 0; j < 3; j++) {
  setTimeout(() => console.log(j), 100);
}
// Output: 0, 1, 2 (j is unique to each iteration)

Closures

A closure is a function that remembers variables from its outer scope even after the outer function has finished executing. This is one of JavaScript’s most powerful features.

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

The inner function “closes over” the count variable, keeping it alive even after createCounter() has returned. This pattern is incredibly useful for creating private data and factory functions.

Practical Closure Example

Closures are perfect for creating functions with preset configurations:

function createGreeter(greeting) {
  return function(name) {
    return `${greeting}, ${name}!`;
  };
}

const sayHello = createGreeter("Hello");
const sayHi = createGreeter("Hi");
const sayHowdy = createGreeter("Howdy");

console.log(sayHello("Alice")); // "Hello, Alice!"
console.log(sayHi("Bob"));      // "Hi, Bob!"
console.log(sayHowdy("Carol")); // "Howdy, Carol!"

Each returned function maintains its own greeting value—this is closure in action.

Summary

You’ve learned the three main ways to create functions in JavaScript: declarations, expressions, and arrow functions. Each has its use case—declarations for traditionally structured code, expressions for flexibility, and arrow functions for concise callbacks.

You also explored scope: global scope makes variables accessible everywhere, function scope (with var) limits them to functions, and block scope (with let and const) limits them to code blocks. Understanding scope helps you avoid bugs and write cleaner code.

Finally, you saw how closures let functions remember their surrounding scope, enabling powerful patterns like data privacy and function factories.

In the next tutorial, you’ll learn about objects and arrays—JavaScript’s primary data structures for organizing and storing collections of data.