A Comprehensive Guide to JavaScript Variables and Scope

How to define a variable

There are three different ways to define a variable in JavaScript, using the keywords let, const, and var.

The let variables

let defines a mutable variable, meaning you can change the value after it has been declared. For example,

javascript
1let variable = 123;
2
3variable = null;
4console.log(variable);
5
6variable = 0;
7console.log(variable);
8
9variable = "John Doe";
10console.log(variable);
text
1null
20
3John Doe

Using let, you are allowed to declare the variable first, and then assign a value later. Before the value is assigned, the variable will be automatically assigned undefined.

javascript
1let variable;
2console.log(variable);
3
4variable = 123;
5console.log(variable);
text
1undefined
2123

The const variables

const, on the other hand, defines an immutable constant. If you try to change its value, an error will be returned.

javascript
1const variable = 123;
2
3variable = 100;
4console.log(variable);
text
1/Users/. . ./index.js:3
2variable = 100;
3         ^
4
5TypeError: Assignment to constant variable.
6    at Object.<anonymous> (/Users/. . ./index.js:3:10)
7    . . .
8
9Node.js v21.6.0

With const, you cannot declare the variable first and then assign the value later.

javascript
1const variable;
2
3variable = 100;
4console.log(variable);
text
1/Users/. . ./index.js:1
2const variable;
3      ^^^^^^^^
4
5SyntaxError: Missing initializer in const declaration
6    at internalCompileFunction (node:internal/vm:77:18)
7    . . .
8
9Node.js v21.6.0

However, object is a special case. After defining a const with an object, you are not allowed to assign it to a different object:

javascript
1const obj = {
2  name: "",
3};
4
5obj = {
6  name: "John Doe",
7};
8
9console.log(obj);
text
1/Users/. . ./index.js:5
2obj = {
3    ^
4
5TypeError: Assignment to constant variable.
6    at Object.<anonymous> (/Users/. . ./index.js:5:5)
7    . . .
8
9Node.js v21.6.0

But you can change the properties inside the object.

javascript
1const obj = {
2  name: "",
3};
4
5obj.name = "John Doe";
6
7console.log(obj);
text
1{ name: 'John Doe' }

The var variables

The var keyword is the legacy method of defining a variable. In terms of mutability, it behaves just like let. You can change its value after being declared.

javascript
1var variable = 123;
2
3variable = 100;
4console.log(variable);
text
1100

The feature that makes var unique has to do with its scope. var cannot be block-scoped. It can only be either function-scoped, or global-scoped.

To understand what this means, we have to first discuss what is a scope.

Variable scope

The scope refers to the section of the code where a variable is accessible.

A variable defined inside a block (if statement, for loops, functions...) is only accessible from within that block. It is called a local variable. For example,

javascript
1if (true) {
2  let local = 100;
3  console.log(local); // -> 100
4}
5
6console.log(local); // -> Error
text
1100
2/Users/. . ./index.js:6
3console.log(local);
4            ^
5
6ReferenceError: local is not defined
7    at Object.<anonymous> (/Users/. . ./index.js:6:13)
8    . . .
9
10Node.js v21.6.0

A variable defined in the parent block is also accessible in the child block.

javascript
1if (true) {
2  const parent = 100; // This variable is defined in the parent
3
4  function child() {
5    console.log(parent); // But it is accessible in the child block
6  }
7
8  child();
9}
text
1100

However, a variable defined in the child block will not be accessible in the parent block.

javascript
1if (true) {
2  function child() {
3    const local = 100; // This variable is defined in the child block
4  }
5
6  console.log(local); // It cannot be accessed in the parent block
7}
text
1/Users/. . ./index.js:6
2  console.log(local); // It cannot be accessed in the parent block
3              ^
4
5ReferenceError: local is not defined
6    at Object.<anonymous> (/Users/. . ./index.js:6:15)
7    . . .
8
9Node.js v21.6.0

A variable defined outside of any blocks is a global variable, and it will be accessible anywhere in the file.

javascript
1let global = 100; // This is a global variable
2
3console.log(global); // It is accessible here
4
5if (true) {
6  console.log(global); // Here
7
8  function child() {
9    console.log(global); // And here
10  }
11
12  child();
13}
text
1100
2100
3100

However, variables defined with var works differently. A var variable defined in a function would be local to that function.

javascript
1if (true) {
2  function child() {
3    var local = 100; // This variable is defined in a function block
4    console.log(local); // It can be accessed from within the function
5  }
6
7  child();
8
9  console.log(local); // But not here
10}
text
1100
2/Users/. . ./index.js:9
3  console.log(local); // But not here
4              ^
5
6ReferenceError: local is not defined
7    at Object.<anonymous> (/Users/. . ./index.js:9:15)
8    . . .
9
10Node.js v21.6.0

However, a var variable defined in any other blocks will be a global variable and accessible anywhere.

javascript
1if (true) {
2  if (true) {
3    var global = 100; // This variable is defined in an if block, but it is actually a global variable
4    console.log(global); // So it is accessible here
5  }
6
7  console.log(global); // Here
8}
9
10console.log(global); // And here
text
1100
2100
3100

var is the legacy method of declaring variables, and you should not use it in your code. We are only covering it here because it is used in some of the older software. But today, the block-level variables like let and const have become the industry standard.

Nested functions and closure

Think about this example:

javascript
1function parent() {
2  let num = 123;
3
4  return function child() {
5    console.log(num);
6  };
7}

Inside the parent() function, we defined a variable num and a child() function. The child() function will be returned as the parent() function's output.

The child() will have access to all the variables defined in the parent().

javascript
1function parent() {
2  let num = 123;
3
4  return function child() {
5    console.log(num);
6  };
7}
8
9let x = parent();

Now, this is where things get interesting. Here we called the function parent(), and its output, the child() function, will be assigned to x. At this point, the parent() has reached the end of its life cycle. The return statement has been executed, and the function has been terminated.

So the question is, would the child() function still have access to the variable num? After parent() has been terminated?

For many other programming languages, the answer would be no. The variables inside a function's scope only live as long as the function itself. After the function has been terminated, the variables will be cleared.

But for JavaScript, there is something called closure. It is basically an environment that allows the child functions to have access to the variables of the parent function, even after the parent is terminated.

The closure allows us to do this:

javascript
1function parent() {
2  let variable = 123;
3
4  return function child() {
5    console.log(variable);
6  };
7}
8
9let x = parent();
10
11x(); // <- This is not possible in many other programming languages
text
1123