Primitives vs Reference Types
JavaScript values fall into two categories:
- Primitive types β simple, immutable values stored directly in memory. There are exactly 7.
- Reference types β complex values (objects, arrays, functions) stored as a reference (pointer) to a memory location.
| Category | Types | Example |
|---|---|---|
| Primitive | string | "hello" |
| Primitive | number | 42, 3.14 |
| Primitive | bigint | 9007199254740993n |
| Primitive | boolean | true, false |
| Primitive | undefined | undefined |
| Primitive | null | null |
| Primitive | symbol | Symbol("id") |
| Reference | object | { name: "Alice" } |
| Reference | array | [1, 2, 3] |
| Reference | function | function() {} |
The 7 Primitive Types
String
A string is a sequence of characters. Strings are immutable β you can't change individual characters in place; methods return new strings.
let greeting = "Hello, World!";
let name = 'Alice';
let template = `My name is ${name}`;
console.log(typeof greeting); // "string"
console.log(greeting.length); // 13
console.log(greeting[0]); // "H"
Number
JavaScript uses a single number type for both integers and floating-point numbers (64-bit IEEE 754). This includes NaN (Not a Number) and Infinity.
let integer = 42;
let float = 3.14;
let negative = -100;
console.log(typeof integer); // "number"
console.log(typeof NaN); // "number" (surprising!)
console.log(typeof Infinity); // "number"
console.log(1 / 0); // Infinity
console.log(-1 / 0); // -Infinity
console.log(0 / 0); // NaN
console.log(NaN === NaN); // false β NaN is not equal to itself!
Boolean, BigInt, undefined, null, Symbol
// Boolean
let isActive = true;
let isAdmin = false;
console.log(typeof isActive); // "boolean"
// BigInt β for integers beyond Number.MAX_SAFE_INTEGER
let bigNum = 9007199254740993n;
console.log(typeof bigNum); // "bigint"
// undefined β variable declared but not assigned
let notAssigned;
console.log(notAssigned); // undefined
console.log(typeof notAssigned); // "undefined"
// null β intentional absence of a value
let empty = null;
console.log(empty); // null
console.log(typeof null); // "object" β famous JS bug!
// Symbol β unique, immutable identifier
let id1 = Symbol("id");
let id2 = Symbol("id");
console.log(id1 === id2); // false β every Symbol is unique
This is one of JavaScript's most notorious quirks β typeof null returns "object", not "null". It has been this way since JavaScript was created in 1995 and fixing it would break too much existing code. To check for null, use === null directly.
Reference Types β Objects, Arrays, Functions
Reference types are stored as references (memory addresses). Variables don't hold the value itself β they hold a pointer to where the value lives in the heap.
// Object
const person = { name: "Alice", age: 30 };
console.log(typeof person); // "object"
// Array (also type "object"!)
const colors = ["red", "green", "blue"];
console.log(typeof colors); // "object"
console.log(Array.isArray(colors)); // true β use this to check for arrays
// Function
function greet() { return "Hello!"; }
console.log(typeof greet); // "function" (special case of object)
null vs undefined
Both represent "no value" but they have different semantic meanings:
undefinedβ the variable exists but has not been assigned a value. JavaScript sets this automatically.nullβ the programmer intentionally set the variable to "empty" or "no object". It's an explicit assignment.
let a; // JavaScript sets this to undefined
let b = null; // Developer explicitly says "no value"
console.log(a); // undefined
console.log(b); // null
console.log(a == b); // true β loose equality coerces them
console.log(a === b); // false β strict equality: different types
Dynamic Typing
JavaScript is dynamically typed β a variable can hold a value of any type, and you can change the type by simply assigning a new value. This is flexible but requires care.
let value = 42;
console.log(typeof value); // "number"
value = "hello";
console.log(typeof value); // "string"
value = true;
console.log(typeof value); // "boolean"
value = { x: 1 };
console.log(typeof value); // "object"
Pass by Value vs Pass by Reference
This is one of the most important concepts to understand for avoiding bugs:
// Primitives are passed BY VALUE β a copy is made
let x = 10;
let y = x; // y gets a copy of 10
y = 20;
console.log(x); // 10 β x is unchanged
// Objects are passed BY REFERENCE β both point to the same object
let obj1 = { name: "Alice" };
let obj2 = obj1; // obj2 points to the SAME object
obj2.name = "Bob";
console.log(obj1.name); // "Bob" β obj1 was also changed!
// To copy an object, use spread or Object.assign
let obj3 = { ...obj1 }; // shallow copy
obj3.name = "Charlie";
console.log(obj1.name); // "Bob" β obj1 unchanged this time
Primitive values are always immutable β you cannot change a string's characters in place, and number operations always produce a new number. When you write let s = "hello"; s = s.toUpperCase();, you're not modifying the original string β you're creating a new one and reassigning the variable.
ποΈ Practical Exercise
- Use
typeofon a string, number, boolean, undefined, null, object, array, and function. Note which ones surprise you. - Demonstrate that
NaN === NaNisfalse. Then useNumber.isNaN()to properly check for NaN. - Create two variables pointing to the same object. Mutate one property through the second variable and observe that the first variable's object also changed.
- Create a shallow copy of an object using the spread operator
{ ...obj }and verify that changes to the copy don't affect the original. - Check the difference between
null == undefinedandnull === undefined.
π₯ Challenge Exercise
Write a function describeType(value) that takes any value and returns a human-readable description: e.g., "a string of 5 characters", "the number 42", "an array of 3 elements", "null (intentional absence)", "undefined (not assigned)", or "an object with 2 properties". Use typeof, Array.isArray(), and other checks to handle all the cases.
π Summary
- JavaScript has 7 primitive types: string, number, bigint, boolean, undefined, null, symbol.
- Reference types (object, array, function) store a pointer to a heap location, not the value itself.
typeof null === "object"is a historical bug β check for null with=== null.undefinedmeans unassigned;nullmeans intentionally empty.- Primitives are copied by value; objects/arrays are copied by reference.
- Use
Array.isArray()to check for arrays sincetypeof []returns"object".
Interview Questions
- What are the 7 primitive types in JavaScript?
- Why does
typeof nullreturn"object"? - What is the difference between
nullandundefined? - How do you check if a variable is an array?
- What is the difference between pass by value and pass by reference?
- Why is
NaN === NaNequal tofalse?
Frequently Asked Questions
null == undefined is true β loose equality considers them equal. null === undefined is false β strict equality requires the same type and value. A common pattern is if (value == null) to catch both null and undefined in one check.
It's a trade-off. Dynamic typing makes JavaScript quick to write and flexible for small scripts. However, in large codebases it can lead to type-related bugs. That's why TypeScript was created β it adds a static type layer on top of JavaScript. Most large JavaScript projects today use TypeScript for safety while still compiling to regular JS.
Symbols are mainly used for creating unique property keys that won't accidentally conflict with other code. They're commonly used in library code to avoid name collisions β for example, defining a unique "hidden" property on an object without overwriting an existing property with the same name. As a beginner, you'll rarely need to create Symbols directly.
The safe integer range is Number.MIN_SAFE_INTEGER to Number.MAX_SAFE_INTEGER, which is Β±(253 β 1) or about Β±9 quadrillion. Beyond this, floating-point representation causes precision errors. Use BigInt when you need exact arithmetic with very large integers (cryptocurrency amounts, database IDs, etc.).
The spread operator { ...obj } creates a shallow copy β nested objects are still shared by reference. For a deep copy, use structuredClone(obj) (modern browsers and Node 17+), or the older JSON.parse(JSON.stringify(obj)) trick (which loses functions, undefined, Dates, etc.). structuredClone is the recommended modern approach.