Functions and Scope

Table of Contents

  1. Why Functions?
  2. Three Ways to Write a Function
  3. Parameters, Defaults, and Rest
  4. Return Values
  5. Pure Functions and Side Effects
  6. Scope: Who Can See What
  7. Hoisting, Briefly
  8. Closures
  9. Callbacks: Passing a Function In
  10. Try It Yourself

1. Why Functions?

A function is a named, reusable piece of code. You write the steps once, give them a name, and call them whenever you need them. This gives you three things:

  • No repetition: fix a bug in one place, not ten.
  • Names that explain: formatYear(2025) tells the reader more than four lines of arithmetic.
  • Small pieces: a big program becomes many small functions that you can understand and test one at a time.

Dara's page will need functions for almost everything: showing a project, checking a form, switching to dark mode. This chapter gives you the tools to write them well.

2. Three Ways to Write a Function

Here is the same idea written three ways.

A function declaration starts with the keyword function:

function greet(name) {
  return "Hello, " + name + "!";
}

console.log(greet("Dara")); // "Hello, Dara!"

A function expression stores a function in a variable:

const double = function (n) {
  return n * 2;
};

An arrow function is a shorter form of an expression:

const triple = (n) => n * 3;

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

Two arrow-function rules to remember:

  • If the body is a single expression, you can drop the braces and the word return; the value is returned automatically (triple above).
  • To return an object this way, wrap it in parentheses, so JavaScript does not think the braces are a function body:
const makeBadge = (title) => ({ label: title, isNew: true });
FormLooks likeNotes
Declarationfunction greet() {}Can be called before its line (see hoisting)
Expressionconst greet = function () {}Only works after its line
Arrowconst greet = () => {}Short; best for small functions and callbacks

Which should you use? Pick one style and stay consistent. A common choice, and the one in this series: declarations for the main named functions, arrows for short helpers and callbacks.

3. Parameters, Defaults, and Rest

The names in the parentheses are parameters; the values you pass in are arguments. A parameter is like a variable that is created fresh each time the function runs.

function describe(title, year = 2025, ...tags) {
  return `${title} (${year}) [${tags.join(", ")}]`;
}

console.log(describe("Recipe Finder"));
console.log(describe("Recipe Finder", 2024, "html", "css"));
console.log(describe("Quick Note", undefined, "js"));

The output is:

Recipe Finder (2025) []
Recipe Finder (2024) [html, css]
Quick Note (2025) [js]
  • Default value: year = 2025 is used when the argument is missing or undefined. In the third call, undefined was passed on purpose to keep the default.
  • Rest parameter: ...tags collects all remaining arguments into a real array. It must be the last parameter.
  • If you pass too few arguments, the missing parameters are undefined. If you pass too many, the extras are ignored (unless you use rest).

4. Return Values

return ends the function and sends a value back to the caller:

function sign(n) {
  if (n < 0) {
    return "negative";
  }

  return "not negative";
}

console.log(sign(-1)); // "negative"
console.log(sign(3)); // "not negative"

The line after a return that runs is never reached. This lets you write early returns: handle the unusual case first, then continue with the normal case. It avoids deeply nested if blocks.

A function with no return gives back undefined:

function noReturn() {
  const x = 1;
}

console.log(noReturn()); // undefined

A very common beginner mistake is to print a result inside a function (console.log) when you meant to return it. A printed value is gone; a returned value can be stored, tested, and passed to the next function.

5. Pure Functions and Side Effects

A function is pure when it depends only on its arguments, and the only thing it does is return a result. Give it the same input and you always get the same output.

const withDiscount = (price, percent) => price - (price * percent) / 100;

console.log(withDiscount(100, 10)); // 90

A function has a side effect when it also changes something outside itself: printing, changing a variable, editing the page, saving a file.

const log = [];

function addToLog(message) {
  log.push(message); // changes data outside the function
}

addToLog("page loaded");
console.log(log); // [ 'page loaded' ]

Side effects are not bad; programs need them to do anything useful. But pure functions are far easier to test and to trust. A good habit: keep the calculating in pure functions, and keep the side effects in a few small, clearly named places. Dara's page will follow this: one pure function decides which projects to show, and a separate function puts them on the page.

6. Scope: Who Can See What

Scope is the area of code where a variable can be used. JavaScript has three levels you need to know.

ScopeCreated byVisible
GlobalTop level of the fileEverywhere
FunctionA function bodyOnly inside that function
BlockAny { }, such as if or forOnly inside those braces (let and const)
const siteName = "Dara Sok"; // global

function showScope() {
  const local = "inside"; // function scope

  if (true) {
    const blockOnly = "block"; // block scope
  }

  console.log(blockOnly); // ReferenceError: blockOnly is not defined
  return siteName + " " + local;
}

Inner code can see outer variables, but outer code cannot see inner ones. Inside showScope, siteName is visible. Outside it, local is not:

console.log(local); // ReferenceError: local is not defined

JavaScript looks for a name starting in the current scope, then moves outward one level at a time. If an inner variable has the same name as an outer one, the inner one shadows it:

const x = "outer";

function shadow() {
  const x = "inner";
  return x;
}

console.log(shadow(), x); // "inner" "outer"

Why does this matter? Small scopes protect you. A variable that lives only inside one function cannot be changed by mistake from somewhere else. So: declare variables as close to where you use them as possible, and avoid global variables.

One old keyword, var, ignores block scope and leaks out of if and for braces. You will see it in old code. Do not write it; use const and let.

7. Hoisting, Briefly

Before running your code, JavaScript scans it and registers declarations. This is called hoisting, and it has one practical effect: function declarations can be called before the line where they appear.

console.log(hoisted()); // "works"

function hoisted() {
  return "works";
}

But const, let, and function expressions are not usable before their line. Trying gives an error:

console.log(later); // ReferenceError: Cannot access 'later' before initialization
const later = 1;

You do not need to memorize the rules. The advice is simple: define things before you use them, and when you see "Cannot access ... before initialization", move the definition up.

8. Closures

A function remembers the variables around the place where it was created, even after the outer function has finished. This is called a closure.

function makeCounter() {
  let count = 0;

  return () => {
    count++;
    return count;
  };
}

const counterA = makeCounter();
const counterB = makeCounter();

console.log(counterA(), counterA(), counterA()); // 1 2 3
console.log(counterB()); // 1

makeCounter has finished running, yet the returned function still reaches count. Each call to makeCounter creates a separate count, so counterB starts from 1. Nothing outside can touch count directly; it is private to those functions.

A closure is also a handy way to "pre-fill" a function:

function makeGreeter(greeting) {
  return (name) => `${greeting}, ${name}!`;
}

const sayHello = makeGreeter("Hello");
console.log(sayHello("Dara")); // "Hello, Dara!"

You will meet closures again in chapter 8, when an event handler "remembers" which button it belongs to.

9. Callbacks: Passing a Function In

In JavaScript, a function is a value, just like a number or a string. You can store it in a variable, and you can pass it to another function. A function passed as an argument is called a callback.

function repeat(times, action) {
  for (let i = 1; i <= times; i++) {
    action(i);
  }
}

repeat(3, (n) => console.log("Round", n));

This prints Round 1, Round 2, Round 3. The function repeat does not know what the work is; the caller decides.

You have already used the idea. Array methods take a callback:

const titles = ["Recipe Finder", "Weather Dashboard"];
const upper = titles.map((title) => title.toUpperCase());

console.log(upper); // [ 'RECIPE FINDER', 'WEATHER DASHBOARD' ]

Callbacks are also how JavaScript waits. You say, "run this function later":

setTimeout(() => console.log("later"), 1000);
console.log("first");

first appears immediately and later after one second. This "call me back when it is ready" pattern is the foundation of events (chapter 8) and asynchronous code (chapter 9).

10. Try It Yourself

  1. Three forms. Write a function isEven(n) as a declaration, then as an expression, then as a one-line arrow. Test all three with 4 and 7.

  2. Defaults. Write welcome(name, greeting = "Hello") that returns a string. Call it with one argument and with two.

  3. Rest. Write average(...numbers) that returns the average. What should it return for no arguments? Handle that case with an early return.

  4. Return, not print. Write formatTitle(title) that returns the title in uppercase with " - Dara Sok" added. Use the result in a console.log outside the function.

  5. Find the scope error. Predict what each line prints, then run it:

    const a = 1;
    function test() {
      const b = 2;
      if (b > 1) {
        const c = 3;
      }
      console.log(a, b);
      console.log(c);
    }
    test();
    
  6. Closure. Write makeMultiplier(factor) that returns a function multiplying its input by factor. Create double and triple from it and test them.

Next, we'll look closely at the two structures JavaScript programs use most, arrays and objects, and model Dara's projects as real data.

Buy Me a Coffee

Found this tutorial helpful? Consider buying me a coffee to support more free content!