JavaScript Data Types: Primitive Types and Objects
Every value in JavaScript has a type. When you write 42, JavaScript knows it is a number. When you write "hello", it knows it is a string. Understanding data types is essential because the type of a value determines what operations you can perform on it, how it behaves in comparisons, and how it is stored in memory.
JavaScript has eight data types: seven primitives (number, bigint, string, boolean, null, undefined, symbol) and one non-primitive (object). This guide explores each type in depth, explains how JavaScript's dynamic type system works, and covers the critical difference between how primitives and objects are stored in memory.
JavaScript's Type System: Dynamic and Weakly Typed
Before diving into individual types, you need to understand two defining characteristics of JavaScript's type system.
Dynamically Typed
In JavaScript, variables do not have fixed types. A variable can hold a number one moment and a string the next. The type belongs to the value, not to the variable:
let data = 42; // data holds a number
console.log(typeof data); // "number"
data = "hello"; // now data holds a string
console.log(typeof data); // "string"
data = true; // now data holds a boolean
console.log(typeof data); // "boolean"
In statically typed languages like Java or TypeScript, this would be an error. In JavaScript, it is perfectly valid. This flexibility makes JavaScript easy to get started with, but it also means the language will not catch type-related mistakes at development time.
Weakly Typed (Loose Type Coercion)
JavaScript automatically converts values between types when it thinks a conversion makes sense. This is called type coercion, and it can produce surprising results:
console.log("5" + 3); // "53" (number 3 converted to string, concatenated)
console.log("5" - 3); // 2 (string "5" converted to number, subtracted)
console.log(true + 1); // 2 (true converted to 1)
console.log("5" * "3"); // 15 (both strings converted to numbers)
console.log("hello" * 2); // NaN ("hello" can't convert to a number)
Compare this with Python, which is strongly typed and would throw an error instead of guessing:
# Python raises an error
"5" + 3 # TypeError: can only concatenate str to str
Implicit type coercion is one of the most common sources of bugs in JavaScript. We cover this topic in full detail in the Type Conversions chapter. For now, be aware that JavaScript will silently convert types, and the results are not always what you expect.
Primitive Types Overview
JavaScript has seven primitive types. A primitive is a value that is not an object, has no methods of its own (though it can use methods via auto-boxing), and is immutable (cannot be changed, only replaced).
| Type | Example Values | typeof Result |
|---|---|---|
| number | 42, 3.14, NaN, Infinity | "number" |
| bigint | 9007199254740993n, 0n | "bigint" |
| string | "hello", 'world', `template` | "string" |
| boolean | true, false | "boolean" |
| null | null | "object" (bug!) |
| undefined | undefined | "undefined" |
| symbol | Symbol("id") | "symbol" |
Plus one non-primitive:
| Type | Example Values | typeof Result |
|---|---|---|
| object | {}, [], new Date(), function(){} | "object" (or "function") |
number: Integers, Floats, Infinity, NaN, and Numeric Limits
The number type represents both integers and floating-point numbers. Unlike many languages that have separate int and float types, JavaScript uses a single type for all numeric values.
Integers and Floating-Point Numbers
let integer = 42;
let negative = -17;
let float = 3.14;
let scientific = 2.5e6; // 2,500,000 (2.5 × 10^6)
let tiny = 1.5e-3; // 0.0015 (1.5 × 10^-3)
console.log(typeof integer); // "number"
console.log(typeof float); // "number" (same type for both)
Numeric Separators (ES2021)
For large numbers, you can use underscores as visual separators. They have no effect on the value:
let billion = 1_000_000_000;
let bytes = 0xFF_FF_FF;
let budget = 9_999.99;
console.log(billion); // 1000000000
Special Numeric Values
JavaScript has three special numeric values that are still of type number:
Infinity and -Infinity:
console.log(1 / 0); // Infinity
console.log(-1 / 0); // -Infinity
console.log(Infinity + 100); // Infinity
console.log(typeof Infinity); // "number"
// Checking for infinity
console.log(isFinite(42)); // true
console.log(isFinite(Infinity)); // false
console.log(isFinite(NaN)); // false
NaN (Not a Number):
NaN represents a failed numeric operation. Despite its name, typeof NaN returns "number":
console.log("hello" * 2); // NaN
console.log(Math.sqrt(-1)); // NaN
console.log(0 / 0); // NaN
console.log(parseInt("abc")); // NaN
console.log(typeof NaN); // "number" (yes, really)
NaN has a uniquely strange property: it is not equal to anything, including itself:
console.log(NaN === NaN); // false (the only value in JS not equal to itself!)
console.log(NaN == NaN); // false
To check if a value is NaN, use Number.isNaN():
// WRONG way to check for NaN
let result = "hello" * 2;
console.log(result === NaN); // false (always false!)
// RIGHT way to check for NaN
console.log(Number.isNaN(result)); // true
console.log(Number.isNaN(42)); // false
console.log(Number.isNaN("hello")); // false (it's a string, not NaN)
Do not confuse Number.isNaN() with the older global isNaN() function. The global version converts its argument to a number first, which leads to misleading results:
isNaN("hello"); // true ("hello" is converted to NaN first)
Number.isNaN("hello"); // false ("hello" is a string, not the value NaN)
isNaN("123"); // false ("123" converts to 123)
Number.isNaN("123"); // false (correct for the same reason, but more reliable)
Always use Number.isNaN().
Floating-Point Precision
JavaScript uses the IEEE 754 double-precision format (64-bit floating point), which means some decimal numbers cannot be represented exactly:
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false!
This is not a JavaScript bug. It happens in virtually every programming language that uses floating-point arithmetic. The fix depends on context:
// Solution 1: Round the result
console.log((0.1 + 0.2).toFixed(2)); // "0.30" (string)
console.log(Number((0.1 + 0.2).toFixed(2))); // 0.3 (number)
// Solution 2: Compare with a small tolerance
console.log(Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON); // true
// Solution 3: Work in cents/integers for money
let priceInCents = 499; // $4.99 stored as 499 cents
Safe Integer Range
The number type can safely represent integers between -(2^53 - 1) and 2^53 - 1:
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(Number.MIN_SAFE_INTEGER); // -9007199254740991
// Beyond this range, precision is lost
console.log(9007199254740991 + 1); // 9007199254740992 (correct)
console.log(9007199254740991 + 2); // 9007199254740992 (WRONG! should be ...993)
console.log(Number.isSafeInteger(9007199254740991)); // true
console.log(Number.isSafeInteger(9007199254740992)); // false
For numbers beyond this range, use BigInt.
bigint: Working with Arbitrarily Large Numbers
BigInt was introduced in ES2020 to represent integers of arbitrary size, beyond the safe integer limit of the regular number type.
Creating BigInts
// Add 'n' suffix to create a BigInt literal
let big = 9007199254740993n;
console.log(big); // 9007199254740993n
console.log(typeof big); // "bigint"
// Or use the BigInt() constructor
let alsoBig = BigInt("9007199254740993");
console.log(alsoBig); // 9007199254740993n
// Regular number loses precision, BigInt does not
console.log(9007199254740993); // 9007199254740992 (wrong!)
console.log(9007199254740993n); // 9007199254740993n (correct)
BigInt Operations
BigInts support standard arithmetic, but cannot be mixed with regular numbers:
let a = 100n;
let b = 200n;
console.log(a + b); // 300n
console.log(a * b); // 20000n
console.log(b / a); // 2n (integer division, no decimals)
console.log(b ** 3n); // 8000000n
// Mixing BigInt and number throws an error
console.log(100n + 50); // TypeError: Cannot mix BigInt and other types
To mix them, you must explicitly convert:
let bigVal = 100n;
let numVal = 50;
console.log(bigVal + BigInt(numVal)); // 150n
console.log(Number(bigVal) + numVal); // 150 (only if bigVal is within safe range)
When to Use BigInt
BigInt is needed for very specific use cases: cryptographic calculations, database IDs larger than 2^53, financial computations requiring exact large integers, or working with APIs that return large numbers as strings.
For everyday programming, the regular number type is sufficient.
string: Text Data, Quotes, and Template Literals
Strings represent text data. Every piece of text in JavaScript, from a single character to an entire book, is a string.
Three Ways to Create Strings
JavaScript offers three quoting styles:
let single = 'Hello, World!';
let double = "Hello, World!";
let backtick = `Hello, World!`;
// Single and double quotes are functionally identical
// Backticks have special features (template literals)
Single and double quotes behave the same way. The choice between them is purely a style preference. Most JavaScript projects use single quotes (Airbnb style guide) or double quotes (Prettier default) consistently.
Template Literals (Backticks)
Backticks create template literals, which have two powerful features:
String interpolation with ${ }:
let name = "Alice";
let age = 30;
let greeting;
// Without template literals (concatenation)
greeting = "Hello, " + name + "! You are " + age + " years old.";
// With template literals (interpolation) -> cleaner and easier to read
greeting = `Hello, ${name}! You are ${age} years old.`;
console.log(greeting); // "Hello, Alice! You are 30 years old."
// Any expression works inside ${}
console.log(`2 + 3 = ${2 + 3}`); // "2 + 3 = 5"
console.log(`Uppercase: ${name.toUpperCase()}`); // "Uppercase: ALICE"
console.log(`Is adult: ${age >= 18}`); // "Is adult: true"
Multi-line strings:
let poem;
// Without backticks: must use \n for newlines
poem = "Roses are red,\nViolets are blue,\nJavaScript is awesome,\nAnd so are you.";
// With backticks: line breaks are preserved naturally
poem = `Roses are red,
Violets are blue,
JavaScript is awesome,
And so are you.`;
console.log(poem);
// Roses are red,
// Violets are blue,
// JavaScript is awesome,
// And so are you.
Escape Characters
Special characters are inserted using a backslash \:
| Escape | Character |
|---|---|
\n | New line |
\t | Tab |
\\ | Backslash |
\' | Single quote (in single-quoted strings) |
\" | Double quote (in double-quoted strings) |
\` | Backtick (in template literals) |
let path = "C:\\Users\\Alice\\Documents";
console.log(path); // C:\Users\Alice\Documents
let quote = 'She said, "It\'s amazing!"';
console.log(quote); // She said, "It's amazing!"
String Immutability
Strings in JavaScript are immutable. You cannot change individual characters. Any operation that appears to modify a string actually creates a new one:
let greeting = "Hello";
greeting[0] = "J"; // No error, but no effect either
console.log(greeting); // "Hello" (unchanged)
// To "change" a string, create a new one
let newGreeting = "J" + greeting.slice(1);
console.log(newGreeting); // "Jello"
boolean: true, false, and Truthy/Falsy Values
The boolean type has exactly two values: true and false. Booleans are the foundation of all conditional logic.
let isActive = true;
let isDeleted = false;
console.log(typeof isActive); // "boolean"
Boolean from Comparisons
Comparisons always produce boolean values:
console.log(5 > 3); // true
console.log(10 === 10); // true
console.log("a" < "b"); // true
console.log(1 === "1"); // false (strict equality, different types)
Truthy and Falsy Values
In JavaScript, every value has an inherent boolean nature. When used in a boolean context (like an if condition), values are automatically converted to true or false.
Falsy values (exactly 8 values that convert to false):
// These are ALL the falsy values in JavaScript:
console.log(Boolean(false)); // false
console.log(Boolean(0)); // false
console.log(Boolean(-0)); // false
console.log(Boolean(0n)); // false (BigInt zero)
console.log(Boolean("")); // false (empty string)
console.log(Boolean(null)); // false
console.log(Boolean(undefined)); // false
console.log(Boolean(NaN)); // false
Everything else is truthy:
// These might surprise you, they are all truthy:
console.log(Boolean("0")); // true - non-empty string!
console.log(Boolean(" ")); // true - string with a space!
console.log(Boolean("false")); // true - string containing the word "false"!
console.log(Boolean([])); // true - empty array!
console.log(Boolean({})); // true - empty object!
console.log(Boolean(function(){})); // true - function!
console.log(Boolean(-1)); // true any non-zero number!
console.log(Boolean(Infinity)); // true
The values "0", " ", "false", [], and {} are all truthy. This catches many developers off guard. An empty array and an empty object are truthy because they are objects, and all objects are truthy.
Practical Usage
let username = "";
if (username) {
console.log(`Welcome, ${username}!`);
} else {
console.log("Please enter a username."); // This runs -> empty string is falsy
}
let items = [];
if (items.length) {
console.log("You have items in your cart.");
} else {
console.log("Your cart is empty."); // This runs -> 0 is falsy
}
null: Intentional Absence of Value
null represents the deliberate absence of a value. It is used when you want to explicitly say "this variable has no value" or "this value is empty on purpose."
let selectedUser = null; // No user is selected yet
console.log(selectedUser); // null
console.log(typeof selectedUser); // "object" (this is a known bug, see below)
// Practical usage
function findUser(id) {
let user = database.find(u => u.id === id);
if (user) {
return user;
}
return null; // Explicitly signals: "I searched, but found nothing"
}
let result = findUser(999);
if (result === null) {
console.log("User not found");
}
null is a value you assign intentionally. If a variable is null, it means a developer put it there.
undefined: Uninitialized and Missing Values
undefined means a value has not been assigned. It is JavaScript's way of saying "this exists, but nothing has been put here yet."
let age;
console.log(age); // undefined -> declared but never assigned
console.log(typeof age); // "undefined"
Where undefined Appears
JavaScript returns undefined in several situations:
// 1. Declared but uninitialized variables
let name;
console.log(name); // undefined
// 2. Missing function parameters
function greet(name) {
console.log(name);
}
greet(); // undefined -> no argument was passed
// 3. Functions that don't return a value
function doSomething() {
let x = 1 + 2;
// no return statement
}
console.log(doSomething()); // undefined
// 4. Accessing non-existent object properties
let user = { name: "Alice" };
console.log(user.age); // undefined -> property doesn't exist
// 5. Array elements that don't exist
let arr = [1, 2, 3];
console.log(arr[10]); // undefined -> index out of bounds
null vs. undefined: The Difference
| Aspect | null | undefined |
|---|---|---|
| Meaning | Intentional empty value | Value not yet assigned |
| Set by | The developer, deliberately | JavaScript, automatically |
| Use case | "I checked, and there's nothing here" | "This hasn't been initialized" |
typeof | "object" (bug) | "undefined" |
| In arithmetic | Converts to 0 | Converts to NaN |
console.log(null + 5); // 5 (null → 0)
console.log(undefined + 5); // NaN (undefined → NaN)
console.log(null == undefined); // true (loose equality)
console.log(null === undefined); // false (strict equality, different types)
symbol: Unique Identifiers (Introduction)
Symbol is a primitive type introduced in ES6 that creates guaranteed unique identifiers. Even if you create two symbols with the same description, they are different values.
let id1 = Symbol("id");
let id2 = Symbol("id");
console.log(id1 === id2); // false (every Symbol is unique)
console.log(typeof id1); // "symbol"
Symbols are primarily used as unique object property keys that do not conflict with other properties and are not visible in normal iteration:
let id = Symbol("userId");
let user = {
name: "Alice",
[id]: 12345 // Symbol as a property key
};
console.log(user.name); // "Alice"
console.log(user[id]); // 12345
// Symbols are hidden from normal enumeration
console.log(Object.keys(user)); // ["name"] (symbol key not listed)
Symbols are an advanced topic. They are used for creating hidden properties, implementing well-known protocols (like making objects iterable), and avoiding property name collisions. We cover them in full detail in a dedicated chapter later in the course.
object: The Non-Primitive Type (Preview)
The object type is the only non-primitive type in JavaScript. While primitives hold a single value, objects can hold collections of values and more complex entities.
// Object literal
let user = {
name: "Alice",
age: 30,
isAdmin: false
};
// Array (a special type of object)
let colors = ["red", "green", "blue"];
// Function (also an object)
function greet(name) {
return `Hello, ${name}!`;
}
// Date (built-in object)
let now = new Date();
console.log(typeof user); // "object"
console.log(typeof colors); // "object"
console.log(typeof greet); // "function" (special case, technically an object)
console.log(typeof now); // "object"
Objects are covered extensively in their own module. The key point for now is that everything that is not a primitive is an object, including arrays, functions, dates, regular expressions, and errors.
The typeof Operator and Its Quirks
The typeof operator returns a string indicating the type of a value. It is the primary way to check types at runtime.
console.log(typeof 42); // "number"
console.log(typeof 3.14); // "number"
console.log(typeof NaN); // "number"
console.log(typeof Infinity); // "number"
console.log(typeof 42n); // "bigint"
console.log(typeof "hello"); // "string"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof Symbol("id")); // "symbol"
console.log(typeof {}); // "object"
console.log(typeof []); // "object" (arrays are objects!)
console.log(typeof null); // "object" (this is a BUG)
console.log(typeof function(){}); // "function" (special case)
The typeof null Bug
The most famous typeof quirk:
console.log(typeof null); // "object" (this is WRONG)
null is not an object. This is a bug from the very first version of JavaScript (1995) that was never fixed because changing it would break too much existing code. The null type is its own primitive type.
To check for null, compare directly instead of using typeof:
let value = null;
// WRONG: doesn't work for null
if (typeof value === "object") {
// This runs for null too. Misleading!
}
// CORRECT: check for null explicitly
if (value === null) {
console.log("Value is null");
}
// SAFE: check for object that is not null
if (value !== null && typeof value === "object") {
console.log("Value is a real object");
}
Checking for Arrays
Since typeof [] returns "object", you need Array.isArray() to check if something is an array:
let items = [1, 2, 3];
let user = { name: "Alice" };
console.log(typeof items); // "object" (not helpful!)
console.log(typeof user); // "object" (same result!)
console.log(Array.isArray(items)); // true
console.log(Array.isArray(user)); // false
Complete typeof Results Table
| Value | typeof Result | Notes |
|---|---|---|
42 | "number" | |
NaN | "number" | NaN is technically a number |
42n | "bigint" | |
"hello" | "string" | |
true | "boolean" | |
undefined | "undefined" | |
Symbol() | "symbol" | |
null | "object" | Bug! Should be "null" |
{} | "object" | |
[] | "object" | Use Array.isArray() instead |
function(){} | "function" | Functions are objects, but get special treatment |
new Date() | "object" |
Primitive vs. Reference Types: Understanding Memory
This is one of the most important concepts in JavaScript. Primitives and objects are stored and copied differently, and misunderstanding this leads to some of the most common bugs.
Primitives: Stored by Value
When you assign a primitive value to a variable, the actual value is copied:
let a = 10;
let b = a; // The VALUE 10 is copied to b
b = 20; // Changing b does NOT affect a
console.log(a); // 10 (unchanged)
console.log(b); // 20
Each variable has its own independent copy of the value. Changing one has no effect on the other.
let greeting = "Hello";
let copy = greeting;
copy = "World";
console.log(greeting); // "Hello" (unchanged)
console.log(copy); // "World"
Objects: Stored by Reference
When you assign an object to a variable, the variable does not contain the object itself. It contains a reference (a pointer, an address) to where the object lives in memory:
let user = { name: "Alice", age: 30 };
let admin = user; // The REFERENCE is copied, not the object
admin.name = "Bob"; // Modifying through 'admin'...
console.log(user.name); // "Bob" (user is affected too!)
console.log(admin.name); // "Bob"
// Both variables point to the SAME object in memory
Visualize it like this:
user ──────┐
├──→ { name: "Bob", age: 30 } (one object in memory)
admin ──────┘
Comparing Primitives vs. Objects
Primitives are compared by value, objects are compared by reference:
// Primitives: compared by value
let x = "hello";
let y = "hello";
console.log(x === y); // true (same value)
// Objects: compared by reference
let obj1 = { name: "Alice" };
let obj2 = { name: "Alice" };
console.log(obj1 === obj2); // false (different objects in memory!)
let obj3 = obj1;
console.log(obj1 === obj3); // true (same reference)
Even though obj1 and obj2 contain identical data, they are different objects stored at different memory locations. The === operator checks whether both variables point to the same object, not whether the objects have the same contents.
Arrays Are Objects
The same reference behavior applies to arrays:
let original = [1, 2, 3];
let copy = original; // Reference copied, not the array
copy.push(4);
console.log(original); // [1, 2, 3, 4] (original is modified!)
console.log(copy); // [1, 2, 3, 4]
To create an independent copy of an array, use the spread operator or slice():
let original = [1, 2, 3];
let trueCopy = [...original]; // Spread creates a new array
trueCopy.push(4);
console.log(original); // [1, 2, 3] (unchanged)
console.log(trueCopy); // [1, 2, 3, 4]
Common Mistake: Confusing null and undefined
Mistake 1: Using undefined as an Intentional Empty Value
// BAD: using undefined to "reset" a variable
let currentUser = getUser();
// later...
currentUser = undefined; // Don't do this
Fix: Use null to intentionally indicate "no value":
// GOOD: null clearly communicates intent
let currentUser = getUser();
// later...
currentUser = null; // Explicitly set to "no user"
undefined should be reserved for JavaScript's own use (uninitialized variables, missing parameters). When you want to set something to empty, use null.
Mistake 2: Checking for Both null and undefined Incorrectly
let value = null;
// Overly verbose: checking each separately
if (value === null || value === undefined) {
console.log("No value");
}
// Simpler: loose equality covers both
if (value == null) {
console.log("No value"); // Catches both null AND undefined
}
This is one of the FEW acceptable uses of == instead of ===:
null == undefinedistruenull == 0isfalsenull == ""isfalsenull == falseisfalse
It ONLY matches null and undefined, nothing else
Mistake 3: Not Checking for null Before Accessing Properties
let user = null;
// This crashes
console.log(user.name); // TypeError: Cannot read properties of null
// Always check first
if (user !== null) {
console.log(user.name);
}
// Or use optional chaining (ES2020)
console.log(user?.name); // undefined (no error)
Mistake 4: Assuming typeof Detects null
let value = null;
if (typeof value === "null") {
// This NEVER runs: typeof null returns "object", not "null"
}
// Correct check
if (value === null) {
console.log("Value is null");
}
Mistake 5: Confusing Falsy With null/undefined
let count = 0;
let name = "";
// BAD: this treats 0 and "" the same as null/undefined
if (!count) {
console.log("No count"); // Runs! But 0 might be a valid count
}
if (!name) {
console.log("No name"); // Runs! But "" might be an intentional empty string
}
// BETTER: check specifically for null/undefined when 0 or "" are valid values
if (count == null) {
console.log("Count is null or undefined"); // Does NOT run: 0 is not null
}
if (name == null) {
console.log("Name is null or undefined"); // Does NOT run: "" is not null
}
This becomes especially important with the nullish coalescing operator ??, which you will learn about in the logical operators chapter.
Summary
JavaScript has eight data types, and understanding each one is foundational to everything else you will learn:
numberhandles integers, floats,Infinity, andNaN. Be aware of floating-point precision issues and the safe integer range.bigintrepresents integers beyondNumber.MAX_SAFE_INTEGER. Use thensuffix to create BigInt literals.stringrepresents text. Use template literals (backticks) for interpolation and multi-line strings. Strings are immutable.booleanhas two values:trueandfalse. Every value in JavaScript is either truthy or falsy. Memorize the eight falsy values.nullis an intentional empty value assigned by the developer.undefinedmeans a value has not been assigned. It is JavaScript's default for uninitialized variables, missing parameters, and non-existent properties.symbolcreates unique identifiers, primarily used as hidden object property keys.objectis the only non-primitive type. Arrays, functions, dates, and everything that is not a primitive are objects.typeofchecks the type of a value, but has quirks:typeof null === "object"(bug) andtypeof [] === "object"(useArray.isArray()).- Primitives are copied by value, meaning each variable gets its own independent copy. Objects are copied by reference, meaning multiple variables can point to the same object in memory.
- Use
nullfor intentional empty values, and let JavaScript useundefinedfor uninitialized state.
With a solid grasp of data types, you are ready to learn about type conversions, where JavaScript's dynamic nature truly reveals its quirks and powers.