I remember when i first started learning JavaScript everything was so confusing, i felt completely overwhelmed.
Variables, Data Types, let, const, Scope, Hoisting, Temporal Dead Zone (TDZ)... ahh so many things.
It felt like everyone else understand it and i was just trying to memorize these things without actually understanding what was going on under the hood.
So in this blog i’m going to cover and explain slowly, clearly & practically everything the way i wish someone had explained it to me when i started learning JavaScript.
By the end of this blog you won’t just know the definitions but you will understand how JavaScript behaves actually.
So let’s start from the very beginning.
What are JavaScript Variables?
Let’s forget the technical definition for now and think of a variable like a box
So start with this Imagine you have a box
You put something inside it
Write a lable on it
Open it later to see what’s inside the box
Later replace what’s inside if needed
That box is exactly what a variable is
In JavaScript a variable is simply a named container that stores data so we can use it later.
for example:
let name = "Nausheen";
How I see it in my head:
But the question is: Why do we need variables?
The reason is simple because programs need memory
When you log into a website, it needs to remember:
Without variables, JavaScript wouldn’t be able to remember anything not even for a second.
Variables are the foundation of everthing
How I Declare Variables (var, let, const)
Here we go In JavaScript, we have three ways to create variables:
But the thing is they are not the same. And this is where beginners usually get confused.
So, let me break it down the way I personally understand it and where to use them.
1. var (old way)
var age = 20;
yup simple, this is how it works.
And yes, you can also change it:
age = 21;
But here’s what I’ve learned over time:
var has some strange behaviors that can create confusion in larger programs.
For example:
var city = "Delhi";
var city = "Mumbai"; // No error
It allows redeclaration in the same scope.
That might not seem dangerous now, but in a big project, this can silently overwrite values & cause bugs that are hard to track.
That’s why in morder JavaScript, we mostly avoid var
Not because it’s bad, but because there are safer alternatives.
2. let (Flexible and Modern)
let score = 50;
I use let when I know that value might change later.
score = 60; // Perfectly fine
But here’s something important:
let score = 70; // Error
You cannot redeclare a let variable in the same scope.
And honestly, I like this.
Because it prevents accidental mistakes.
3. const (Fixed and Safe)
const birthYear = 2004;
Ok so, when I write const I'm basically saying:
I don’t want this value to change
And JavaScript respects that.
birthYear = 2005; // Error
This is why I personally follow this rule:
This makes code safer and more predictable.
Difference Between var, let, and const

If you’re just starting out, here’s what I’d suggest you:
Use const most of the time
Use let when necessary
Forget var for now
What Are Data Types?
What’s Inside the Box?
Now let’s talk about what we actually store inside variables.
When I say "data type" I'm simply asking:
What kind of value is inside this variable?
If variables are boxes, data type describes what kind of thing is inside the box.
In JavaScript, there are many types. But as beginners, we focus on primitive data types.
Basically, there are two categories of data types:
Primitive Data Types
Non-Primitive Data Types
But in this blog, we will only focus on primitive data types.
The main primitive types in JavaScript are:
String
Number
Boolean
Null
Undefined
Let’s understand them properly
1. String - Text Data
Whenever I want to store some text data, I use a string.
let name = "Nausheen";
let message = "Hello World";
String are always written inside quotes.
Without quotes, JavaScript thinks it’s a variable name.
Strings are used for:
Name
Emails
Messages
Addresses
Basically, anything that’s text.
2. Number - Numeric Values
let age = 21;
let price = 99.99;
In JavaScript, there is only one number type.
It doesn’t seperate integers and decimals unlike many other programming languages
Whether it’s 21 or 99.99 , both are simply number.
Numbers are used for:
Age
Score
Prices
Calculations
3. Boolean - True or False
Booleans are very simple but very powerful.
They only have two values:
let isLoggedIn = true;
let hasPermission = false;
Booleans are mainly used in decisions.
For example:
Most conditions in programming rely on booleans.
4. null - Intentionally Empty
When I use null, I'm basically saying:
I’m intentionally keeping this empty for now.
let selectedUser = null;
This means:
I will assign a real value later.
5. undefined - Not Assigned Yet
If i declare a variable but don’t give it a value:
let score;
JavaScript automatically assigns it undefined.
That means:
The variable exist, but nothing is inside it yet.
The difference between null and undefined is subtle but important:
What is Scope?
When I first heard the word scope, it sounded technical and abstract. But one I simplified it in my mind everything become clearer.
So, Scope simply answers one question:
Where can I access this variable?
That’s it……
Scope determines the visibility & accessibility of variables in different parts of your prograrm.
Let’s take an example of “The Rooms in a House”
Imagine scope as rooms inside a house
Each { } block is like a separate room.
If I place something inside one room, I can only access it inside that room. I cannot randomly walk into another room & expect that thing to be there.
In JavaScript, a block looks like this:
{
let message = "Hello there";
console.log(message); // It works
}
console.log(message); // ReferenceError
Here, message was created inside the block (inside the "room").
So when we try to access it outside the block, JavaScript throws a ReferenceError.
Not because the value is undefined
But because it does not exist in that scope.
That’s an important distinction.
Block Scope (let & const)
Variables declared using let and const follow block scope.
A block is anything inside { }:
if statements
for loop
while loop
plain curly braces
function
if (true) {
let age = 22;
}
console.log(age); // ReferenceError
Here, age only exists inside that if block.
Once execution leaves that block, the variable is gone.
This behavior makes your code safer because:
It prevents accidental overwriting of variables
It avoids naming conflicts
It keeps variables limited to where they are needed
This is one of the main reason why JavaScript prefers let and const.
They give you controlled scope.
Function Scope
Before ES6, Javascript only had function scope.
That means variables declared with var are accessible anywhere inside the function they were declared in regardless of blocks.
function test() {
if (true) {
var name = "Nausheen";
}
console.log(name); // works
}
Even though name was declared inside the if block, it is accessible outside that block but still inside the function.
Why?
Because var doesn't follow block scope.
This difference is the root cause of many bugs in older JavaScript code.
Why var Is Problematic?
Let’s look at this code example:
if (true) {
var count = 10;
}
console.log(count); // Works (unexpected behavior for beginners)
Here, count leaks outside the block.
This can cause:
That’s why modern JavaScript discourages to use var unless absolutely necessary.
Global Scope
If we declare a variable outside of any block or function, it belongs to the global scope.
Let’s see a Example:
let globalMessage = "Hi"; // Global variable
function greet() {
console.log(globalMessage); // Accessible
}
Global variables can be accessed everywhere in the program.
But here’s the danger:
Too many global variables make your code messy & hard to maintain.
Think of global scope as the entire house, if you throw everything in the living room, it becomes chaos.
Good developers minimize global scope usage.

Function Inside Function
Until now, we talked about scope like rooms in a house.
Let’s now imagine something more interesting
what if a room had another room inside it?
Now let’s see what happens when functions are inside other functions…
Yes this is completely valid look at this code:
function outer() {
let outerMessage = "Hello from outer";
function inner() {
console.log("Hello from inner");
}
inner();
}
outer();
Here, outer is one room, inner is smaller room inside it.
Now let’s test something important.
function outer() {
let outerMessage = "Hello from outer";
function inner() {
console.log(outerMessage); // works
}
inner();
}
outer();
The inner function can access the outer function’s variable.
But, can outer access inner’s variable to, let test that also.
function outer() {
function inner() {
let secret = "Hidden"
}
inner();
console.log(secret); // Error
}
outer();
Oops outer cannot access inner’s variables.
So, remember access only flow upward means:
Child -> Parent
Never Parent -> Child
This idea is the foundation of lexical scope.
But before we go there……
First we need to understand something deeper.
How JavaScript actually runs you code
How JavaScript Actually Runs Your Code (Execution context)
Most beginners think JavaScript reads code line by line.
Reads line 1
Then line 2
Then line 3…
But that’s not exactly true.
JavaScript runs your code in two phases.
And this is where everything changes.
Phase 1: Memory Creation Phase
Before executing anything…
JavaScript first scans you entire code.
But keep this in mind:
Variables don’t get their real values yet.
var => initialized with undefined
let and const => created but not initialized
Functions => fully stored in memory
console.log(name); // undefined
var name = "Nausheen";
undefined why?
Because during memory phase:
var name = undefined;
Then during execution phase:
name = "Nausheen";
This behavior is called hoisting, but we’ll go deeper into that soon… yes we’re going to cover that too.
Phase 2: Code Execution Phase
Now JavaScript starts running the code line by line.
Values are assigned
Functions get executed
console.log() run
Memory Phase always comes first
Then comes the Execution Phase
Simple as that. Yes nothing scary
Call Stack
Every time a function runs… JavaScript creates a new execution context.
These execution contexts are stacked on top of each other.
Think of the call stack like a stack of plates
When you call a function:
Javascripit puts that function on top of the stack
When the function finishes:
JavaScript removes it from the top
That’s it
It always works Last In, First Out (LIFO).
The last function added is the first one removed
function one() {
two();
}
function two() {
console.log("Hello");
}
one();
Let’s see what happens step by step:
Global code starts running
one() is called -> added to stack
Inside one(), two() is called -> added on top
two() finishes -> removed
one() finishes -> removed
Back to global
So, flow looks llike this:
Global
one()
two()
Then it goes backward:
two() removed
one() removed
back to Global
This is how JavaScript manages execution
Hoisting - What It Actually is (And What Most People get Wrong)
Now that you understand execution context… Hoisting will make sense.
When I first learned hoisting, someone told me:
JavaScript moves variables to the top
And honestly?..
That explanation confused me more than it helped.
Because JavaScript doesn’t physically move your code
Nothing jumps to the top
Your code stays exactly where you wrote it.
So the question is what’s really happening?
To understand hoisting properly, we have to go back to something we already learned:
Execution context
Memory Phase
Execution Phase
Hoisting is simply a result of how JavaScript prepares memory before running code.
How var Hoisting Actually Works
Let’s see:
console.log(name); // undefined
var name = "Nausheen";
Output is undefined why not error?
Because during memory phase
JavaScript does this internally:
var name = undefined;
Then during execution phase:
name = "Nausheen";
So when console.log runs,
name exists but its value is undefined
So, remember this is important:
var is declared during compilation
It is also initialized to undefined during compilation
That’s why it’s accessible early.
Why var Can Be Dangerous
Now look at this:
function example() {
console.log(x); // undefined
if (false) {
var x = 10;
}
console.log(x); // undefined
}
example();
undefined
undefined
Even though the if block never ran.
Why?
Because var x was hoisted to the function scope.
This is the dangerous part.
var ignores block scope.
It can silently create undefined variables in unexpected places.
This is one of the biggest reasons modern JavaScript prefers let and const.
Function Hoisting
Function declarations behave differently it’s even more powerful.
They are not just declared
They are fully initialized during compilation
greet();
function greet() {
console.log("Hello!");
}
This works perfectly.
Because during compilation:
greet: [complete function stored in memory]
The entire function body is available before execution begins.
That’s why function declarations are called:
Fully hoisted
Function Expression vs Function Declaration
Now this is important.
sayHi();
var sayHi = function () {
console.log("Hi");
};
Now this throws:
TypeError: sayHi is not a function
Why?
Because only variable is hoisted
var sayHi = undefined;
Which causes the error.
The difference is
Now Let’s Talk About let and const
Here’s where things get misunderstood
Many people say:
let and const are not hoisted
That is wrong…
They are hoisted
But they behave differently
During compilation:
This creates something called… TDZ

Temporal Dead Zone (TDZ)
Another scary term TDZ. Ahh, now what’s that?
Don’t worry. I’ll keep it simple
This is one of the most misunderstood concepts.
The Temporal Dead Zone is:
The time between when a variable is hoisted
*And when it is initialized
Let’s see it:
console.log(age);
let age = 25;
This throws:
ReferenceError: Cannot access 'age' before initialization
Why?
Beause:
The TDZ starts:
At the beginning of the block
And ends:
At the line where the variable is declard
TDZ Is About Time, Not Position
Very important, TDZ is not about where the variable is written.
It’s about time.
function test() {
console.log(score);
let score = 100;
}
test();
From the moment the function starts running,
score exists.
But until let score = 100 executes,
it is in the TDZ.
That’s why accessing it throws an error.
Proof That let Is Actually Hoisted
Look at this:
let x = "outer";
function demo() {
console.log(x);
let x = "inner";
}
demo();
This throws ReferenceError.
If let x inside demo was NOT hoisted,
JavaScript would use the outer x and print "outer".
But it doesn’t.
Why?
Because the inner x is hoisted to the function scope.
It creates a TDZ.
That proves:
let is hoisted
It is not initialized
That difference change everything.
How const Behaves
const works exactly like let regarding hoisting and TDZ.
But with one extra rule:
You must initialize it immediately.
const name; // SyntaxError
Correct:
const name = "Nausheen";
It is hoisted.
It has TDZ.
But it must be initialized.
Common misconceptions you should always remember
“Hoisting moves code to the top”
No.
Code stays exactly where you wrote it
Memory is prepared before execution
“Only var is hoisted”
Wrong.
All declarations are hoisted:
var
let
const
function
class
The difference is initialization timing.
“TDZ means variable doesn’t exist”
Wrong.
The variable exists.
It just cannot be accessed yet.
Why TDZ Is Actually a Good Thing
With var, this is allowed:
var total = total + 5;
This creates weird undefined behavior.
With let or const, you immediately get an error.
TDZ protects you from accidental bugs.
It forces cleaner code.
It makes JavaScript safer.