Functions

Sooner or later you notice the same few lines showing up in three, four, five spots across a script. Maybe you flash a friendly greeting when a visitor signs in, again when they sign out, and once more somewhere in the middle. Copy-pasting that block everywhere means every future tweak has to be made everywhere too. Miss one spot and the behavior drifts.

Functions are the fix. They are the main building blocks of a program: you write a piece of logic once, give it a name, and run it as many times as you want from wherever you want.

You’ve already been calling functions someone else wrote — alert(message), prompt(message, default), confirm(question) are all built into the browser. Now you’ll write your own.

Function Declaration

The most common way to make a function is a function declaration. It reads left to right in a fixed shape:

function showMessage() {
  alert( 'Hello everyone!' );
}

Four parts, always in this order: the function keyword, the name you pick, a list of parameters in parentheses (comma-separated, empty here), and the body — the code to run — inside curly braces.

function
showMessage
name
( )
parameters
{ … }
body
The anatomy of a function declaration.

The general template looks like this:

function name(parameter1, parameter2, ... parameterN) {
 // body
}

Defining a function doesn’t run it. The body just sits there until you call it by writing its name followed by parentheses: showMessage().

function showMessage() {
  alert( 'Hello everyone!' );
}

showMessage();
showMessage();

Each call executes the body from the top. Two calls, two alerts. That’s the whole point of a function on display: write the greeting once, fire it as often as you need.

The payoff shows up when requirements change. Want a different message, or a fancier dialog instead of alert? You edit the one function body, and every call across the codebase picks up the change automatically.

Local variables

A variable declared with let (or const) inside a function lives only inside that function. Nobody on the outside can see it.

function showMessage() {
  let message = "Hello, I'm JavaScript!"; // local variable

  alert( message );
}

showMessage(); // Hello, I'm JavaScript!

alert( message ); // <-- Error! The variable is local to the function

The last line throws a ReferenceError. As far as the outside world is concerned, message never existed — it was born when the call started and vanished when the call finished.

showMessage()
message = ‘Hello…’
alert(message)  ✗  not visible here
A local variable is trapped inside the function's box. Outside code can't reach in.

Outer variables

A function can reach out and read variables declared in the surrounding code:

let userName = 'Maya';

function showMessage() {
  let message = 'Hello, ' + userName;
  alert(message);
}

showMessage(); // Hello, Maya

Inside showMessage, userName isn’t local, so JavaScript looks one level up and finds it in the outer scope. Reading works — and so does writing:

let userName = 'Maya';

function showMessage() {
  userName = "Theo"; // (1) changed the outer variable

  let message = 'Hello, ' + userName;
  alert(message);
}

alert( userName ); // Maya before the function call

showMessage();

alert( userName ); // Theo, the value was modified by the function

Because there’s no local userName, the assignment on line (1) reaches back out and overwrites the outer one. After the call, the change is visible everywhere.

That only happens while there’s no local variable of the same name. The moment you declare a local userName, it shadows the outer one — the function works with its own copy and leaves the outer variable untouched:

let userName = 'Maya';

function showMessage() {
  let userName = "Theo"; // declare a local variable

  let message = 'Hello, ' + userName; // Theo
  alert(message);
}

// the function will create and use its own userName
showMessage();

alert( userName ); // Maya, unchanged, the function did not access the outer variable
outer scope
userName = ‘Maya’
showMessage()
userName = ‘Theo’  ← used here
Same name, two boxes. The local `userName` shadows the outer one; assignments inside touch only the inner box.

The rule in one line: a function uses a local variable if one exists; otherwise it falls back to the nearest outer one.

Parameters

Parameters let you feed data into a function so it can behave differently on each call. List them in the parentheses, separated by commas.

function showMessage(from, text) { // parameters: from, text
  alert(from + ': ' + text);
}

showMessage('Priya', 'Hello!'); // Priya: Hello! (*)
showMessage('Priya', "What's up?"); // Priya: What's up? (**)

When the call runs at (*) and (**), the values you pass are copied into the local variables from and text. Inside the body they behave exactly like local variables that happened to arrive pre-filled.

Type a sender and a message below, then call the function. The same showMessage runs each time — only the arguments change.

interactiveCalling one function with different arguments

That word — copied — matters. A function receives a copy of each value, so reassigning a parameter inside the function can’t reach back and change the caller’s variable:

function showMessage(from, text) {

  from = '*' + from + '*'; // make "from" look nicer

  alert( from + ': ' + text );
}

let from = "Priya";

showMessage(from, "Hello"); // *Priya*: Hello

// the value of "from" is the same, the function modified a local copy
alert( from ); // Priya

The outer from stays "Priya". The function decorated its own private copy and threw it away when it returned.

from (outer)'Priya'
from (param)'*Priya*'
The parameter is a separate box holding a copy. Reassigning it leaves the outer variable alone.

Default values

Call a function with fewer arguments than it declares, and the missing ones become undefined. That’s not an error:

showMessage("Priya");

With the earlier definition, that prints "*Priya*: undefined"text was never passed, so it’s undefined, and string concatenation turns it into the text "undefined".

Often you’d rather supply a sensible fallback. Add = after a parameter to give it a default value used when the argument is omitted:

function showMessage(from, text = "no text given") {
  alert( from + ": " + text );
}

showMessage("Priya"); // Priya: no text given

The default also kicks in when the argument is explicitly undefined, since that’s indistinguishable from “not passed”:

showMessage("Priya", undefined); // Priya: no text given
argument passed and not undefined?  →  use the argument
argument missing or undefined, default given?  →  evaluate the default
argument missing, no default?  →  value is undefined
How JavaScript resolves a parameter's value on each call.

A default doesn’t have to be a plain literal. It can be any expression, even a function call, and it’s only evaluated when the argument is actually missing:

function showMessage(from, text = anotherFunction()) {
  // anotherFunction() only executed if no text given
  // its result becomes the value of text
}

Alternative default parameters

Sometimes you’d rather decide the fallback in the body instead of the parameter list — maybe the logic is involved, or depends on other parameters computed first. You can compare against undefined yourself:

function showMessage(text) {
  // ...

  if (text === undefined) { // if the parameter is missing
    text = 'empty message';
  }

  alert(text);
}

showMessage(); // empty message

Or reach for ||:

function showMessage(text) {
  // if text is undefined or otherwise falsy, set it to 'empty'
  text = text || 'empty';
  ...
}

When the only values you want to reject are null and undefined — but 0 or an empty string should pass through as real input — the nullish coalescing operator ?? is the precise tool:

function showCount(count) {
  // if count is undefined or null, show "unknown"
  alert(count ?? "unknown");
}

showCount(0); // 0
showCount(null); // unknown
showCount(); // unknown

Notice showCount(0) prints 0, not "unknown". With || it would have printed "unknown", because 0 is falsy. That difference is exactly why ?? exists.

Try each argument below and watch the two fallbacks disagree exactly when the value is 0 or "" — falsy, but not missing.

interactive?? keeps real zeros, || throws them away

Returning a value

A function can hand a result back to the code that called it, using return. The classic example adds two numbers:

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

let result = sum(1, 2);
alert( result ); // 3

return a + b computes the sum and ships it out. The call sum(1, 2) evaluates to 3, which lands in result.

Change either number and the returned value updates live. The function has no idea where its result goes — it just hands one back.

interactivesum(a, b) returns a value

return can appear anywhere in the body. The instant execution hits it, the function stops and control jumps back to the caller with the given value. A function can have several return statements, and only the first one reached actually fires:

function checkAge(age) {
  if (age >= 18) {
    return true;
  } else {
    return confirm('Do you have permission from your parents?');
  }
}

let age = prompt('How old are you?', 18);

if ( checkAge(age) ) {
  alert( 'Access granted' );
} else {
  alert( 'Access denied' );
}
checkAge(20)
→ runs body →
return true
→ caller receives →
true
`return` ends the function immediately and sends its value back to the caller.

You can also write return with no value to bail out of a function early:

function showMovie(age) {
  if ( !checkAge(age) ) {
    return;
  }

  alert( "Showing you the movie" ); // (*)
  // ...
}

If checkAge(age) is falsy, the bare return exits before line (*), so the movie never shows. This “guard clause” pattern keeps the happy path un-indented and easy to follow.

Naming a function

A function does something, so its name usually reads as a verb. Keep it short, accurate, and descriptive enough that a reader understands the action without opening the body.

A widely-followed habit is to start the name with a verb prefix that hints at the kind of work. Teams agree on what each prefix means and stick to it. Functions beginning with "show", for instance, tend to show something.

Common prefixes:

  • "get…" — return a value,
  • "calc…" — calculate something,
  • "create…" — create something,
  • "check…" — check something and return a boolean.

Some names built from them:

showMessage(..)     // shows a message
getAge(..)          // returns the age (gets it somehow)
calcSum(..)         // calculates a sum and returns the result
createForm(..)      // creates a form (and usually returns it)
checkPermission(..) // checks a permission, returns true/false

With a shared vocabulary of prefixes, a glance at a call site tells you both what the function does and roughly what it gives back.

Functions == Comments

Short functions that each do one thing are a joy to read. When one thing grows too big, that’s a hint to split it into smaller named functions. It isn’t always obvious how, but it pays off.

A well-named helper is easier to test and debug — and its name doubles as a comment. Compare two versions of showPrimes(n), both printing the prime numbers below n.

The first inlines the primality test with a nested loop and a label:

function showPrimes(n) {
  nextPrime: for (let i = 2; i < n; i++) {

    for (let j = 2; j < i; j++) {
      if (i % j == 0) continue nextPrime;
    }

    alert( i ); // a prime
  }
}

The second pulls the test into its own function, isPrime:

function showPrimes(n) {

  for (let i = 2; i < n; i++) {
    if (!isPrime(i)) continue;

    alert(i);  // a prime
  }
}

function isPrime(n) {
  for (let i = 2; i < n; i++) {
    if ( n % i == 0) return false;
  }
  return true;
}

The second reads better. Where the first has a knot of nested loops and a label, the second just says isPrime(i) — the name is the explanation. Code like this is often called self-describing.

Here is that self-describing version running. Drag the slider to change n; showPrimes leans entirely on the well-named isPrime helper.

interactiveshowPrimes(n) reads like prose thanks to isPrime

So it’s worth creating a function even when you’ll call it exactly once. Structure and readability are reasons enough.

Summary

A function declaration has this shape:

function name(parameters, delimited, by, comma) {
  /* code */
}
  • Values passed in as arguments are copied into the function’s local variables.
  • A function can read outer variables, but the visibility is one-way: outside code cannot see anything declared inside.
  • A function can return a value. With no return, or an empty one, the result is undefined.

For clean code, lean on parameters and local variables rather than reaching out to modify outer ones. A function that takes inputs, works with them, and returns a result is far easier to follow than one that silently mutates global state as a side effect.

On naming:

  • The name should describe what the function does, so a call site reads clearly.
  • Functions are actions, so names are usually verbs.
  • Lean on well-known prefixes — create…, show…, get…, check… — to signal intent.

Functions are the main building blocks of scripts. You’ve got the basics now, which is enough to start writing real ones. We’ll keep coming back to them and dig into their more advanced powers as the course goes on.