JavaScript Variables and Data Types | let, const, and var Explained
이 글의 핵심
A hands-on guide to JavaScript variables and data types: let, const, var, scoping rules, and how to write predictable code.
Introduction
What is a variable?
A variable is a named place to store data. JavaScript is a dynamically typed language: a variable’s “type” is determined by the value it holds.
1. Declarations: let, const, var
let: reassignment allowed
let age = 25;
console.log(age); // 25
age = 26; // reassignment OK
console.log(age); // 26
// Declare now, assign later
let name;
console.log(name); // undefined
name = "Alice";
console.log(name); // Alice
const: no reassignment
const declares a constant binding; you cannot reassign it:
const PI = 3.14159;
console.log(PI); // 3.14159
// Reassignment throws
// PI = 3.14; // TypeError: Assignment to constant variable
// Must initialize at declaration
// const x; // SyntaxError: Missing initializer in const declaration
// Important: object/array *contents* can still change
const person = { name: "Alice", age: 25 };
// The binding `person` cannot be reassigned
// But properties can change
person.age = 26; // ✅ OK (mutate object contents)
console.log(person); // { name: 'Alice', age: 26 }
// person = {}; // ❌ TypeError (rebinding not allowed)
const arr = [1, 2, 3];
// The binding `arr` cannot be reassigned
// Elements can change
arr.push(4); // ✅ OK
console.log(arr); // [1, 2, 3, 4]
arr[0] = 10; // ✅ OK
console.log(arr); // [10, 2, 3, 4]
// arr = []; // ❌ TypeError
What const really means:
// const fixes the *reference* the variable holds
// The referenced object’s contents can still change
const obj = { x: 1 };
// obj points at a specific object in memory
obj.x = 2; // ✅ mutate contents (same reference)
// obj = { x: 2 }; // ❌ new object (new reference)
Deep immutability:
// Object.freeze(): shallow freeze
const person = Object.freeze({ name: "Alice", age: 25 });
// person.age = 26; // ignored (strict mode throws)
console.log(person.age); // 25
// Nested objects are not frozen automatically
const user = Object.freeze({
name: "Alice",
address: { city: "Seoul" } // inner object not frozen
});
user.address.city = "Busan"; // ✅ still mutates nested object
console.log(user.address.city); // Busan
var: legacy (not recommended)
var x = 10;
var x = 20; // redeclaration allowed (problematic!)
console.log(x); // 20
// Function scope (ignores block scope)
if (true) {
var y = 30;
}
console.log(y); // 30 (accessible outside the block!)
// Hoisting surprise
console.log(z); // undefined (not an error!)
var z = 40;
let vs const vs var
| let | const | var | |
|---|---|---|---|
| Reassignment | ✅ | ❌ | ✅ |
| Redeclare (same scope) | ❌ | ❌ | ✅ |
| Scope | Block | Block | Function |
| Hoisting | TDZ | TDZ | undefined |
| Recommended | ✅ | ✅ | ❌ |
TDZ (Temporal Dead Zone): accessing before declaration throws ReferenceError.
// let/const: TDZ
// console.log(x); // ReferenceError
let x = 10;
// var: hoisted as undefined
console.log(y); // undefined
var y = 20;
2. Scope
Block scope
let and const are block-scoped:
{
let x = 10;
const y = 20;
console.log(x, y); // 10 20
}
// console.log(x); // ReferenceError
// console.log(y); // ReferenceError
if (true) {
let z = 30;
console.log(z); // 30
}
// console.log(z); // ReferenceError
for (let i = 0; i < 3; i++) {
console.log(i); // 0 1 2
}
// console.log(i); // ReferenceError
// Nested blocks
{
let x = 10;
{
let x = 20; // shadowing
console.log(x); // 20
}
console.log(x); // 10
}
// Closures in loops: let creates a new binding per iteration
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // 0 1 2
}, 100);
}
// With var, one shared binding
for (var j = 0; j < 3; j++) {
setTimeout(() => {
console.log(j); // 3 3 3
}, 100);
}
Benefits of block scope:
- Fewer accidental collisions
- Variables can be collected when the block ends
- Clearer lifetime
- Safer with closures in loops
Function scope
function test() {
let x = 10;
var y = 20;
if (true) {
let x = 30; // new block-scoped x
var y = 40; // same function-scoped y
console.log(x, y); // 30 40
}
console.log(x, y); // 10 40
}
test();
Global scope
let globalVar = "global";
function test() {
console.log(globalVar);
}
test();
// Browser: window
// var global = "test";
// console.log(window.global);
// Node.js: global
// global.myVar = "test";
3. Data types
Primitive types
JavaScript has seven primitive types.
1. Number
let integer = 42;
let float = 3.14;
let negative = -10;
let inf = Infinity;
let negInf = -Infinity;
let notANumber = NaN;
console.log(10 / 0); // Infinity
console.log("abc" / 2); // NaN
console.log(isNaN(NaN)); // true
console.log(isNaN(10)); // false
console.log(Number.MAX_SAFE_INTEGER);
console.log(Number.MIN_SAFE_INTEGER);
2. String
let str1 = "double quotes";
let str2 = 'single quotes';
let str3 = `template literal`;
let name = "Alice";
let age = 25;
let message = `Hello, ${name}! You are ${age}.`;
console.log(message);
let multiline = `line one
line two
line three`;
let escaped = "He said \"Hello\"";
console.log(escaped);
3. Boolean
let isTrue = true;
let isFalse = false;
console.log(10 > 5); // true
console.log(10 === 5); // false
4. undefined
let x;
console.log(x); // undefined
console.log(typeof x); // undefined
function test() {
// no return
}
console.log(test()); // undefined
5. null
let empty = null;
console.log(empty); // null
console.log(typeof empty); // "object" (historical quirk)
let x; // undefined (declared only)
let y = null; // explicit empty
6. Symbol (ES6+)
let sym1 = Symbol("description");
let sym2 = Symbol("description");
console.log(sym1 === sym2); // false (always unique)
let obj = {
[sym1]: "value1"
};
console.log(obj[sym1]); // value1
7. BigInt (ES2020+)
let bigNum = 9007199254740991n;
let bigNum2 = BigInt("9007199254740991");
console.log(bigNum + 1n);
// Cannot mix with Number without conversion
// console.log(bigNum + 1); // TypeError
console.log(bigNum + BigInt(1)); // OK
Reference types
Object
let person = {
name: "Alice",
age: 25,
greet: function() {
console.log(`Hello, I'm ${this.name}.`);
}
};
console.log(person.name);
person.greet();
Array
let numbers = [1, 2, 3, 4, 5];
let mixed = [1, "hello", true, null, { name: "Alice" }];
console.log(numbers[0]);
console.log(numbers.length);
Function
function add(a, b) {
return a + b;
}
console.log(typeof add); // function
4. Type checks and conversion
typeof
console.log(typeof 42); // number
console.log(typeof "hello"); // string
console.log(typeof true); // boolean
console.log(typeof undefined); // undefined
console.log(typeof null); // object (quirk)
console.log(typeof {}); // object
console.log(typeof []); // object
console.log(typeof function(){}); // function
console.log(Array.isArray([])); // true
console.log(Array.isArray({})); // false
Coercion
To string
let num = 42;
let str = String(num);
console.log(str, typeof str);
let str2 = num.toString();
let result = 42 + " apples";
console.log(result);
To number
let str = "42";
let num = Number(str);
console.log(parseInt("42px"));
console.log(parseFloat("3.14abc"));
console.log(parseInt("abc")); // NaN
console.log("42" - 10);
console.log("42" * 2);
console.log(+"42");
To boolean
console.log(Boolean(1));
console.log(Boolean(0));
console.log(Boolean("hello"));
console.log(Boolean(""));
// Falsy values
console.log(Boolean(false));
console.log(Boolean(0));
console.log(Boolean(""));
console.log(Boolean(null));
console.log(Boolean(undefined));
console.log(Boolean(NaN));
// Truthy
console.log(Boolean(1));
console.log(Boolean("hello"));
console.log(Boolean([]));
console.log(Boolean({}));
if ("hello") {
console.log("Truthy!");
}
5. Hoisting
What is hoisting?
Hoisting means declarations are conceptually moved to the top of their scope (behavior differs for var, let/const, and functions).
var hoisting
console.log(x); // undefined
var x = 10;
console.log(x); // 10
// Conceptually similar to:
var x;
console.log(x);
x = 10;
console.log(x);
let/const and the TDZ
// console.log(x); // ReferenceError (TDZ)
let x = 10;
// console.log(y); // ReferenceError
const y = 20;
Function hoisting
greet(); // works (function declaration hoisted)
function greet() {
console.log("Hello!");
}
// sayHello(); // ReferenceError (function expression not hoisted)
const sayHello = function() {
console.log("Hello!");
};
sayHello();
6. Practical examples
Example 1: swapping variables
let a = 10;
let b = 20;
let temp = a;
a = b;
b = temp;
console.log(a, b); // 20 10
let x = 10;
let y = 20;
[x, y] = [y, x];
console.log(x, y); // 20 10
Example 2: a small getType helper
function getType(value) {
if (value === null) return "null";
if (Array.isArray(value)) return "array";
return typeof value;
}
Example 3: safe integer parsing
function safeParseInt(value, defaultValue = 0) {
const parsed = parseInt(value);
return isNaN(parsed) ? defaultValue : parsed;
}
Example 4: config and immutability
const config = {
apiUrl: "https://api.example.com",
timeout: 5000
};
config.timeout = 10000; // OK (mutate object)
const frozenConfig = Object.freeze({
apiUrl: "https://api.example.com",
timeout: 5000
});
const user = Object.freeze({
name: "Alice",
address: { city: "Seoul" }
});
user.address.city = "Busan"; // nested still mutable
function deepFreeze(obj) {
Object.freeze(obj);
Object.keys(obj).forEach(key => {
if (typeof obj[key] === 'object' && obj[key] !== null) {
deepFreeze(obj[key]);
}
});
return obj;
}
7. Common mistakes
Mistake 1: using var in loops
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
Mistake 2: misunderstanding const
const arr = [1, 2, 3];
arr.push(4);
arr[0] = 100;
Mistake 3: == vs ===
console.log(5 == "5");
console.log(5 === "5");
Mistake 4: accidental string concatenation
console.log("10" + 20);
console.log(Number("10") + 20);
Mistake 5: leaking globals
// count = 0; // creates global in sloppy mode — avoid
let count = 0;
function increment() {
count++;
}
8. Practical tips
Tip 1: const by default
const API_URL = "https://api.example.com";
let retryCount = 0;
Tip 2: meaningful names
let userAge = 25;
let scores = [1, 2, 3];
const MAX_USERS = 100;
Tip 3: strict mode
"use strict";
// x = 10; // ReferenceError
9. Exercises
Exercise 1: rotate three variables
let a = 1;
let b = 2;
let c = 3;
[a, b, c] = [c, a, b];
console.log(a, b, c); // 3 1 2
Exercise 2: toNumber
function toNumber(value, defaultValue = 0) {
const num = Number(value);
return isNaN(num) ? defaultValue : num;
}
Exercise 3: remove falsy values
function removeFalsy(arr) {
return arr.filter(Boolean);
}
Summary
Key points
- Declarations:
constdefault,letwhen rebinding, avoidvarin new code. - Scope: block scope for
let/const, function scope forvar. - Types: seven primitives plus objects, arrays, functions.
- Coercion: prefer explicit
Number/String/Booleanwhen unclear. - Hoisting:
varinitializes asundefined;let/constuse TDZ.
Best practices
- Use
const, thenlet. - Prefer
===over==. - Use
"use strict". - Prefer descriptive names.
- Minimize globals.
Next steps
- JavaScript functions
- JavaScript arrays and objects
Related posts
- Get started with JavaScript | essential web language intro