JavaScript variables hold values. The modern keywords are let for values that change and const for bindings that do not reassign. The language has seven primitive types plus object. This lesson walks through declaration, the type system, the surprising rules of type coercion, and the equality operators you should use.
let, const, and the old var
Use const by default. Use let only when the binding must change. Avoid var, which has function scope and a hoisting surprise called the temporal dead zone. const does not mean the value is immutable, only that the binding cannot be reassigned. Objects and arrays declared with const can still be mutated.
const name = "Asha";
name = "Bala"; // TypeError: Assignment to constant variable
let score = 0;
score = 100; // OK
const user = { name: "Asha" };
user.name = "Bala"; // OK, mutating the object
user = {}; // TypeError
The seven primitive types
A primitive is a value that is not an object and has no methods stored on it. The seven are string, number, bigint, boolean, the missing-value primitive, null, and symbol. JavaScript auto-boxes primitives so you can call methods on them, but the methods are looked up on a wrapper object each time.
const s = "hello";
console.log(s.toUpperCase()); // HELLO
const n = 42;
console.log(n.toString(2)); // "101010" -- binary representation
console.log(typeof s); // "string"
console.log(typeof n); // "number"
console.log(typeof undeclaredName); // returns the missing-value-primitive name as a string
console.log(typeof null); // "object" -- historical bug
console.log(typeof Symbol("x")); // "symbol"
console.log(typeof 1n); // "bigint"
Type coercion and the === operator
JavaScript will silently convert types in many places. The == operator does coercion, which leads to bugs. Always use === (strict equality) which compares without coercion. The few cases where == is sensible are null and the missing-value primitive, because the loose check accepts both at once.
console.log(0 == ""); // true (loose)
console.log(0 === ""); // false (strict)
const missing = void 0;
console.log(null == missing); // true (intentional: matches both null and the missing-value primitive)
console.log(null === missing); // false
console.log("5" + 3); // "53" (number to string, then concat)
console.log("5" - 3); // 2 (string to number, then subtract)
console.log(true + 1); // 2
console.log(true + "1"); // "true1"
Numbers, bigints, and floating point
All numbers in JavaScript are IEEE-754 doubles. That gives you about 15 to 17 significant digits and the floating-point quirks you would expect. Use bigint for integers beyond 2 to the 53 minus 1. The Math object holds the usual constants and functions.
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(Number.EPSILON); // 2.220446049250313e-16
const big = 9007199254740993n;
console.log(big + 1n); // 9007199254740994n
console.log(Math.max(1, 5, 3)); // 5
console.log(Math.floor(3.7)); // 3
console.log(Math.random()); // 0.0 to 1.0
Strings and template literals
Strings are immutable sequences of UTF-16 code units. Use single quotes, double quotes, or backticks. Backticks give you template literals, which let you embed expressions with ${} and span multiple lines.
const name = "Asha";
const age = 28;
const greeting = `Hello, ${name}! You are ${age} years old.`;
const multiline = `This string
spans multiple
lines.`;
console.log(greeting); // Hello, Asha! You are 28 years old.
console.log("hello".toUpperCase()); // HELLO
console.log("abc".repeat(3)); // abcabcabc
Summary
Use const for bindings that never reassign, let for everything else. Always use === over ==. Numbers are doubles, so beware floating-point quirks. The next lesson covers control flow with if, else, switch, and the modern ternary and nullish-coalescing operators.
Common pitfalls with types and variables
The most common bug for new JavaScript developers is reassigning a const binding and getting a runtime TypeError. The next is mutating an object or array they thought was immutable because the binding was const. The third is mixing up == and ==, especially in code that checks for null or the missing-value primitive. The fix is always to default to const, only switch to let when reassignment is required, and always use === unless you are explicitly checking for both null and the missing-value primitive at once.
// Common bug: mutating assumed-immutable const
const list = Object.freeze([1, 2, 3]);
list.push(4); // TypeError: cannot add property 4, object is not extensible
// Object.freeze is shallow. Nested objects are still mutable.
const config = Object.freeze({ ui: { theme: "dark" } });
config.ui.theme = "light"; // works (nested object is not frozen)
Reference versus value semantics
Primitives are passed and compared by value. Objects and arrays are passed and compared by reference. Two distinct objects with the same content are not equal under ===. This surprises developers from languages with value-type collections. The fix is structural comparison with deep equality libraries, or use the reference itself as the identity.
const a = { x: 1 };
const b = { x: 1 };
console.log(a === b); // false (different references)
console.log(JSON.stringify(a) === JSON.stringify(b)); // true (same content)
const arr1 = [1, 2, 3];
const arr2 = arr1;
arr2.push(4);
console.log(arr1); // [1, 2, 3, 4] -- shared reference
const copy = [...arr1]; // shallow copy
copy.push(5);
console.log(arr1); // [1, 2, 3, 4] -- not affected
Comparison with Python
Python has separate primitive and collection types just like JavaScript, but Python integers can grow arbitrarily large and Python has a clean None value instead of the missing-value primitive. JavaScript's number type is always a double, while Python has int, decimal.Decimal, and fractions.Fraction. Python's None compares equal to itself only; JavaScript's null and the missing-value primitive compare equal under loose == but not strict ===. Python's f-strings play the same role as JavaScript's template literals.