How scope and variables work in JavaScript

Brief overview on the different variable declaration keywords and how they affect scope

Published Aug. 11, 2022

What is scope?

Scope in JavaScript is similar to an actual scope in real life. Whenever you look through a scope, you can only see a certain part of what's around you. Similarly, scope in JavaScript simply refers to the current context of the code; it determines where variables can be accessed. The two main types of scope are global and local. A locally-scoped variable can either be function scope or block scope, but that'll be covered later. For now, let's look at global and local scope.

Global scope


// script.js
var msg = 'hello!';

function sayHi() {
}
        

...here, the variable msg is declared at the top of the script, outside of any functions. Because of this, the variable is globally-scoped. When a variable is in the global scope, it can be accessed from anywhere, including inside of functions...


// script.js
var msg = 'hello!';

function sayHi() {
    return msg;
}

console.log(sayHi()); // hello!
        

...as you can see, the function returns the value without any issues. This is where global scope differentiates from local scope.

Local scope

Unlike global scope, local scope works a little differently. Using the same example, let's move the variable inside the function:


// script.js
function sayHi() {
    var msg = 'hello!';
    return msg;
}

console.log(sayHi()); // hello!
        

...everything works the same, but now the msg variable is in the local scope. This means the variable can only be accessed within that function. If we try accessing the variable outside the sayHi() function, it'll throw an error...


// script.js
function sayHi() {
    var msg = 'hello!';
}

console.log(msg); // UncaughtReferenceError: msg is not defined
        

...since this produces an error, you may be inclined to believe this is bad, so you might decide to declare everything globally. Well, believe it or not, you WANT these type of errors.

Declaring every single variable globally isn't the best idea. For one, it uses up more resources, which can throttle the performance of bigger applications. Additionally, it doesn't make a lot of sense; if you need to use a variable inside a function, declare it inside that function — why would you declare it outside?

Function scope and block scope

Now that we covered global scope and local scope, we can talk about function scope and block scope, which are two types of local scope. To put it simply, a variable is in function scope when it's declared inside a function:


// script.js
function sayHi() {
    var msg = 'hello!';
}
        

...simple enough, right? On the other hand, if you declare a variable in something like a conditional statement or loop, then it's considered to be in block scope...


// script.js
if (true) {
    var msg = 'hello!';
}
        

...since both of these examples are locally-scoped, both should throw an error if msg is accessed from the outside, right? Let's find out...


// script.js
function sayHi() {
    var msg = 'hello!';
}
console.log(msg); // UncaughtReferenceError: msg is not defined

if (true) {
    var msg = 'hello!';
}
console.log(msg); // hello!
        

...wait a second, how come the conditional statement doesn't throw an error, despite msg being locally-scoped? The reason for this is hoisting. Hoisting is a whole different topic that I'll cover in a separate article; for now, you just need to know that we don't want this type of behavior. We want block scope variables to behave the same way as function scope variables, being accessible only from within the curly braces. The solution to this? The let and const keywords.

let keyword

Let's try running the conditional statement from earlier again, this time using the let keyword instead of var:


// script.js
if (true) {
    let msg = 'hello!';
}
console.log(msg); // UncaughtReferenceError: msg is not defined
        

...awesome! Now it behaves like a true local scope variable. let is essentially the replacement for var. There's no reason to declare variables with the var keyword anymore — it can cause a lot of unwanted bugs and it's a syntax that's long outdated. Avoid using var and opt for let or const instead, which we're about to cover.

const keyword

Along with the addition of the let keyword, const was also introduced to JavaScript. const is a way to assign an identifier that won't be changed, a "constant". Whereas variables declared with the let keyword can be reassigned, variables declared with const cannot. Here's an example:


let age = 12;
age = 14;
console.log(age); // 14

const ANIMAL = 'dog';
ANIMAL = 'cat'; // UncaughtTypeError: Assignment to constant variable
        

...age is declared with the let keyword, therefore it can be reassigned without a problem. If we try to reassign ANIMAL, it'll throw an error. Typically, it's best practice to declare const variables using all-uppercase letters.

One final thing to note is that unlike the var keyword (which you shouldn't use), neither let or const can be re-declared:


var msg = 'hello';
var msg = 'bye';
console.log(msg);
// bye

let age = 12;
let age = 14;
// UncaughtSyntaxError: Identifier 'age' has already been declared

const ANIMAL = 'dog';
const ANIMAL = 'cat';
// UncaughtSyntaxError: Identifier 'ANIMAL' has already been declared
        

...to summarize, use let for identifiers that might change, and const for identifiers that'll remain constant. Avoid using var at all costs!