Understanding JavaScript scope: Global vs. function scope

Oladipupo Ishola
9 min readMar 11, 2023
Photo by Nangialai Stoman on Unsplash

Introduction

In JavaScript, the scope concept governs the visibility and accessibility of variables and functions within a program. There are two types of scope in JavaScript: global scope and function scope. In this article, we'll look at how the two differ and how they affect how you write JavaScript code.

Global scope

Variables declared outside of any function are regarded as having global scope. This means they can be seen and accessed from anywhere in your code, including any functions you define. This simply means that it is accessible to all scripts and functions on a web page.

Here’s an example:

// Global variable
var greeting = "Hello, world!";

function sayHello() {
// Access global variable
console.log(greeting);
}

sayHello(); // Output: "Hello, world!"

In this example, thegreetingvariable is defined in the global scope, outside of any function. ThesayHellofunction is able to access the greeting variable and log its value to the console.

While global scope can be useful, it can also cause issues if you are not careful. Because global variables can be accessed from anywhere in your code, it can be difficult to keep track of where they are used and modified. Furthermore, naming conflicts may occur if multiple global variables have the same name.

To avoid these problems, use function scope as much as possible instead of global variables.

Function scope

Variables declared within a function are known as "function scope variables." This means that they can only be seen and accessed within the context of the function that defines them. Variables declared within a JavaScript function are said to have a local scope because they are only accessible within the function.

Here’s an example:

function sayHello() {
// Local variable
var greeting = "Hello, world!";

console.log(greeting);
}

sayHello(); // Output: "Hello, world!"
console.log(greeting); // Throws a ReferenceError: greeting is not defined

Because the greeting variable in this example is defined within the sayHello function, it is only accessible within that function. If we try to access the greeting variable outside of the function, we will get a ReferenceError because it is not defined in that scope, as shown in the preceding example.

Using function scope can help you avoid naming conflicts and make your code more modular and easier to reason about. By keeping variables and functions within their own scopes, you can reduce the possibility of unintended side effects and make debugging and maintaining your code easier.

Block Scope (optional)

The block scope feature of JavaScript allows variables and functions to have a limited scope that is restricted to the block of code in which they are defined. A code block is a set of statements that are separated by curly braces. Variables and functions defined within a block of code with the let or const keywords have block scope, which means they are only accessible within that block of code.

Consider the following example:

function someFunction() {
var x = 10;
if (true) {
let y = 20;
const z = 30;
console.log(x); // Output: 10
console.log(y); // Output: 20
console.log(z); // Output: 30
}
console.log(x); // Output: 10
console.log(y); // Uncaught ReferenceError: y is not defined
console.log(z); // Uncaught ReferenceError: z is not defined
}

In this example, x has function scope, which means it can be accessed from anywhere within the function someFunction(). y and z, on the other hand, have block scope, which means they can only be accessed within the if block where they are defined.

Scope chains

Variable references in JavaScript are resolved using "scope chains," which are a series of nested scopes traversed by the interpreter to find the value of a variable. When the interpreter comes across a variable reference, it first checks the current scope, then the outer scope, and so on until it reaches the global scope. An error is thrown if the variable is not found in any of the scopes.

Consider the following example:

function outer() {
var x = 10;
function inner() {
var y = 20;
console.log(x); // Output: 10
console.log(y); // Output: 20
}
inner();
console.log(x); // Output: 10
console.log(y); // Uncaught ReferenceError: y is not defined
}

The function inner is nested inside the function outer in this case, resulting in the creation of a new scope. When inner is called, it first searches for the variable y within its own scope and then searches for x in outer's outer scope. When outer is called, it first searches within its own scope for x, and because it is defined there, it does not need to look any further.

Closure

The MDN documentation defined closure as the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function.

Closures, which rely on function scope, are a powerful concept in JavaScript. A closure is formed when a function is defined inside another function, and the inner function has access to the outer function's variables and parameters.

Here’s an example:

function counter() {
var count = 0;

return function() {
count++;
console.log(count);
}
}

var increment = counter();
increment(); // Output: 1
increment(); // Output: 2
increment(); // Output: 3

In this example, the counter function returns an inner function that increments a count variable and prints its value to the console. When we call the counter function, the inner function is returned, which we store in the increment variable. The increment function can then be called multiple times, and each time it will access and update the count variable from the parent function.

Closures are difficult to grasp at first, but they are extremely useful for creating reusable code and managing state in complex applications. It is nearly impossible to discuss scope without mentioning the distinctions between let, var, and const because they play an important role in variable declaration.

Differences between var, let, and const

var

var is the most traditional way to declare variables in JavaScript. It has function scope, which means that variables declared with the var keyword are accessible both within the function that defines them and any nested functions.

Here is an example:

function sayHello() {
var greeting = "Hello";

function greet(name) {
var message = greeting + ", " + name + "!";
console.log(message);
}

greet("Alice");
greet("Bob");
}

sayHello();

The greeting and message are declared with "var" in this example and are accessible from both the sayHello and nested greet functions.

One disadvantage of using var is that it has hoisting behavior, which means that variables declared with var are moved to the top of their respective scopes during runtime, regardless of where they were declared in the code. This can occasionally result in unexpected behavior.

let

The let is a newer variable declaration method in JavaScript that was introduced in ECMAScript 6. Variables declared with the let keyword are only accessible within the block in which they are defined, as well as any nested blocks.

function sayHello() {
let greeting = "Hello";

function greet(name) {
let message = `${greeting}, ${name}!`;
console.log(message);
}

greet("Alice");
greet("Bob");
}

sayHello();

In this example, greeting and message are declared with let and are only accessible within the block where they are defined, which in this case is the sayHello function.

The let variable declaration, unlike var, has no hoisting behavior. If you try to access a let variable before it's been declared, you'll get a reference error.

const

The const variable declaration is similar to the let variable declaration in that it also has block scope. 'const', on the other hand, is used to declare constants, which cannot be reassigned once defined.

function sayHello() {
const greeting = "Hello";

function greet(name) {
const message = `${greeting}, ${name}!`;
console.log(message);
}

greet("Alice");
greet("Bob");
}

sayHello();

The greeting and message variables in this example are declared with const and can only be accessed within the block in which they are defined. Furthermore, once greeting has been defined, it cannot be reassigned to a new value.

Like, let, const does not have hoisting behavior, and you will get a reference error if you try to access a `const` variable before it has been declared.

Common pitfalls and best practices for managing scope

When managing scope in JavaScript, there are some common pitfalls to be aware of and best practices to follow to avoid them. Here are some tips:

i. Accidental global variables

Accidentally creating global variables is one of the most common pitfalls when managing scope in JavaScript. This can happen if you forget to declare a variable inside a function with 'var', 'let', or 'const' and it ends up being declared in the global scope instead.

function foo() {
bar = "hello";
}

foo();
console.log(bar); // "hello"

In this example, 'bar' is declared as a global variable without the use of 'var', 'let', or 'const' within the 'foo' function. This can result in naming conflicts and unexpected behavior.

ii. Naming conflicts

Another common stumbling block is naming clashes between variables declared in different scopes. For example, if you declare a variable in a nested function with the same name as one declared in the outer function, the inner variable will shadow the outer variable.

function foo() {
let x = 1;

function bar() {
let x = 2;
console.log(x); // 2
}

bar();

console.log(x); // 1
}

foo();

The "x" variable declared within the "bar" function shadows the "x" variable declared within the "foo" function.

Best Practices for Managing Scope

Here are some best practices for managing scope in your JavaScript code:

  • Reduce your use of global variables and instead rely as much as possible on function scope. This will assist you in avoiding naming conflicts while also making your code more modular and easier to reason about.
  • Make it clear what each variable represents and how it is used by using descriptive variable names. This can prevent you from accidentally reusing a variable name and causing unintended consequences.
  • Use closures with caution to avoid memory leaks. Because inner functions have access to the outer function's variables and parameters, they can keep hold of references to objects that are no longer needed, preventing them from being garbage collected. To avoid this, make sure to remove any unnecessary references to objects after you've finished with them.
  • To help catch common scope-related errors, use strict mode. Strict mode is a JavaScript feature that makes a variable declaration and other language features more stringent. It can help detect errors that would otherwise go undetected, as well as make your code more robust and reliable.
  • Use tools such as linters and debuggers to catch scope-related errors and debug your code more easily. Most modern code editors and IDEs include built-in linting and debugging tools for JavaScript code, which can help you catch errors early and save time when debugging your code.

You can write more robust, modular, and maintainable JavaScript code by following these best practices and understanding the differences between global scope and function scope.

Conclusion

Congratulations! You've taken the first step toward becoming an expert in JavaScript. Understanding scope can be difficult at first, but with practice and persistence, you'll be able to master this critical concept in no time.

So, why is scope so important, you may wonder? To begin with, it enables you to write code that is easier to read, debug, and maintain. You can keep your code organized and reduce the likelihood of errors by using function scope whenever possible and avoiding global variables.

Not only that, but understanding scope opens up a whole new world of possibilities for writing reusable code. Closures allow you to create functions that are powerful, flexible, and efficient. Just be careful not to overuse them and avoid memory leaks!

But don't just take my word for it; start exploring the world of JavaScript scope for yourself. Experiment with different scenarios to see how scope affects your code. Also, don't be afraid to use the many tools available to help you debug and lint your JavaScript code.

So go forth and conquer the world of JavaScript scope! With some practice and tenacity, you'll be writing high-quality, efficient, and reusable code in no time.

What are your thoughts on the significance of understanding JavaScript scope? Do you agree that it's an important concept to understand in order to write high-quality JavaScript code? Do you have any suggestions or best practices to add to the list? Share your thoughts in the comments section below!

Thank you for reading.

--

--

Oladipupo Ishola

Tech Writer | Mentor & Code Instructor | Bootcamp Grad | Full Stack Developer | Frontend Developer | Backend Developer | MLH ’21 Alum