Understanding Primitive and Reference Types
JavaScript has two main categories of data types:
| Category | Types |
|---|---|
| Primitives (7) | Number, String, Boolean, Undefined, Null, Symbol, BigInt |
| Reference Types | Object, Array, Function, Date, RegExp, etc. |
JavaScript has only one number type - all numbers are 64-bit floating point values.
// Integers
let age = 25;
let count = 100;
// Floating point
let price = 19.99;
let pi = 3.14159;
// Special numeric values
let infinity = Infinity;
let negInfinity = -Infinity;
let notANumber = NaN;
// Number operations
console.log(10 / 3); // 3.3333333333333335
console.log(0.1 + 0.2); // 0.30000000000000004 (floating point precision)
console.log(5 / 0); // Infinity
console.log("hello" / 2); // NaN
// Checking for NaN
console.log(isNaN(NaN)); // true
console.log(isNaN("hello")); // true
console.log(Number.isNaN(NaN)); // true (more strict)
console.log(Number.isNaN("hello")); // false
// Checking for finite numbers
console.log(isFinite(100)); // true
console.log(isFinite(Infinity)); // false
// Parsing
console.log(parseInt("42")); // 42
console.log(parseInt("42.5")); // 42
console.log(parseFloat("42.5")); // 42.5
console.log(parseInt("42px")); // 42
// Converting to fixed decimals
let num = 3.14159;
console.log(num.toFixed(2)); // "3.14"
Strings represent text and are immutable sequences of characters.
// String literals
let single = 'Hello';
let double = "World";
let template = `Hello, World!`;
// String concatenation
let greeting = "Hello" + " " + "World"; // "Hello World"
// Template literals (ES6)
let name = "Alice";
let age = 25;
let message = `My name is ${name} and I'm ${age} years old`;
// Multi-line strings
let multiline = `
This is a
multi-line
string
`;
// Escape characters
let escaped = "She said, \"Hello!\"";
let newline = "Line 1\nLine 2";
let tab = "Column1\tColumn2";
let str = "Hello, World!";
// Length
console.log(str.length); // 13
// Accessing characters
console.log(str[0]); // "H"
console.log(str.charAt(0)); // "H"
// Case conversion
console.log(str.toLowerCase()); // "hello, world!"
console.log(str.toUpperCase()); // "HELLO, WORLD!"
// Searching
console.log(str.indexOf("World")); // 7
console.log(str.includes("World")); // true
console.log(str.startsWith("Hello")); // true
console.log(str.endsWith("!")); // true
// Extracting
console.log(str.slice(0, 5)); // "Hello"
console.log(str.substring(7, 12)); // "World"
console.log(str.substr(7, 5)); // "World" (deprecated)
// Replacing
console.log(str.replace("World", "JavaScript")); // "Hello, JavaScript!"
// Splitting
console.log(str.split(", ")); // ["Hello", "World!"]
// Trimming
let padded = " hello ";
console.log(padded.trim()); // "hello"
Booleans represent logical values: true or false.
let isActive = true;
let isComplete = false;
// Boolean operations
console.log(true && false); // false (AND)
console.log(true || false); // true (OR)
console.log(!true); // false (NOT)
// Comparison operators return booleans
console.log(5 > 3); // true
console.log(10 === 10); // true
console.log("a" < "b"); // true
// Truthy and Falsy values
// Falsy: false, 0, "", null, undefined, NaN
// Everything else is truthy
if ("hello") {
console.log("Strings are truthy");
}
if (0) {
console.log("This won't run");
}
// Converting to boolean
console.log(Boolean(1)); // true
console.log(Boolean(0)); // false
console.log(Boolean("")); // false
console.log(Boolean("text")); // true
undefined means a variable has been declared but not assigned a value.
let x;
console.log(x); // undefined
console.log(typeof x); // "undefined"
// Function with no return value
function doNothing() {}
console.log(doNothing()); // undefined
// Accessing non-existent property
let obj = {};
console.log(obj.name); // undefined
// Function parameter not provided
function greet(name) {
console.log(name);
}
greet(); // undefined
null represents the intentional absence of a value.
let empty = null;
console.log(empty); // null
console.log(typeof null); // "object" (historical bug!)
// null vs undefined
let notAssigned; // undefined
let intentionallyEmpty = null; // null
// Checking for null
if (empty === null) {
console.log("Value is null");
}
// null in operations
console.log(null + 5); // 5 (null converts to 0)
console.log(null == undefined); // true (loose equality)
console.log(null === undefined); // false (strict equality)
Symbols are unique and immutable identifiers, often used as object property keys.
// Creating symbols
let sym1 = Symbol();
let sym2 = Symbol("description");
let sym3 = Symbol("description");
console.log(sym2 === sym3); // false (each symbol is unique)
// Using symbols as object keys
let id = Symbol("id");
let user = {
name: "Alice",
[id]: 123
};
console.log(user[id]); // 123
console.log(user.id); // undefined
// Symbols are not enumerable
for (let key in user) {
console.log(key); // Only "name" is logged
}
// Global symbol registry
let globalSym1 = Symbol.for("app.id");
let globalSym2 = Symbol.for("app.id");
console.log(globalSym1 === globalSym2); // true
BigInt allows you to work with integers larger than Number.MAX_SAFE_INTEGER.
// Creating BigInt
let bigNum1 = 9007199254740991n;
let bigNum2 = BigInt("9007199254740991");
console.log(typeof bigNum1); // "bigint"
// BigInt operations
let sum = 100n + 200n; // 300n
let product = 50n * 2n; // 100n
// Cannot mix BigInt and Number
// console.log(100n + 50); // TypeError
// Must convert explicitly
console.log(100n + BigInt(50)); // 150n
console.log(Number(100n) + 50); // 150
// Comparison works
console.log(100n > 50); // true
console.log(100n === 100); // false (different types)
console.log(100n == 100); // true (loose equality)
Objects are collections of key-value pairs.
// Object literal
let person = {
name: "Alice",
age: 25,
isStudent: true,
greet: function() {
console.log("Hello!");
}
};
// Accessing properties
console.log(person.name); // "Alice"
console.log(person["age"]); // 25
// Adding properties
person.email = "alice@example.com";
// Deleting properties
delete person.isStudent;
// Checking for properties
console.log("name" in person); // true
console.log(person.hasOwnProperty("age")); // true
// Object methods
console.log(Object.keys(person)); // ["name", "age", "email", "greet"]
console.log(Object.values(person)); // ["Alice", 25, "alice@example.com", function]
console.log(Object.entries(person)); // [["name", "Alice"], ...]
Arrays are ordered collections of values.
// Array literal
let numbers = [1, 2, 3, 4, 5];
let mixed = [1, "hello", true, null, {name: "Alice"}];
// Accessing elements
console.log(numbers[0]); // 1
console.log(numbers.length); // 5
// Modifying arrays
numbers.push(6); // Add to end
numbers.pop(); // Remove from end
numbers.unshift(0); // Add to beginning
numbers.shift(); // Remove from beginning
// Array methods
let doubled = numbers.map(n => n * 2);
let evens = numbers.filter(n => n % 2 === 0);
let sum = numbers.reduce((acc, n) => acc + n, 0);
// Checking if array
console.log(Array.isArray(numbers)); // true
console.log(typeof numbers); // "object"
Functions are first-class objects in JavaScript.
// Function declaration
function add(a, b) {
return a + b;
}
// Function expression
let multiply = function(a, b) {
return a * b;
};
// Arrow function
let divide = (a, b) => a / b;
// Functions are objects
console.log(typeof add); // "function"
add.customProperty = "value";
console.log(add.customProperty); // "value"
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" (bug!)
console.log(typeof Symbol()); // "symbol"
console.log(typeof 100n); // "bigint"
console.log(typeof {}); // "object"
console.log(typeof []); // "object"
console.log(typeof function(){}); // "function"
// Check for null
let value = null;
console.log(value === null); // true
// Check for array
console.log(Array.isArray([])); // true
console.log(Array.isArray({})); // false
// Check for object (not null, not array)
function isObject(value) {
return value !== null &&
typeof value === 'object' &&
!Array.isArray(value);
}
// Using Object.prototype.toString
function getType(value) {
return Object.prototype.toString.call(value).slice(8, -1);
}
console.log(getType(42)); // "Number"
console.log(getType("hello")); // "String"
console.log(getType([])); // "Array"
console.log(getType(null)); // "Null"
console.log(getType(new Date())); // "Date"
JavaScript automatically converts types in certain situations (implicit coercion).
// + operator with strings
console.log("5" + 3); // "53" (number to string)
console.log("Hello" + true); // "Hellotrue"
console.log("Value: " + null); // "Value: null"
// Template literals
console.log(`Count: ${5}`); // "Count: 5"
// Arithmetic operators (except +)
console.log("5" - 2); // 3
console.log("10" * "2"); // 20
console.log("20" / "4"); // 5
console.log("5" % 2); // 1
// Unary + operator
console.log(+"42"); // 42
console.log(+true); // 1
console.log(+false); // 0
console.log(+null); // 0
console.log(+undefined); // NaN
// Falsy values: false, 0, "", null, undefined, NaN
// Everything else is truthy
if ("") {
console.log("Won't run");
}
if ("hello") {
console.log("Will run");
}
// Double NOT for explicit conversion
console.log(!!"hello"); // true
console.log(!!0); // false
console.log(!!null); // false
// Loose equality (==) performs coercion
console.log(5 == "5"); // true
console.log(true == 1); // true
console.log(false == 0); // true
console.log(null == undefined); // true
console.log("" == 0); // true
// Strict equality (===) does NOT coerce
console.log(5 === "5"); // false
console.log(true === 1); // false
console.log(null === undefined); // false
// Always prefer === for clarity
Explicit type conversion is clearer and safer than relying on coercion.
// To String
String(123); // "123"
String(true); // "true"
String(null); // "null"
(123).toString(); // "123"
// To Number
Number("123"); // 123
Number("12.5"); // 12.5
Number("hello"); // NaN
Number(true); // 1
Number(false); // 0
Number(null); // 0
Number(undefined); // NaN
parseInt("42"); // 42
parseInt("42.5"); // 42
parseFloat("42.5"); // 42.5
// To Boolean
Boolean(1); // true
Boolean(0); // false
Boolean("hello"); // true
Boolean(""); // false
Boolean(null); // false
Boolean(undefined); // false
let str = "hello";
str[0] = "H"; // Doesn't work
console.log(str); // "hello"
// String methods return new strings
let upper = str.toUpperCase();
console.log(str); // "hello" (original unchanged)
console.log(upper); // "HELLO" (new string)
let a = 5;
let b = a; // Copy value
b = 10;
console.log(a); // 5 (unchanged)
console.log(b); // 10
let obj1 = { name: "Alice" };
let obj2 = obj1; // Copy reference
obj2.name = "Bob";
console.log(obj1.name); // "Bob" (changed!)
console.log(obj2.name); // "Bob"
// Arrays too
let arr1 = [1, 2, 3];
let arr2 = arr1;
arr2.push(4);
console.log(arr1); // [1, 2, 3, 4] (changed!)
console.log(arr2); // [1, 2, 3, 4]
// Shallow clone object
let original = { name: "Alice", age: 25 };
let clone1 = { ...original }; // Spread operator
let clone2 = Object.assign({}, original);
clone1.name = "Bob";
console.log(original.name); // "Alice" (unchanged)
// Shallow clone array
let arr = [1, 2, 3];
let arrClone1 = [...arr];
let arrClone2 = arr.slice();
// Deep clone (for nested objects)
let nested = { user: { name: "Alice" } };
let deepClone = JSON.parse(JSON.stringify(nested));
deepClone.user.name = "Bob";
console.log(nested.user.name); // "Alice" (unchanged)
console.log(typeof null); // "object" (bug!)
// Correct way to check for null
if (value === null) {
// Handle null
}
console.log(NaN === NaN); // false!
// Use isNaN or Number.isNaN
console.log(isNaN(NaN)); // true
console.log(Number.isNaN(NaN)); // true (more strict)
console.log(0.1 + 0.2); // 0.30000000000000004
// Solution: Use toFixed or round
console.log((0.1 + 0.2).toFixed(2)); // "0.30"
console.log(Math.round((0.1 + 0.2) * 100) / 100); // 0.3
console.log(typeof []); // "object"
// Use Array.isArray
console.log(Array.isArray([])); // true
// Empty array and object are truthy!
if ([]) {
console.log("Empty array is truthy");
}
if ({}) {
console.log("Empty object is truthy");
}
// Check length for arrays
let arr = [];
if (arr.length > 0) {
// Array has items
}
// Check keys for objects
let obj = {};
if (Object.keys(obj).length > 0) {
// Object has properties
}
// Good
if (value === 5) { }
// Avoid
if (value == 5) { }
// Good
let num = Number(input);
let str = String(value);
// Avoid relying on coercion
let num = +input;
let str = value + "";
// Good
const PI = 3.14159;
let counter = 0;
// Avoid
var x = 5;
function divide(a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new TypeError('Arguments must be numbers');
}
if (b === 0) {
throw new Error('Cannot divide by zero');
}
return a / b;
}
typeof for basic type checking=== for strict equality without coercionUnderstanding JavaScript's type system is fundamental to writing reliable code. While JavaScript's dynamic typing and type coercion can be convenient, they can also lead to subtle bugs. By knowing the differences between primitive and reference types, using strict equality, and performing explicit type conversions, you'll write more predictable and maintainable code.
Practice tip: When debugging unexpected behavior, always check the types of your
variables using typeof and console.log(). Many bugs stem from incorrect
type assumptions.