Skip to main content

Command Palette

Search for a command to run...

JavaScript Variables and Data Types: var, let, const Explained

Scope, hoisting, all 8 data types, and the gotchas that confuse beginners

Updated
•9 min read•View as Markdown
JavaScript Variables and Data Types: var, let, const Explained

Every program stores data: a username, a price, a list of items. In JavaScript, you store it in variables, and every value has a data type. This guide covers var, let, const and all the data types, scope, with simple examples, return values, and the gotchas that confuse beginners.

What is a variable?

A variable is a container with a label that stores a value. Think of a labeled box 📦.

let name = "Harshit";
  • name is the variable (the label on the box).

  • "Harshit" is the value (what is inside the box).

Why variables matter:

  • Write a value once, use it many times.

  • Values can change, which makes programs interactive.

  • They make logic possible, like if (age >= 18) need something to compare.

Declaring variables: var, let, const

Syntax: keyword + name + optional = value.

var variableName = value;
let variableName = value;
const variableName = value;

var (old way, avoid)

  • Can be redeclared and updated.

  • Function-scoped (ignores { } blocks).

var x = 10;
var x = 20;   // allowed (redeclared)
x = 30;       // allowed (updated)

let (for values that change)

  • Cannot be redeclared in the same scope.

  • Can be updated.

  • Block scoped.

let age = 25;
age = 30;          // allowed
// let age = 40;   // SyntaxError: 'age' has already been declared

const (for values that do not change)

  • Cannot be redeclared or reassigned.

  • Block scoped.

  • Must be given a value immediately.

const pi = 3.14;
// pi = 3.15;      // TypeError: assignment to constant variable
// const x;        // SyntaxError: missing initializer

const Blocks reassignment, not mutation

const Protects the variable, not the contents of an object or array.

const person = { name: "John" };
person.name = "Alex";          // allowed (changing inside)
// person = { name: "Sam" };   // TypeError (reassigning)

const crew = ["Alok", "Abhinav", "Tasnish"];
crew[0] = "Shubham";           // allowed
crew.push("Riya");             // allowed
// crew = ["Someone"];         // TypeError

To truly lock an object, use Object.freeze(person).

Comparison table

Feature var 🟡 let 🔵 const 🟢
Scope Function Block {} Block {}
Hoisting Yes (starts as undefined) Yes (but in TDZ) Yes (but in TDZ)
Redeclare Allowed Not allowed Not allowed
Reassign Allowed Allowed Not allowed
Initial value Optional Optional Required
Temporal Dead Zone No Yes Yes
Becomes window.name Yes (top level) No No
Use case Old code (avoid) Values that change Fixed references

Rule of thumb: start with const. Switch to let only if you need to reassign. Skip var.

Hoisting and the Temporal Dead Zone (TDZ)

Hoisting means declarations are registered before any code runs. What you can do with the variable before its line depends on the keyword.

console.log(a);   // undefined  (var is hoisted and set to undefined)
var a = 5;

console.log(b);   // ReferenceError: cannot access 'b' before initialization
let b = 5;

let and const are hoisted too, but they sit in the Temporal Dead Zone from the start of the scope until the line where they are declared. Touching them there throws an error, which is good: it catches bugs early.

Gotcha: forgetting the keyword. price = 99; With no declaration, it creates an implicit global in sloppy mode. Add "use strict"; at the top of your file to turn this into an error.

Naming rules

Rule Example
Letters, digits, _, $ are allowed user_1, $price
Cannot start with a digit 1user is invalid
Case-sensitive age and Age are different
No reserved words let, class, return are invalid
Convention: camelCase firstName, isLoggedIn

Choose names that describe the value: totalPrice beats tp.

Data types

JavaScript has 7 primitive types and 1 non-primitive (objects).

Primitives hold a single value and are immutable (the value itself cannot be changed). They are copied by value.

1. Boolean

Only true or false.

let isLoggedIn = true;

2. Number

Integers and decimals share one type.

let age = 25;
let price = 99.99;

1 / 0;               // Infinity
"abc" * 2;           // NaN (Not a Number)
typeof NaN;          // "number" (yes, really)
NaN === NaN;         // false (use Number.isNaN(x))
0.1 + 0.2;           // 0.30000000000000004 (floating point)
Number.MAX_SAFE_INTEGER;   // 9007199254740991

3. BigInt

Integers of any size. Add n at the end.

let big = 12345678901234567890n;
typeof big;          // "bigint"
// 1n + 1;           // TypeError: cannot mix BigInt and Number
1n + 1n;             // 2n

4. String

Text. Use single quotes, double quotes, or backticks (template literals).

let name = "John";
let greeting = `Hello, ${name}!`;   // "Hello, John!"

name[0];             // "J"
name[0] = "X";       // ignored: strings are immutable

5. undefined

A variable that was declared but has no value yet. JavaScript sets it for you.

let x;
console.log(x);      // undefined

6. null

You set this on purpose to mean "empty" or "no value".

let selectedUser = null;

undefined vs null: undefined is "JavaScript has no value here". null is "I am saying there is nothing here".

null == undefined;    // true  (loose equality)
null === undefined;   // false (strict equality, use this)

null is case-sensitive: Null and NULL are not keywords.

7. Symbol

A guaranteed-unique value, often used as a hidden object key.

const id1 = Symbol("id");
const id2 = Symbol("id");
id1 === id2;          // false (always unique)

Non-primitive: Object (including Array and Function)

These store collections or complex data and are copied by reference (a pointer to the memory location).

let user = { name: "John", age: 25 };   // Object
let numbers = [1, 2, 3];                // Array
function greet() { console.log("Hello"); }   // Function

Value vs reference: the key difference

// Primitives: copied by value
let a = 1;
let b = a;
b = 2;
console.log(a);       // 1 (unaffected)

// Objects: copied by reference
let o1 = { x: 1 };
let o2 = o1;
o2.x = 99;
console.log(o1.x);    // 99 (same object!)

The typeof operator

Returns: a string naming the type.

typeof "chaicode";       // "string"
typeof 42;               // "number"
typeof 42n;              // "bigint"
typeof true;             // "boolean"
typeof undefined;        // "undefined"
typeof Symbol();         // "symbol"
typeof {};               // "object"
typeof function () {};   // "function"

typeof null;             // "object" ⚠️ (an old bug, kept for compatibility)
typeof [];               // "object" ⚠️ (arrays are objects)

Better checks:

value === null;           // is it null?
Array.isArray(value);     // is it an array?

Type conversion and truthy/falsy

JavaScript often converts types for you, which can surprise you.

"5" + 1;           // "51"  (+ joins when a string is involved)
"5" - 1;           // 4     (- converts to number)

Convert on purpose:

Number("42");      // 42
Number("abc");     // NaN
String(123);       // "123"
Boolean("");       // false
parseInt("42px");  // 42

Falsy values act as false in an if. There are only 8:

false, 0, -0, 0n, "", null, undefined, NaN

Everything else is truthy, including "0", [], and {}.

Rule: use === (strict equality) instead of == to avoid hidden conversions.

Scope: where a variable can be used

Scope decides where a variable is visible.

Global scope

Declared outside everything. Usable anywhere.

let name = "John";

function greet() {
  console.log(name);   // accessible
}

Keep globals to a minimum. They can be changed from anywhere.

Function (local) scope

Declared inside a function. Usable only inside it.

function greet() {
  let message = "Hello";
  console.log(message);   // accessible
}
// console.log(message);  // ReferenceError

Block scope (let and const)

Inside { } such as if or for. Usable only inside that block.

if (true) {
  let age = 25;
  console.log(age);   // accessible
}
// console.log(age);  // ReferenceError

Special case: var ignores blocks

if (true) {
  var x = 10;
}
console.log(x);   // 10 (leaked out of the block)

The classic loop bug

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);
}
// 3, 3, 3   (one shared `i`)

for (let j = 0; j < 3; j++) {
  setTimeout(() => console.log(j), 0);
}
// 0, 1, 2   (a new `j` for every loop)

Scope chain and shadowing

JavaScript looks for a variable in the current scope, then moves outward. An inner variable with the same name shadows the outer one.

let city = "Delhi";

function show() {
  let city = "Jaipur";   // shadows the outer city
  console.log(city);     // "Jaipur"
}

show();
console.log(city);       // "Delhi"

Common mistakes

Mistake Fix
Using var Use const or let
Using let when the value never changes Use const
Thinking const makes objects immutable Use Object.freeze()
Checking arrays with typeof Use Array.isArray()
Checking null with typeof Use === null
Comparing with == Use ===
Mixing BigInt and Number Convert one first
Forgetting const x = ... Always declare; use "use strict"

Quick reference: the 8 types

Type Example typeof
Boolean true "boolean"
Number 42, 3.14 "number"
BigInt 9007199254740992n "bigint"
String "hello" "string"
undefined let x; "undefined"
null null "object" ⚠️
Symbol Symbol("id") "symbol"
Object (incl. Array, Function) {}, [], function(){} "object" / "function"

Wrap up

  • A variable is a labeled container. Declare it with const by default, let when it changes, and avoid var.

  • There are 7 primitives (copied by value) and objects (copied by reference).

  • typeof null is "object" and typeof [] is "object". Use better checks.

  • let and const are block-scoped; var is function-scoped and leaks out of blocks.

  • Use ===, and add "use strict" so mistakes fail loudly.

Run every example in your browser console. Seeing the results yourself is the fastest way to remember them.