JavaScript Data Types Explained: All 8 Types and the `typeof` Quirks
JavaScript has eight data types. Seven of them are primitives — simple, immutable values like numbers and strings. The eighth is objects — everything else, including arrays, functions, dates, maps, and your own classes. Understanding the split is the foundation for understanding the rest of the language: how variables behave, how equality works, how functions pass arguments, and why typeof null === 'object' is one of the longest-running bugs in software history.
This lesson walks through every type, what makes primitives different from objects, and the typeof rules you need to know to read any JavaScript codebase.
The eight types at a glance #
| Category | Types |
|---|---|
| Primitives (7) | undefined, null, boolean, number, bigint, string, symbol |
| Objects (1) | object (covers Array, Function, Date, Map, Set, class instances, …) |
That's it. Every value you'll ever encounter in JavaScript is one of those eight categories. There is no separate "array" type at the language level — arrays are objects. There is no "function" type — functions are objects with a [[Call]] internal slot. The fancy stuff is layered on top of object.
Primitives: simple, immutable, passed by value #
A primitive is a single, indivisible value. It cannot have properties. It cannot be modified — only replaced.
undefined #
The value a variable has before you assign one. Also what a function returns if you don't explicitly return anything.
let x;
console.log(x); // undefined
function noop() {}
console.log(noop()); // undefined
null #
A value that explicitly means "nothing." You assign it when you want to represent an intentional absence.
let currentUser = null; // explicitly no user yet
The distinction between undefined (forgotten / never set) and null (intentionally empty) is a convention, not a hard rule. Most teams use undefined for "missing" and null for "deliberately empty." Modern JS leans on undefined more — null is largely a holdover from the language's earliest days.
boolean #
true or false. The whole story.
const isReady = true;
const hasErrors = false;
number #
The one numeric type for most cases. It's a 64-bit IEEE 754 floating-point value, the same format used in most languages for double.
const pi = 3.14;
const answer = 42;
const tiny = 1e-10;
const huge = 1.7976931348623157e+308; // Number.MAX_VALUE
This means there is no separate integer type. 1 and 1.0 are the same value. It also means floating-point gotchas apply: 0.1 + 0.2 is 0.30000000000000004, not 0.3. We cover that in Module 5.
Three special numeric values to know:
NaN // "Not a Number" — result of e.g. 0/0 or Number('abc')
Infinity // overflow or 1/0
-Infinity // negative overflow
NaN has one infamous property: it is the only JavaScript value not equal to itself.
NaN === NaN; // false
Number.isNaN(NaN); // true — always use this to check for NaN
bigint #
For integers larger than 2^53 - 1 (the safe integer range of number). Created with the n suffix or BigInt(...).
const huge = 9007199254740993n;
const alsoHuge = BigInt('9007199254740993');
console.log(huge + 1n); // 9007199254740994n
You cannot mix bigint and number in arithmetic — it throws.
1n + 1; // TypeError: Cannot mix BigInt and other types
Useful for IDs from large-scale systems, financial calculations, cryptography. Not needed in most day-to-day code.
string #
A sequence of UTF-16 code units. Created with single quotes, double quotes, or backticks (the latter give you template literals with ${interpolation}).
const a = 'hello';
const b = "hello";
const c = `hello, ${a}`;
Strings are immutable. s.toUpperCase() returns a new string; it does not change s.
const s = 'hi';
s.toUpperCase(); // 'HI'
console.log(s); // 'hi' — unchanged
symbol #
A unique, opaque identifier. Useful for property keys when you need them to be guaranteed unique (no collisions with other code).
const id = Symbol('user-id');
const id2 = Symbol('user-id');
console.log(id === id2); // false — every Symbol() call is unique
We go deep on symbols (well-known symbols, the global registry, why iterators use them) in Module 3.
Objects: composite, mutable, passed by reference #
Everything that isn't a primitive is an object. Arrays, functions, dates, regexes, maps, sets, plain objects, class instances — all object.
const plain = { name: 'Ada' };
const arr = [1, 2, 3];
const fn = () => {};
const date = new Date();
const map = new Map();
typeof plain; // 'object'
typeof arr; // 'object'
typeof fn; // 'function' ← the one exception
typeof date; // 'object'
typeof map; // 'object'
Objects can hold properties. Their contents can be mutated. They are passed by reference — when you pass an object to a function and the function mutates it, the original is mutated.
function addPoints(user, n) {
user.points += n;
}
const u = { points: 0 };
addPoints(u, 5);
console.log(u.points); // 5 — the original object was changed
Compare with primitives, which are passed by value:
function increment(n) {
n = n + 1;
}
let x = 1;
increment(x);
console.log(x); // 1 — unchanged, n was a copy
This distinction is the source of many bugs. Module 3 covers it in detail.
The typeof operator: the practical truth table #
typeof is the most-used way to check what kind of value you have. Its rules look simple but include three notable gotchas.
| Value | typeof returns |
Notes |
|---|---|---|
undefined |
'undefined' |
|
null |
'object' |
The famous bug. Should be 'null'. Can't be fixed without breaking the web. |
true / false |
'boolean' |
|
42, 3.14, NaN, Infinity |
'number' |
Yes, typeof NaN === 'number'. |
9n |
'bigint' |
|
'hi' |
'string' |
|
Symbol() |
'symbol' |
|
function() {} |
'function' |
Special-cased — functions are objects but typeof distinguishes them. |
{}, [], new Date(), etc. |
'object' |
Everything else. |
The three things to remember #
typeof null === 'object'— a 30-year-old bug from JavaScript's first implementation. The early version stored type tags in the lower bits of values;nullwas represented as a null pointer (all-zero bits) which happened to look like the object tag. By the time anyone noticed, too much code on the web depended on it.typeof NaN === 'number'—NaNis part of the IEEE 754 numeric system. It is a number — specifically, a number whose value couldn't be computed. UseNumber.isNaN(x)to check for it.typeofof a function is'function'— even though functions are objects under the hood. This special case exists because checking for callability is so common.
Distinguishing array from plain object #
typeof [] returns 'object'. To check for an actual array, use Array.isArray:
Array.isArray([]); // true
Array.isArray({ length: 0 }); // false
Array.isArray('hi'); // false
Distinguishing null from an object likewise requires extra logic:
function isPlainObject(v) {
return v !== null && typeof v === 'object' && !Array.isArray(v);
}
These helpers come up in almost every codebase. ESLint plugins like unicorn/no-typeof-undefined and TypeScript's type narrowing handle most of the same checks for you in larger projects.
Type conversion is its own topic #
JavaScript implicitly converts between types in many situations: '5' + 1, if (someObject), == comparisons. The rules are subtle and surprising — 1 == '1' is true, [] == ![] is true, and null == undefined is true but null === undefined is false.
We cover all of this in Module 6 (type-coercion-equality). For now, two simple rules will save you 90% of the pain:
- Use
===and!==, never==and!=. The strict operators don't convert types; they just compare. - Convert explicitly when you want to convert.
Number(x),String(x),Boolean(x),parseInt(x, 10). Be the one who decides, not the engine.
A summary you can pin to the wall #
- 8 types total. 7 primitives, 1 object category.
- Primitives are passed by value, immutable, simple.
- Objects are passed by reference, mutable, composite.
typeofreturns one of 8 strings, plus the famousnull → 'object'gotcha and thefunction → 'function'special case.- Always check arrays with
Array.isArray, never withtypeof. - Always check
NaNwithNumber.isNaN, never with===.
Everything else in JavaScript — the equality rules, the call-stack mechanics, the array methods, the class system — is built on top of these eight types.
What's next #
Lesson 1.4 covers operators: arithmetic, comparison, logical, bitwise, nullish coalescing, optional chaining, and the half-dozen modern operators that make JavaScript expression-heavy code surprisingly readable in 2026.
Try it yourself #
The quickest way to feel the primitive-vs-object split is the famous == puzzle. Try predicting the result before you peek:
console.log(typeof null, typeof NaN, typeof []);js_sandboxIt logs object number object.•
typeof null is 'object' — the 30-year-old bug.•
typeof NaN is 'number' — NaN is a numeric value, just one that couldn’t be computed.•
typeof [] is 'object' — arrays are a kind of object; use Array.isArray() to distinguish them.Three values, three lessons, one line of code. That's the type system in action — quirks and all.
Up next in JavaScript
More from this topic
Enjoyed this article?
Get new JavaScript tutorials delivered. No spam — just code-first articles when they ship.


