JavaScript Variables: Scope and Hoisting

Posted on March 05, 2020

Written by Preston Lamb

javascript

tldr;

Creating variables in JavaScript is one of the most basic parts of the language. You probably do it all the time without even thinking twice about it. But if you truly understand how variables are scoped it can save you some issues in the long run, especially when you’re getting started out. In this article, we’ll cover all the basics with variable scope. We’ll cover global, function, and block scoping, as well as the difference in scope when using var, let, and const.

Scope

So, what is scope? Scope is the concept of where you can use a variable or a function in a JavaScript application. Each time you create a variable or function, it has a scope that determines where it can be used. There are three types of scope: global, function, and block. We’ll talk about each these in depth.

Global Scope

The first scope we’ll talk about is global scope. If you declare a variable inside a JavaScript file but not inside a function, that variable will have global scope. Let’s look at an example below:

// index.js

var game = 'Super Mario Brothers';

function play() {
	console.log(`You are playing ${game}.`); // You are playing Super Mario Brothers
}

The game variable is available inside the play function because it has global scope. It will be available anywhere inside the index.js file because of where it was declared. Now, this can be desirable, but it is important to be careful when doing this. It’s one thing to have packages that you’re using and have imported be set at a global scope (because you wouldn’t want to reuse the variable identifier for those packages), but it’s a whole different thing to have a variable (such as game) that could reasonably be reused in different functions at global scope. My advice: be careful and intentional when you declare a variable at global scope.

Function Scope

At first glance, function scope looks very similar to global scope. The difference is pretty obvious though once you see it: variables will only be accessible in the function in which they’re declared or any nested functions. Let’s look at an example:

// index.js

function play() {
	var game = "Luigi's Mansion 3";

	function format() {
		return `You are playing ${game}`;
	}

	return format();
}

play(); // You are playing Luigi's Mansion 3

console.log(game); // Reference Error

In this case, the game variable is accessible inside the play and format functions, but not outside them. This is less error prone than global scope, because you can reuse common identifiers in multiple functions without the worry of overriding the value of a variable or something like that. My advice: when at all possible, select function scope over global scope.

Hoisting

Okay, before we talk about block scope, it’s important to talk about what hoisting is and what it means when using JavaScript. Let’s take a look at our last example:

// index.js

function play() {
	var game = "Luigi's Mansion 3";

	function format() {
		return `You are playing ${game}`;
	}

	return format();
}

We have our play function again, with a variable of game declared. Under the covers, the JavaScript really looks like this:

// index.js

function play() {
	var game = undefined;

	game = "Luigi's Mansion 3";

	function format() {
		return `You are playing ${game}`;
	}

	return format();
}

So JavaScript takes the var variable declarations and moves them to the top of the scope where they’re defined, and initializes them to undefined. Then, wherever you had initialized the value, the value is set for that variable. If we were to use console.log to log the value of the game variable before it’s intialized by us, the value that is logged would be undefined:

// index.js

function play() {
	console.log(game); // undefined

	var game = "Luigi's Mansion 3";

	function format() {
		return `You are playing ${game}`;
	}

	return format();
}

and that’s because the function really looks like this when the file is interpreted:

// index.js

function play() {
	var game = undefined;
	console.log(game); // undefined

	game = "Luigi's Mansion 3";

	function format() {
		return `You are playing ${game}`;
	}

	return format();
}

This isn’t too bad if you understand what you are doing, but you can get yourself in trouble if you try to use variables before you’ve initialized them. Because no error will be thrown, your function(s) will appear to run successfully but the value of the variable will be undefined instead of what you may be expecting.

Now that we understand what hoisting is, let’s talk about block scope.

Block Scope

Block scope is similar to function scope, except that any block of code defined by {} will have its own scoped variables. Variables that have block scoped are created using let or const. There are a couple big differences between block scoped and function scoped variables. The first is that if you try to use a block scoped variable in its block but before it’s declared, you will not get undefined, you’ll get a ReferenceError error. This is actually good in my opinion, because why would you want to use a variable before declaring it? The second part is that a variable that is declared in a for loop or if statement inside your function will not be accessible outside it. Let’s look at quick example first using function scope to show this:

// index.js

function play(numberOfPlayers) {
	if (numberOfPlayers === 1) {
		var game = 'Super Mario Odyssey';
	} else {
		var game = 'Super Smash Brothers';
	}

	function format() {
		return `You are playing ${game}`;
	}

	return format();
}

console.log(play(1)); // You are playing Super Mario Odyssey
console.log(play(2)); // You are playing Super Smash Brothers

Because the game variable is function scoped, it is still accessible inside the function even though it was declared and initialized inside an if block. Block scoped variables would not work if we tried the above. The result would be the following:

// index.js

function play(numberOfPlayers) {
	if (numberOfPlayers === 1) {
		let game = 'Super Mario Odyssey';
	} else {
		let game = 'Super Smash Brothers';
	}

	function format() {
		return `You are playing ${game}`;
	}

	return format();
}

console.log(play(1)); // ReferenceError game is not defined
console.log(play(2));

In this case, the format function can not use the game variable because it is not available in the play function or its nested functions as it is block scoped. To fix the issue, we would have to do something like:

// index.js

function play(numberOfPlayers) {
	let game;
	if (numberOfPlayers === 1) {
		game = 'Super Mario Odyssey';
	} else {
		game = 'Super Smash Brothers';
	}

	function format() {
		return `You are playing ${game}`;
	}

	return format();
}

console.log(play(1)); // You are playing Super Mario Odyssey
console.log(play(2)); // You are playing Super Smash Brothers

This time, the let variable is block scoped to the play function and thus is available in all nested blocks. Variables defined with const work the same way, except they need to be initialized at the same time they are declared. We won’t be covering that in this article, but you can find more on that by searching for articles that talk about the difference in let, var, and const. My advice: block scope variables whenever possible. It is more restricting than function scope, and should keep your code free of overwriting variables or accessing them outside their if statement or for loop.

Conclusion

Hopefully by now you understand the difference in the three types of scope and in the differences between var and let/const. It’s good to know these things as you write JavaScript code so that you understand where variables are scoped to and where they can be used. In my opinion, you should use block scoped variables wherever possible. I personally use const every time unless I know that I have to overwrite the variable and I have a good reason to overwrite it. But as far as scoping goes let and const will give you the same benefit.

Keep an eye out for more JavaScript Fundamentals posts. As I create my Thinkster.io course on JavaScript fundamentals, I’ll be writing a lot of it down here so I can make sure I have it written out in a clear way to explain it to others. Also, go check out my courses on Thinkster.io and keep an eye out for this one on JavaScript Fundamentals. The other two I have are on Deploying Apps to Netlify and Angular CLI Basics

Click here to subscribe to the newsletter and be the notified when a new blog post is available!