JavaScript Data Types
Explore JavaScript's seven primitive types and objects, understand typeof, type coercion, and learn how to check types reliably.
JavaScript’s Type System
JavaScript is dynamically typed — variables don’t have fixed types, values do. You can assign a number to a variable and then assign a string to the same variable. This flexibility is powerful but requires understanding how types work to avoid bugs.
JavaScript has 7 primitive types and one complex type: Object. Primitives are immutable and compared by value. Objects are mutable and compared by reference. Knowing the difference between these two categories explains a large class of surprising JavaScript behaviors.
The 7 Primitive Types
1. String
Text data is one of the most common types you’ll work with — user input, labels, messages, API responses. Strings in JavaScript are immutable: every method that looks like it modifies a string actually returns a new one. They can be enclosed in single quotes, double quotes, or backticks.
const single = 'Hello';
const double = "World";
const template = `Hello, ${single}!`; // "Hello, Hello!"
// Strings are immutable — methods return new strings
const upper = "javascript".toUpperCase(); // "JAVASCRIPT"
const words = "one two three".split(" "); // ["one", "two", "three"]
Template literals (backticks) support multi-line strings and expression interpolation — prefer them for anything beyond simple strings.
2. Number
JavaScript has a single number type for both integers and floats, based on the IEEE 754 double-precision standard. This is convenient — you never have to choose between int and float — but it means all numbers share the same precision limits, and floating-point arithmetic has well-known rounding quirks you need to be aware of.
const integer = 42;
const float = 3.14;
const negative = -100;
const huge = 1_000_000; // numeric separator for readability
// Special values
console.log(1 / 0); // Infinity
console.log(-1 / 0); // -Infinity
console.log(0 / 0); // NaN
// Floating point precision — a classic gotcha
console.log(0.1 + 0.2); // 0.30000000000000004 ← not 0.3
console.log(Math.round((0.1 + 0.2) * 10) / 10); // 0.3 ← correct
Safe integer range: Number.MIN_SAFE_INTEGER (-2⁵³+1) to Number.MAX_SAFE_INTEGER (2⁵³-1). Beyond this range, use BigInt.
3. BigInt
Regular Number can’t reliably represent integers beyond 2⁵³-1. This matters for cryptographic operations, high-precision timestamps, and database IDs from systems that use 64-bit integers. BigInt solves this by supporting arbitrary-precision integers, at the cost of not being interchangeable with Number.
const big = 9007199254740991n; // append 'n' to create a BigInt literal
const sum = big + 1n; // 9007199254740992n
// Cannot mix with Number without explicit conversion
// big + 1; // TypeError: Cannot mix BigInt and other types
BigInt(42) + big; // fine — explicit conversion
BigInt is used in cryptography, database IDs, and anywhere precise large integer arithmetic matters.
4. Boolean
Booleans represent truth and are the type that powers all conditional logic. Every if statement, every while condition, every && and || expression ultimately works with boolean values — or values that get coerced to them. Knowing which values JavaScript considers “truthy” and which are “falsy” is essential for writing correct conditionals.
const isLoggedIn = true;
const hasPermission = false;
// Any value can be coerced to boolean
Boolean(0); // false
Boolean(""); // false
Boolean(null); // false
Boolean(undefined); // false
Boolean(NaN); // false
// Everything else is true ("truthy")
Boolean(1); // true
Boolean("hello"); // true
Boolean([]); // true ← empty array is truthy!
Boolean({}); // true ← empty object is truthy!
The six falsy values — 0, "", null, undefined, NaN, and false — are worth memorizing. Everything else is truthy.
5. null
null is an intentional, explicit absence of value. You assign it when you want to clearly communicate “there is no object here” — for example, when a user is not logged in, or when a search returns no result. Unlike undefined, which the language assigns automatically, null is always set by the programmer on purpose.
let currentUser = null; // not logged in
function findUser(id) {
const user = db.query(id);
return user ?? null; // explicitly null if not found
}
6. undefined
undefined is the language’s default “no value.” It’s what a variable holds before you assign it, what a function returns when it has no return statement, and what you get when you access a property that doesn’t exist on an object. You rarely assign undefined yourself — that’s what null is for.
let x;
console.log(x); // undefined — declared but not assigned
function greet(name) {
console.log(`Hello, ${name}`);
// no return statement — implicitly returns undefined
}
console.log(greet("Alice")); // logs "Hello, Alice", then undefined
const obj = { a: 1 };
console.log(obj.b); // undefined — property doesn't exist
7. Symbol
Symbols are unique, immutable primitives used primarily as object property keys. Their key property is guaranteed uniqueness: two Symbols with the same description are never equal. This makes them ideal for adding metadata or extension points to objects without risking name collisions with other code.
const id1 = Symbol("id");
const id2 = Symbol("id");
console.log(id1 === id2); // false — every Symbol is unique
const user = {
name: "Alice",
[id1]: 12345 // Symbol as a property key — won't clash with "id" string key
};
// Symbol keys don't appear in for...in or JSON.stringify
console.log(Object.keys(user)); // ["name"]
Symbols are used extensively in the JavaScript engine itself (e.g., Symbol.iterator, Symbol.toPrimitive) and in library code to add non-enumerable behavior.
Object
Everything that isn’t a primitive is an Object — including arrays, functions, dates, maps, and sets. Objects are mutable, reference types, meaning two variables can point to the same underlying object. When you pass an object to a function or assign it to another variable, you’re copying the reference, not the object itself.
const person = {
name: "Alice",
age: 30,
greet() {
return `Hi, I'm ${this.name}`;
}
};
const colors = ["red", "green", "blue"]; // Array — a special kind of object
typeof colors; // "object"
Array.isArray(colors); // true — use this to distinguish arrays from plain objects
The typeof Operator
typeof is the quickest way to inspect what type a value has at runtime. It returns a string. Most of the time it works exactly as you’d expect, but there are two famous exceptions worth knowing before you hit them in production.
typeof "hello" // "string"
typeof 42 // "number"
typeof 42n // "bigint"
typeof true // "boolean"
typeof undefined // "undefined"
typeof Symbol() // "symbol"
typeof {} // "object"
typeof [] // "object" ← arrays are objects; use Array.isArray instead
typeof null // "object" ← famous historical bug; check with === null
typeof function(){} // "function" ← functions are objects, but get their own typeof result
Checking types reliably
Because typeof has those two quirks, certain type checks need a more specific approach:
// null check — the only reliable way
value === null
// Array check — typeof gives "object", so use this instead
Array.isArray(value)
// NaN check — NaN !== NaN, so typeof and == both fail
Number.isNaN(NaN) // true
Number.isNaN("hi") // false (unlike the global isNaN which coerces first)
// Object check that excludes null
typeof value === "object" && value !== null
// Instance check for built-in types
value instanceof Date
value instanceof Map
Type Coercion
JavaScript automatically converts types in certain operations. This is called implicit coercion and it’s a source of many surprising behaviors — particularly around the + operator, which doubles as string concatenation, and the loose equality operator ==, which converts types before comparing.
// + with a string converts the other operand to string
"5" + 3 // "53" (number 3 becomes "3")
"5" + true // "5true"
// Arithmetic operators (other than +) convert strings to numbers
"5" - 3 // 2
"5" * "2" // 10
"5" ** 2 // 25
// Equality coercion (== coerces, === does not)
0 == false // true ← 0 and false both coerce to 0
0 === false // false ← strict: different types, not equal
"" == false // true
null == undefined // true ← the one useful == behavior
null === undefined // false
The safest rule: always use === for equality checks and convert types explicitly when you need to:
const input = "42";
// Explicit conversion — clear intent, no surprises
const num = Number(input); // 42
const num2 = parseInt(input, 10); // 42 — always pass radix 10
const str = String(42); // "42"
const bool = Boolean(input); // true
Checking for null and undefined Together
A common pattern is to check whether a value is either null or undefined — collectively called nullish values. Modern JavaScript has dedicated syntax for this that makes the intent very clear.
// Verbose — works but repetitive
if (value === null || value === undefined) { /* ... */ }
// Concise — the one acceptable use of == in modern code
if (value == null) { /* catches both null and undefined */ }
// Modern — nullish coalescing operator
const display = value ?? "default"; // uses "default" if value is null or undefined
// unlike ||, it keeps 0, "", and false as valid values
What’s Next
The next tutorial covers operators — arithmetic, comparison, logical, and the modern ?? and ?. operators that make null-safe code much cleaner.