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

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

letconstvar
Reassignment
Redeclare (same scope)
ScopeBlockBlockFunction
HoistingTDZTDZundefined
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:

  1. Fewer accidental collisions
  2. Variables can be collected when the block ends
  3. Clearer lifetime
  4. 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

  1. Declarations: const default, let when rebinding, avoid var in new code.
  2. Scope: block scope for let/const, function scope for var.
  3. Types: seven primitives plus objects, arrays, functions.
  4. Coercion: prefer explicit Number/String/Boolean when unclear.
  5. Hoisting: var initializes as undefined; let/const use TDZ.

Best practices

  1. Use const, then let.
  2. Prefer === over ==.
  3. Use "use strict".
  4. Prefer descriptive names.
  5. Minimize globals.

Next steps

  • JavaScript functions
  • JavaScript arrays and objects

  • Get started with JavaScript | essential web language intro