Function expressions

A function in JavaScript isn’t some special keyword-only construct that lives apart from your data. It’s a value, the same way 42 or "hello" is a value. That single fact unlocks a lot: you can put a function in a variable, hand it to another function, or return it from one.

You’ve already met one way to build a function — the function declaration:

function sayHi() {
  alert( "Hello" );
}

There’s a second way to write the exact same thing, and it looks like an ordinary assignment. It’s called a function expression:

let sayHi = function() {
  alert( "Hello" );
};

The mechanics are worth slowing down on. On the right side of the =, you build a function value — function() { alert("Hello"); } — and then that value gets stored in the variable sayHi. Because the function is created as part of an expression (everything to the right of the = is an expression), that’s where the name function expression comes from.

Notice there’s no name between function and the (). That’s allowed here, and it’s normal. The variable already gives you a handle to call the function, so a separate name would be redundant. A function with no name of its own is called anonymous.

DECLARATION
function sayHi() { … }
EXPRESSION
let sayHi = function() { … };
↓ both produce ↓
sayHi
ƒ () { alert(“Hello”) }
Two syntaxes, same result: a callable function value sitting in a variable named sayHi.

Later on you’ll see functions that are created and used on the spot — called right away or scheduled to run later — without ever being stored in a variable. In those cases the function stays anonymous for good.

A function is a value

Say it plainly: however you create it, a function is a value. Both snippets above put a function value into sayHi.

Since it’s a value, you can print it. Pass sayHi (no parentheses) to alert and you’ll see the function’s own source code:

function sayHi() {
  alert( "Hello" );
}

alert( sayHi ); // shows the function code

The detail that trips people up: alert( sayHi ) does not run the function. There are no parentheses after sayHi, so nothing gets called. Some languages treat any mention of a function name as a call — JavaScript doesn’t. Here, sayHi is the function itself, and sayHi() is the act of calling it.

Yes, a function is a special kind of value — special because you can invoke it with (). But it’s still a value, so everything you can do with values applies. For one, you can copy it into another variable:

function sayHi() {   // (1) create
  alert( "Hello" );
}

let func = sayHi;    // (2) copy

func(); // Hello     // (3) run the copy — it works!
sayHi(); // Hello    //     and the original still works too

Step by step:

  1. Line (1) is a function declaration. It creates the function and stores it in sayHi.
  2. Line (2) copies the function value into func. Again, no parentheses. If you wrote func = sayHi() instead, you’d call sayHi first and store its return value in func — not the function.
  3. Both names now point at the same function, so sayHi() and func() do the same thing.
sayHi
func
─→
─→
ƒ () { alert(“Hello”) }
Copying a function copies the reference: both variables point at the one function value.

Try it live. The button copies the function value into a second variable and then calls both names — the original and the copy point at the same action, so you get the same greeting twice:

interactiveCopying a function value

Change const func = sayHi; to const func = sayHi(); (add the parentheses) and re-run — now func holds the string "Hello!", not the function, and calling func() would fail. That one detail is the whole point of this lesson.

The same works if you’d used a function expression to build sayHi in the first place:

let sayHi = function() { // (1) create
  alert( "Hello" );
};

let func = sayHi;  // (2) copy
// ...

Nothing changes. The copy behaves identically.

Callback functions

Passing functions around as values pays off the most when one function needs another to finish its job. Consider a function invite(question, accept, decline) with three parameters:

questionthe text to show
accepta function to run if the answer is “Yes”
declinea function to run if the answer is “No”

invite should show the question and then, based on the reply, call either accept() or decline():

function invite(question, accept, decline) {
  if (confirm(question)) accept()
  else decline();
}

function joinTrip() {
  alert( "See you on the trip!" );
}

function skipTrip() {
  alert( "Maybe next time." );
}

// usage: joinTrip and skipTrip are passed as arguments to invite
invite("Coming to the trip?", joinTrip, skipTrip);

Functions shaped like this show up constantly. A real invite would draw a proper dialog rather than lean on confirm, but the pattern is the same: invite doesn’t know or care what the two responses do. It just runs whichever one matches the answer.

The arguments joinTrip and skipTrip are called callback functions, or callbacks. You hand a function over and expect it to be “called back” later, if and when it’s needed. Here joinTrip is the callback for a “yes”, and skipTrip for a “no”.

invite(question,accept, decline)accept() → joinTripdecline() → skipTripYesNo
invite receives two functions and calls exactly one of them based on the answer.

You can compress all of this using function expressions written directly inside the call:

function invite(question, accept, decline) {
  if (confirm(question)) accept()
  else decline();
}

invite(
  "Coming to the trip?",
  function() { alert("See you on the trip!"); },
  function() { alert("Maybe next time."); }
);

The two functions are declared right there in the invite(...) call. They have no name — anonymous — and nothing outside invite can reach them, since they were never assigned to a variable. That’s fine, because you only need them for this one call. This style reads naturally and shows up everywhere in real JavaScript.

Here the same idea runs for real. invite gets a question and two callbacks; clicking a button answers the question, and invite calls exactly one of the callbacks. Notice that invite itself has no idea what “join” or “skip” means — it only knows how to call whichever function fits the answer:

interactiveCallbacks: invite decides which one to call

Function Expression vs Function Declaration

Two forms, one type of value — so when does the difference matter? Two things separate them: how they look, and when the engine creates them.

Start with the look:

  • Function Declaration — a function that stands on its own as a statement in the main flow of the code:

    // Function Declaration
    function sum(a, b) {
      return a + b;
    }
  • Function Expression — a function created inside an expression or some other construct. Here it lives on the right side of an assignment:

    // Function Expression
    let sum = function(a, b) {
      return a + b;
    };

The deeper difference is timing.

A function expression comes into existence only when execution reaches it. Until the line let sum = function… actually runs, the function doesn’t exist. After that line, you can call it, copy it, whatever you like.

A function declaration is available before its own line of code runs. A global declaration is usable across the entire script, no matter where you wrote it.

Why? Before running your script, the engine does a quick preparation pass. It scans for function declarations and creates them up front — think of it as an initialization stage. Only then does it start executing your code top to bottom, with those functions already in place. (This behavior is called hoisting.)

DECLARATION — hoisted
1. prep pass: function created
2. runs top-to-bottom
callable anywhere ✔
EXPRESSION — created in place
1. prep pass: nothing yet
2. runs, reaches the line
callable only after that line ✔
A declaration is set up during preparation; an expression is born only when its line executes.

So this works fine — the call sits above the declaration:

sayHi("Maya"); // Hello, Maya

function sayHi(name) {
  alert( `Hello, ${name}` );
}

The declaration sayHi was created during the preparation stage, so by the time line 1 runs, it already exists.

Swap in a function expression and the same layout breaks:

sayHi("Maya"); // error!

let sayHi = function(name) {  // (*) no magic any more
  alert( `Hello, ${name}` );
};

The expression is only created when execution reaches line (*) — and the call happened before that. Too late.

Block scope

Declarations have one more quirk worth knowing.

In strict mode, a function declaration inside a block is visible only within that block — never outside it. (This course runs in strict mode, which is the modern default.)

Suppose you want to define report() differently depending on a temp you get at runtime, then use it later. Reaching for a declaration inside if won’t do what you’d hope:

let temp = prompt("Current temperature?", 20);

// conditionally declare a function
if (temp < 15) {

  function report() {
    alert("Bundle up!");
  }

} else {

  function report() {
    alert("Nice and mild!");
  }

}

// ...use it later
report(); // Error: report is not defined

The declarations exist only inside their if/else blocks. Once you step outside the curly braces, report is gone.

Here’s the same rule shown from the inside, where the calls do work as long as they stay in the block:

let temp = 10; // take 10 as an example

if (temp < 15) {
  report();                // \   (runs)
                           //  |
  function report() {      //  |  A declaration is available
    alert("Bundle up!");   //  |  everywhere inside the block
  }                        //  |  where it lives — including
                           //  |  lines above it.
  report();                // /   (runs)

} else {

  function report() {
    alert("Nice and mild!");
  }
}

// Out here we're past the curly braces,
// so declarations made inside them are invisible.

report(); // Error: report is not defined
if (temp < 15) {report() ✔}report() ✘ not defined
A block-scoped declaration is trapped inside its braces.

How do you make report reachable after the if? Use a function expression and assign it to a variable that lives outside the block, where the visibility you want already exists:

let temp = prompt("Current temperature?", 20);

let report;

if (temp < 15) {

  report = function() {
    alert("Bundle up!");
  };

} else {

  report = function() {
    alert("Nice and mild!");
  };

}

report(); // ok now

The variable report is declared at the top level; the if only decides which function value gets stored in it. After the block, report still holds whatever was assigned.

You can shrink this further with the ternary ? operator, which picks one of two expressions:

let temp = prompt("Current temperature?", 20);

let report = (temp < 15) ?
  function() { alert("Bundle up!"); } :
  function() { alert("Nice and mild!"); };

report(); // ok now

Both branches are function expressions; the condition decides which one becomes report.

Type a temperature below and check. The value you enter picks which function expression gets stored in report at that moment — and only then is report() called:

interactiveChoosing a function with the ternary operator

Summary

  • Functions are values. You can assign them, copy them, and create them anywhere in your code.
  • A function written as a standalone statement in the main flow is a function declaration.
  • A function created as part of an expression — often on the right of an = — is a function expression.
  • Declarations are processed before the enclosing block runs (hoisting), so they’re callable throughout that block, even above their own line.
  • Expressions are created only when execution reaches them, so they’re callable only from that point on.

Prefer a function declaration most of the time: it’s visible before its own definition, which gives you freedom in how you arrange code, and it usually reads better. Reach for a function expression when a declaration can’t do the job — conditional definitions, inline callbacks, and the other patterns you’ll keep running into.