Mert Özen Further With Every Line
Frontend JavaScript Basics — #03

JavaScript Variables: let and const, Block Scope and the TDZ

Mert Özen Nov 3, 2026 9 min 6 views
JavaScript Variables: let and const, Block Scope and the TDZ

We declare variables with let and const, and use examples to see reassignment versus mutation, block scope, naming rules, why var has been retired, and the TDZ.

Version note: The code in this post targets ES2026 and current browsers (Chrome, Edge, Firefox, Safari).

Predict First

We have a shopping cart. We declared it with const, added something to it, then tried to replace the whole cart:

const cart = ["apple"];
cart.push("pear");
console.log(cart);
cart = ["banana"];
console.log(cart);

What does this code print to the console? Two lines, one line, nothing at all? If there's an error, on which line? Make a guess before you keep reading. We'll come back to the answer once we've talked about what const actually locks.

A small practical note: the examples in this post reuse the same names again and again. If the console complains that something "has already been declared," reload the page and paste the example again.

A Variable: Giving a Value a Name

While a program runs, you want to store a value and use it again later: the user's name, the number of items in the cart, the score in a game. A variable is a name you give to a value. Think of it as sticking labels on boxes in a storeroom. The value is what's inside the box; the variable is the label on top. Read the label and you know which box to look in.

Modern JavaScript has two ways to declare a variable: let and const. Both arrived with ES2015. Let's start with let. Paste this into your browser's console and try it:

let score = 0;
console.log(score);
score = 10;
score = score + 5;
console.log(score);
let user;
console.log(user);

You'll see 0, 15 and undefined, in that order. The line let score = 0 does two jobs: it declares a variable named score and gives it its first value. On the following lines we assign new values without writing let in front; that's called reassignment. Read score = score + 5 from right to left: first 5 is added to the current value, then the result is written back into score.

The last two lines matter too. A let variable you declare without a value isn't empty; it gets the value undefined, which means "nothing has been assigned yet." We'll get to know undefined better in the next post, along with the data types.

const: Bind It Once and Leave It

With const, you give the variable a value when you declare it, and that name can never be bound to a different value again:

const birthYear = 1990;
console.log(birthYear);
birthYear = 1991;

The first line prints 1990, then you get TypeError: Assignment to constant variable. The engine is telling you "you can't assign a new value to this name." You can't write a const without a value either; the line const city; fails before it even runs, with SyntaxError: Missing initializer in const declaration. That makes sense: there's no point leaving something empty if you can never change it later.

So which one should you use, and when? The widely accepted rule today is simple: write const by default, and switch to let when the value genuinely needs to change. It also sends a message through your code. When you see let in a file, you read it as "careful, this value will change later"; when you see const, you can put it out of your mind.

Reassignment or Mutation?

Back to the opening question. The console first shows ["apple", "pear"], then TypeError: Assignment to constant variable. appears, and the last console.log never runs. So how did const let us add a pear to the cart?

Because const doesn't lock the value; it locks the name's binding. Back to the label example: with const you stick the label on the box and lock it in place. You can't peel the label off and stick it on another box, but you can open the lid and put something inside. cart.push("pear") puts something inside the box; that's called mutation. cart = ["banana"] tries to move the label to a completely different box; that's reassignment, and const doesn't allow it.

The same goes for objects:

const settings = { theme: "dark" };
settings.theme = "light";
console.log(settings.theme);

This code runs without complaint and prints "light". We'll cover arrays and objects in detail in upcoming posts; for now, keep this in mind: const means "this name will always point to the same thing," not "the inside of this thing will never change." We'll also see ways to truly freeze contents later on.

Block Scope: The Boundary of Curly Braces

Where in your code a variable can be seen is called its scope. let and const are block-scoped: a variable declared inside curly braces { } lives only inside those braces. Try this in the console:

const city = "Ankara";
{
  const city = "Izmir";
  const district = "Alsancak";
  console.log(city);
}
console.log(city);
console.log(district);

The output is Izmir, Ankara and ReferenceError: district is not defined. A block is like a room. The district you declared inside the room can't leave it; the moment you step out the door, that name is gone. The inner city doesn't break the outer city; it just hides it while you're inside the room. This is called shadowing. When the engine looks up a name, it checks the current block first, and if it isn't there, it moves out one block.

This rule isn't limited to standalone blocks. The curly braces of if statements and loops are blocks too, and they work the same way; we'll see them in upcoming posts. The benefit of block scope: if you declare a variable in the narrowest area where you need it, you won't collide with someone using the same name in another corner of the code.

Naming Rules and camelCase

Variable names must follow a few strict rules. You can use letters, digits, $ and _, but a name can't start with a digit. No spaces or hyphens either. JavaScript is case-sensitive: name and Name are two different variables. All of these are valid:

let userName = "mert";
let $price = 100;
let _temp = true;
let number2 = 7;
const MAX_ATTEMPTS = 3;

These, on the other hand, throw errors; try each one separately:

let 2number = 7;
let user-name = "mert";
let class = "A";

The first gives a SyntaxError because it starts with a digit, the second because it contains a hyphen, which the engine reads as a minus sign. The third runs into a reserved word. Words like let, const, class, if, for, return, function, new and typeof belong to the language itself; you can't use them as variable names.

Beyond the rules there's also convention. In JavaScript, variables are written in camelCase: the first word lowercase, the first letter of each following word uppercase. userName instead of username, totalAmount instead of total_amount. For settings that are set once at the start of the program and never change, uppercase with underscores is used, like MAX_ATTEMPTS.

Non-English letters are technically valid; let şehir works. Still, since they're awkward to type on other keyboards and can cause friction in a team, I'd suggest sticking to ASCII letters. Most important of all is choosing meaningful names: productCount instead of x, birthDate instead of d. The person reading this code a month from now will most likely be you.

var: Why We Don't Use It Anymore

In old code, old tutorials and plenty of answers online you'll see a third way: var. Before ES2015 it was the only option we had. We don't use it in new code today, but you need to know why, because you'll run into it. Try this in the console:

var message = "first";
var message = "second";
console.log(message);

{
  var leak = "I escaped the block";
}
console.log(leak);

Two problems show up at once. First, you declared the same name a second time without any warning. In a long file, that's the easiest way to overwrite someone else's variable without noticing. Had you done the same with let, you'd have gotten SyntaxError: Identifier 'message' has already been declared.

Second, var ignores block scope. The leak you declared inside curly braces can be read from outside without any trouble. The boundary of var is the function, not the block; this is called function scope, and we'll cover functions in detail in upcoming posts. On top of that, var variables declared at the top level become properties of the window object: after var color = "blue", window.color also gives "blue". let and const don't do that.

In short, var is like an old tenant who doesn't care much for the rules: spills out of their room, registers twice under the same name, and nobody says a word. When you see it in old code, understand what it does; in new code, use let and const.

A First Look at the TDZ

var has one more quirk, and this is exactly where the most interesting behavior of let and const shows up. Let's try to read a variable before declaring it:

console.log(oldOne);
var oldOne = "with var";

console.log(newOne);
let newOne = "with let";

The first console.log doesn't throw; it prints undefined. Before running the code, the engine scans for names declared with var and sets them up with the value undefined from the start. The assignment itself only happens when that line is reached. The second console.log, however, gives ReferenceError: Cannot access 'newOne' before initialization. Firefox words the message a little differently, but the error type is still ReferenceError.

The reason is the Temporal Dead Zone, TDZ for short. The engine also knows about let and const names from the start of the block, but it doesn't give them a value and won't let you touch them until the declaration line is reached. The stretch from the start of the block to the declaration line is the variable's "dead zone."

Think of a reserved seat in a theater. There's a card on the seat with your name on it, so the seat is yours. But until you arrive and sit down, nobody can put anything there, and anyone who asks "who's sitting here?" is told "they haven't arrived yet." var, on the other hand, leaves an empty cardboard box on the seat before you arrive; whoever looks doesn't get an error, but they see the wrong thing. It's clear which is safer. We'll cover every detail of hoisting and the TDZ in upcoming posts; for now, knowing the rule is enough: you can't use let and const before they're declared.

Common Pitfalls

Thinking const means "the value never changes." As we saw, const doesn't protect the contents of an array or object; it only stops the name from being bound to something else.

Being fooled by the console's leniency. For convenience, Chrome's console lets you declare the same name again with let across separate entries. The same two lines run from a file, or in a single go, throw a SyntaxError. Don't assume every bit of slack the console gives you also applies to real code.

Forgetting the declaration. If you write total = 5 without let or const in front, the browser quietly creates a global variable in normal mode. The strict mode we saw earlier turns this into a ReferenceError, which is a good reason to catch the mistake early.

Declaring the same name twice in the same block. Since this is a SyntaxError, as we saw earlier, not a single line of the file runs. One name clash can stop the whole script.

Writing let for everything. It works, but it sends the reader the wrong signal. Declare values that won't change with const, so that wherever you see let, you know you really need to pay attention.

A Small Experiment

★☆☆ Declare your name and birth year with const, and your age with let. Calculate the age with 2026 - birthYear and print all three with a single console.log. Then try changing the birth year and read the error you get.

★★☆ Predict the output before running it:

let x = 1;
{
  let x = 2;
  x = x + 10;
  console.log(x);
}
console.log(x);

★★★ The code below has more than one problem. Predict which error you'll see first when you paste it into the console, and explain why. Then rewrite it from scratch with let, const and proper names; make the quantity a value that genuinely needs to change.

var total = 0;
var price-1 = 40;
const quantity = 2;
quantity = 3;
total = price-1 * quantity;

Throughout this post we put numbers, text and arrays into variables, but we never talked about the values themselves. In the next post we'll look at JavaScript's data types: the seven primitive types, objects, typeof, and the typeof null trap that catches everyone at least once.