# Function Declarations vs Expressions: Hoisting Explained

> Learn the difference between function declarations and expressions, how hoisting works under the hood, and when to use arrow functions in practice.

**Published:** 2026-04-04 | **Updated:** 2026-04-04

---


At some point while writing JavaScript, you'll end up with code like this:

```javascript
greet(); // "Hello World!"

function greet() {
  console.log("Hello World!");
}
```

Called before the declaration, and it works. But change it to this:

```javascript
greet(); // TypeError: greet is not a function

var greet = function () {
  console.log("Hello World!");
};
```

Error. Ten minutes of `console.log` debugging later, you finally read the message: `TypeError: greet is not a function`. It's a function — yet it's not a function.

Turns out JavaScript does this to everyone, not just me. "Why does the same function behave differently?" — Sound familiar?

{{< img src="images/contents/code-on-screen.jpg" alt="Code on a dark monitor screen — knowing how the functions you use every day actually work changes how you see them" caption="Photo: <a href='https://unsplash.com/ko/@gamell' target='_blank' title='Opens in new window'>Joan Gamell</a> / <a href='https://unsplash.com/ko' target='_blank' title='Opens in new window'>Unsplash</a>" >}}

Function declarations and function expressions may look similar, but they behave differently. The key to that difference is **hoisting**. Let's break it down with practical examples.

## Function Declarations

The most basic function form, starting with the `function` keyword.

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

A name is required — `function () {}` without a name isn't valid here.

## Function Expressions

Assigning a function to a variable, treating it as a value.

```javascript
const add = function (a, b) {
  return a + b;
};
```

The `function (a, b) { ... }` on the right is an **anonymous function expression**. You can give it a name too.

```javascript
const add = function addNumbers(a, b) {
  return a + b;
};
```

Naming the function means the function name shows up in stack traces, which helps with debugging. However, `addNumbers` can only be referenced inside the function — externally, you call it via `add`.

{{< img src="images/contents/hoisting-concept.png" alt="Illustration of code blocks being lifted upward — hoisting refers to declarations being moved to the top of their scope" >}}

## The Key Difference: Hoisting

The most important difference between the two comes down to **hoisting**.

Hoisting is the behavior where the JavaScript engine moves variable and function declarations to the top of their scope before executing any code. The code doesn't literally move — it happens during the engine's preparation of the execution context.

### Function declarations are fully hoisted

```javascript
// Works even when called before the declaration
console.log(add(2, 3)); // 5

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

Before executing code, the JavaScript engine registers the entire function declaration (name + body). That's why calling it before the declaration is fine.

Internally, this is what the engine processes conceptually:

```javascript
// Conceptual order of engine processing
function add(a, b) {
  return a + b;
}

console.log(add(2, 3)); // 5
```

### Function expressions: only the variable declaration is hoisted

```javascript
console.log(add); // undefined (var) or ReferenceError (let/const)
console.log(add(2, 3)); // TypeError: add is not a function

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

With `var`, the variable itself is hoisted, but **the function assignment only happens at runtime**. Calling it before the assignment tries to execute `undefined()` — which throws an error.

`let` and `const` are stricter. They are hoisted, but due to the **TDZ (Temporal Dead Zone)**, accessing them before their declaration immediately throws a `ReferenceError`.

```javascript
console.log(add); // ReferenceError: Cannot access 'add' before initialization

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

### Quick comparison

| | Function Declaration | Function Expression (`var`) | Function Expression (`let`/`const`) |
|--|--|--|--|
| Hoisting | Full (name + body) | Variable only (value is `undefined`) | Variable only (TDZ) |
| Call before declaration | ✅ Works | ❌ TypeError | ❌ ReferenceError |
| Name required | Yes | No | No |

After seeing that table, you might think "I'll just always use function declarations." I had the same thought — but then arrow functions were quietly waiting.

## Where Do Arrow Functions Fit?

Arrow functions (`=>`) are **a type of function expression**. Their hoisting behavior is identical.

```javascript
// Writing this
const add = (a, b) => a + b;

// behaves the same as this
const add = function (a, b) {
  return a + b;
};
```

But arrow functions have important differences from regular functions.

**`this` binding is different.** Arrow functions don't have their own `this` — they inherit `this` from the surrounding scope where they're defined.

```javascript
const counter = {
  count: 0,
  // Regular function: this refers to the counter object
  increment: function () {
    this.count++;
  },
  // Arrow function: this refers to the outer scope (global or undefined)
  incrementArrow: () => {
    this.count++; // May not work as intended
  },
};
```

When you need `this` in object methods or event handlers, use a regular function expression.

**No `arguments` object.** Arrow functions don't have an `arguments` object. For variadic functions, use rest parameters (`...args`).

```javascript
// Regular function: arguments available
function sum() {
  return Array.from(arguments).reduce((acc, val) => acc + val, 0);
}

// Arrow function: use rest parameters
const sum = (...args) => args.reduce((acc, val) => acc + val, 0);
```

## Practical Usage

The theory makes sense, but it can be hard to know which to reach for in practice. Here are a few guidelines.

### Use arrow functions for callbacks and short functions

Arrow functions feel natural as callbacks for array methods like `map`, `filter`, and `forEach`. They keep code concise and readable.

```javascript
const numbers = [1, 2, 3, 4, 5];

// Arrow functions are much more concise
const doubled = numbers.map((n) => n * 2);
const evens = numbers.filter((n) => n % 2 === 0);
```

### Module-level functions: pick a style, but stay consistent

Modern frontend code often uses `const` with function expressions, especially for React components and utility functions.

```javascript
// Common style in many projects
const formatDate = (date) => {
  return new Intl.DateTimeFormat("ko-KR").format(date);
};

export default function UserCard({ name }) {
  return <div>{name}</div>;
}
```

Neither style is inherently better — **what matters is following your team's convention and being consistent**.

### Avoid relying on hoisting

Some developers intentionally call function declarations before defining them, knowing it works due to hoisting. But this can confuse anyone reading the code later.

```javascript
// ❌ Works, but the reader has to scroll down to find the declaration
initApp();

function initApp() {
  // ...
}

// ✅ Intent is clearer when you declare first, then call
function initApp() {
  // ...
}

initApp();
```

Of course, placing initialization code at the top and helper functions below is also a valid style. What matters is that everyone on the team understands and agrees on the approach.

## Wrap-Up

- **Function declaration**: fully hoisted → can be called before declaration. Name required.
- **Function expression**: variable-only hoisting → cannot be called before declaration. Name optional.
- **Arrow function**: a type of function expression. No `this` or `arguments` of its own.
- **Practical tip**: code that relies on hoisting is harder to read — prefer declaring before using.

These are functions you use every day, but knowing how they work makes debugging significantly faster. Next time you hit `TypeError: xxx is not a function`, check hoisting first.

---

{{< faq >}}

## Other Posts in This Series

- [`Promise.all` vs `Promise.allSettled`: The Difference That Matters]({{< relref "/posts/javascript-promise-methods" >}})

