Getting Comfortable with JavaScript Scope, Function Expressions, and IIFEs

Sangy KSangy K
10 min read

As your Javascript projects grow, it's not just what you write โ€” it's where you write it that starts to matter.

Why does one variable work in one place but crash in another?

Why does a function behave differently inside a loop?

Welcome to the world of scope, function expressions, and IIFEs โ€” some of the most misunderstood, yet powerful, features in Javascript. ๐Ÿง ๐Ÿ’ฅ

In this guide, we'll learn about scope (where variables live), explore how function expressions give your code flexibility, and see how IIFEs let you run private, one-time logic.

Let's get you comfortable โ€” and confident โ€” with this essential foundation. ๐Ÿ’ช

๐Ÿ” Scope Basics: Where Variables Live in Javascript

In Javascript, scope refers to the area of your code where a variable can be accessed. You can think of scope like rooms in a house. Some items are available in every room (global scope), while others are only found in specific rooms (local scope).

๐Ÿ  Global Scope

A variable declared outside of any function or block lives in the global scope. This means it's accessible from anywhere in your script (or even the browser console if you're running in the browser).

let globalGreeting = "Hello from the outside!";

function greet() {
    console.log(globalGreeting); // โœ… Accessible here
}

greet();
console.log(globalGreeting); // โœ… Accessible here too

๐Ÿ›๏ธ Local Scope

Variables declared inside a function (using let, const, or var) are only accessible within that function. These are local variables.

function secret() {
    let secretMessage = "This stays in the function!";
    console.log(secretMessage); // โœ… Works
}

secret();
console.log(secretMessage); // โŒ Error: secretMessage is not defined

โœ… Tip: Prefer using local variables unless you really need a value to be global. It keeps your code safer and easier to maintain.

๐Ÿ“ฆ Block Scope with let and const

Unlike var, which is function-scoped, both let and const are block-scoped. That means they live only inside the nearest set of curly braces {}.

if (true) {
    let message = "Hello from inside!";
    console.log(message); // โœ… Works
}

console.log(message); // โŒ Error: message is not defined

These scoping rules are essential to understanding how data flows through your program. They help prevent bugs, keep your code modular, and are foundational for topics like closures and callbacks โ€” which we'll be diving into soon. ๐Ÿš€

โš”๏ธ Variable Shadowing: When Names Collide

Now let's talk about variable shadowing, which occurs when a variable in a local scope has the same name as a variable in an outer scope (like the global one) โ€” the local one shadows the outer one inside that scope.

โ˜๏ธ Shadowing in Action

let language = "Javascript"; // global variable

function favoriteLanguage() {
    let language = "Python"; // local variable with the same name
    console.log(language); // "Python" โ€” local shadows global
}

favoriteLanguage();
console.log(language); // "Javascript" โ€” global remains unchanged

Inside favoriteLanguage, the local variable language shadows the global one. The outer language still exists โ€” it's just hidden from view inside the function.

โš ๏ธ Common Gotchas with Scope

  • Forgetting Local vs. Global Boundaries:

function foo() {
    x = 10; // โ— Implicit global variable! (if 'use strict' is not enabled)
}

foo();
console.log(x); // 10 โ€” accidentally global

๐Ÿ’ก Always declare your variables with let or const to avoid these surprises.

  • Expecting Access Outside Local Scope:

function calculate() {
    let result = 42;
}

console.log(result); // โŒ Error: result is not defined

Function Expressions: Functions as Values ๐ŸŽญ

By now, we've seen function declarations โ€” the classic function greet() { ... }. But Javascript gives us more flexibility through something called function expressions.

These are especially powerful because they let us treat functions like any other value: store them in variables, pass them around, return them from other functions, and more.

Let's break this down step by step. ๐Ÿงฉ

๐Ÿง  What is a Function Expression?

A function expression is when we create a function and assign it to a variable.

const greet = function() {
    console.log("Hi there!");
};

greet(); // "Hi there!"

Here:

  • We've defined a function without a name.

  • That function is stored in a variable called greet.

  • We then call it using greet().

Yes โ€” in Javascript, functions are called as first-class citizens. They can be assigned to variables just like strings or numbers.

โš ๏ธ Key Differences: Function Declaration vs. Function Expression

FeatureFunction DeclarationFunction Expression
Syntaxfunction greet() {}const greet = function() {}
Hoistingโœ… Yes (can be called before definition)โŒ No (must be defined first)
Can be anonymous?โŒ Needs a nameโœ… Can be anonymous
Scope behaviorHoisted to the top of the scopeExists only after assignment

๐Ÿ’ Functions without a name are called as Anonymous functions. Function expressions can be anonymous (no name) or named

๐Ÿคฏ Hoisting in Action

Function declarations are hoisted, meaning Javascript moves them to the top of their scope before running your code. But function expressions are not

sayHi(); // Works fine!

function sayHi() {
    console.log("Hello!");
}

But this will fail:

sayHi(); // โŒ Error: sayHi is not a function

const sayHi = function() {
    console.log("Hello!");
};

Explanation: Javascript sees the const sayHi = ... line as a variable declaration during hoisting, but not the function assignment โ€” so when you try to call it early, sayHi is still undefined.

When to Use Function Expressions

  • When you want to pass a function as data (e.g., callbacks).

  • When you only need it temporarily and don't want to clutter the global scope.

โšก IIFE: The Fire-and-Forget Function in JavaScript

We've seen functions that we define and then call later.

But what if you want a function that runs immediately, just once, right when it's defined?

Enter the IIFE โ€” short for Immediately Invoked Function Expression (yep, it's a mouthful ๐Ÿ˜…). But don't worry, it's easier than it sounds.

๐Ÿง  What is an IIFE?

An IIFE is a function expression that runs as soon as it's created.

Here's what it looks like:

(function () {
    console.log("This runs right away!");
})();

๐Ÿš€ Output:

This runs right away!

Let's break it down:

  • (function() { ... }) โ†’ This is a function expression wrapped in parentheses. The parentheses make sure Javascript treats it as an expression, not a declaration.

  • () โ†’ This pair of parentheses invokes the function immediately.

So you're defining it and calling it at the same time.

๐Ÿค” Why Use an IIFE?

Now the real question โ€” why write a function that runs right away?

Here are two powerful reasons:

  • Avoid Polluting the Global Scope ๐Ÿ›ก

In Javascript, everything declared outside a function lives in the global scope โ€” meaning it's accessible from anywhere, which can quickly become a mess ๐Ÿ˜ฌ.

An IIFE gives you a private scope.

(function () {
    const secret = "shhh... ๐Ÿ”";
    console.log("Inside IIFE:", secret);
})();

console.log(secret); // โŒ ReferenceError: secret is not defined

Boom ๐Ÿ’ฅ โ€” secret is hidden. It only exists inside the IIFE, and your global scope stays clean.

This was especially useful before block-scoped variables (let/const) were introduced in ES6.

  • Run Setup Code Just Once ๐Ÿ”ง

IIFEs are perfect for initialisation tasks โ€” the kind of stuff you do once at the start of your program.

(function () {
    const config = {
        theme: "dark",
        language: "en",
    };
    console.log("Config loaded โœ…");
})();

This is like loading config values, registering startup logic, or setting defaults.

No need to store the function for reuse. Just run it and move on.

๐Ÿ’ก Bonus Use Case: Simulated Private Variables (Pre-ES6)

Before we had modules and let/const, IIFEs were the go-to tool for simulating private data:

const counter = (function () {
    let count = 0;

    return {
        increment: function () {
                        count++;
                        return count;
                    },

        getCount: function () {
                        return count;
                    },
    };
})();

console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount()); // 2

Here, count is not accessible outside the function โ€” it's "private"! Only increment and getCount can access it. ๐Ÿ’ผ

๐Ÿ“Œ Syntax Variants

Here are a few ways to write an IIFE (all valid):

(function () { ... })(); // classic

(() => { ... })(); // arrow function style

!(function () { ... })(); // works too (using ! to force expression context)

The outer parentheses are required โ€” they force Javascript to treat the function as an expression, not a declaration.

โš ๏ธ Gotchas to Avoid

  • Don't forget the () at the end โ€” without it, the function won't execute.

  • Avoid making your entire codebase one huge IIFE. It defeats the purpose of modular design.

โœ… Best Practices for Using IIFEs

  • Use for initialisation, not ongoing logic.

  • Keep them small and focused โ€” think "setup and forget".

  • Combine them with function expressions, closures, or even modules for extra power.

๐Ÿน Arrow Functions (Intro)

A cleaner, modern way to write functions โ€” with a few quirks!

In ES6 (aka ECMAScript 2015), Javascript introduced arrow functions โ€” a new way to define functions using the => syntax (called the "fat arrow").

They're handy for short, simple functions, and they make your code look cleaner and easier to read (less boilerplate, more focus).

๐Ÿ”ง Basic Syntax

Here's how you write a regular function:

function greet() {
    console.log("Hi!");
}

And here's the same function using an arrow function:

const greet = () => {
    console.log("Hi!");
};

โœ… Shorter

โœ… No function keyword

โœ… Perfect for quick tasks

๐Ÿ˜Ž Even Shorter: Implicit Return

If your function just returns a value, you can make it a one-liner โ€” no curly braces {} and no return keyword needed:

const add = (a, b) => a + b;

This is the same as:

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

๐Ÿง  It's called "implicit return" โ€” because Javascript automatically returns the result of the expression.

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

๐Ÿง’ One Parameter? Drop the Parentheses

If you only have one parameter, you can even drop the parentheses:

const greet = name => console.log(`Hi, ${name}!`);

greet("Luna"); // Hi, Luna!

But if you have zero or multiple parameters, the parentheses are required:

const sayHi = () => console.log("Hi!");
const fullName = (first, last) => ${first} ${last};

โ— Gotcha: Arrow Functions Don't Have this

Arrow functions do not have their own this. They inherit this from the surrounding (lexical) scope.

That's good and bad โ€” depending on your use case.

We will explore more on the scope, lexical environment, this keyword, etc., in a later article.

๐Ÿ“ Good: Useful in callbacks

const user = {
    name: "Alex",

    greet: function() {
                setTimeout(() => {
                    console.log(`Hi, Iโ€™m ${this.name}`); // this refers to user
                }, 1000);
    }
};

user.greet(); // "Hi, Iโ€™m Alex"

Here, the arrow function inside setTimeout doesn't have its own this, so it uses this from the surrounding function โ€” perfect!

โš ๏ธ Bad: Not for object methods

const user = {
    name: "Jane",
    greet: () => {
        console.log(`Hi, Iโ€™m ${this.name}`); // โŒ this is NOT user
    }
};

user.greet(); // Hi, Iโ€™m undefined

Yikes. Since arrow functions don't bind their own this, it ends up pointing to something else (likely the global object or undefined in strict mode).

๐Ÿ›‘ Avoid using arrow functions for object methods.

๐Ÿ’ก Best Use Cases for Arrow Functions

โœ… When you need a quick, one-off function

โœ… As callbacks for map, filter, reduce, forEach, etc.

โœ… Inside promises or async functions

By learning these essentials โ€” scope, expressions and IIFEs, you've unlocked a new level of control over your Javascript code.

From keeping variables private with block scope to firing off immediate functions with IIFEs, you now write smarter, safer code.

Next up โ€” let's dive into the world of objects and arrays and see the real magic they bring to Javascript! ๐Ÿง™โ€โ™‚๏ธโœจ๐Ÿ“ฆ๐Ÿ”ข

0
Subscribe to my newsletter

Read articles from Sangy K directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Sangy K
Sangy K

An Introvert Coder | Trying to be a Full-stack Developer | A Wannabe Content Creator