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,

let variable = 123;

variable = null;
console.log(variable);

variable = 0;
console.log(variable);

variable = "John Doe";
console.log(variable);
null
0
John 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.

let variable;
console.log(variable);

variable = 123;
console.log(variable);
undefined
123

The const variables

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

const variable = 123;

variable = 100;
console.log(variable);
/Users/. . ./index.js:3
variable = 100;
         ^

TypeError: Assignment to constant variable.
    at Object.<anonymous> (/Users/. . ./index.js:3:10)
    . . .

Node.js v21.6.0

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

const variable;

variable = 100;
console.log(variable);
/Users/. . ./index.js:1
const variable;
      ^^^^^^^^

SyntaxError: Missing initializer in const declaration
    at internalCompileFunction (node:internal/vm:77:18)
    . . .

Node.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:

const obj = {
  name: "",
};

obj = {
  name: "John Doe",
};

console.log(obj);
/Users/. . ./index.js:5
obj = {
    ^

TypeError: Assignment to constant variable.
    at Object.<anonymous> (/Users/. . ./index.js:5:5)
    . . .

Node.js v21.6.0

But you can change the properties inside the object.

const obj = {
  name: "",
};

obj.name = "John Doe";

console.log(obj);
{ 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.

var variable = 123;

variable = 100;
console.log(variable);
100

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,

if (true) {
  let local = 100;
  console.log(local); // -> 100
}

console.log(local); // -> Error
100
/Users/. . ./index.js:6
console.log(local);
            ^

ReferenceError: local is not defined
    at Object.<anonymous> (/Users/. . ./index.js:6:13)
    . . .

Node.js v21.6.0

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

if (true) {
  const parent = 100; // This variable is defined in the parent

  function child() {
    console.log(parent); // But it is accessible in the child block
  }

  child();
}
100

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

if (true) {
  function child() {
    const local = 100; // This variable is defined in the child block
  }

  console.log(local); // It cannot be accessed in the parent block
}
/Users/. . ./index.js:6
  console.log(local); // It cannot be accessed in the parent block
              ^

ReferenceError: local is not defined
    at Object.<anonymous> (/Users/. . ./index.js:6:15)
    . . .

Node.js v21.6.0

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

let global = 100; // This is a global variable

console.log(global); // It is accessible here

if (true) {
  console.log(global); // Here

  function child() {
    console.log(global); // And here
  }

  child();
}
100
100
100

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

if (true) {
  function child() {
    var local = 100; // This variable is defined in a function block
    console.log(local); // It can be accessed from within the function
  }

  child();

  console.log(local); // But not here
}
100
/Users/. . ./index.js:9
  console.log(local); // But not here
              ^

ReferenceError: local is not defined
    at Object.<anonymous> (/Users/. . ./index.js:9:15)
    . . .

Node.js v21.6.0

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

if (true) {
  if (true) {
    var global = 100; // This variable is defined in an if block, but it is actually a global variable
    console.log(global); // So it is accessible here
  }

  console.log(global); // Here
}

console.log(global); // And here
100
100
100

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:

function parent() {
  let num = 123;

  return function child() {
    console.log(num);
  };
}

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().

function parent() {
  let num = 123;

  return function child() {
    console.log(num);
  };
}

let 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:

function parent() {
  let variable = 123;

  return function child() {
    console.log(variable);
  };
}

let x = parent();

x(); // <- This is not possible in many other programming languages
123