JavaScript (ES2026) Module 2 – Values, types and operators
Primitive types and typeof
JavaScript's seven primitive types and objects, what typeof returns for each, its null and function quirks, and why you can call methods on a primitive.
What you will learn
- List JavaScript's seven primitive types and the object type
- Use typeof correctly, including its two historical quirks
- Explain why primitives behave like objects when you call methods on them
Before you start
What you’ll learn · Before you start
What you will learn
- List JavaScript's seven primitive types and the object type
- Use typeof correctly, including its two historical quirks
- Explain why primitives behave like objects when you call methods on them
Before you start
On this page
Every value in JavaScript has one of eight types. Seven of them are primitive types: undefined, null,
boolean, number, bigint, string and symbol. A primitive is a simple value with no parts that can change.
Everything else is an object, and that includes arrays, dates and functions. The typeof operator tells you which
type a value has, as a string such as 'number', with two well-known surprises: typeof null is 'object', and a
function, which is an object, reports 'function'. This lesson shows what typeof returns for every kind of value,
how to tell null and arrays apart reliably, and why 'abc'.toUpperCase() works although a string is not an
object.
Seven primitives and one object type
| Type | Example values | Used for |
|---|---|---|
| undefined | undefined |
a variable or property that has no value yet |
| null | null |
“deliberately no value”, set by your code |
| boolean | true, false |
yes-or-no answers |
| number | 42, 3.14, NaN, Infinity |
integers and decimals, all stored the same way |
| bigint | 42n, 2n ** 64n |
whole numbers of any size |
| string | 'hello', `fare: ${fare}` |
text |
| symbol | Symbol('id') |
unique keys that cannot clash with other keys |
| object | { seats: 72 }, [1, 2], functions |
everything else: values with properties |
Asking typeof about one value of each kind, and a few more, shows how it maps the eight types onto its answers:
// What typeof says about fifteen values: the code that made each value, then the typeof result.
const values = [
['undefined', undefined],
['null', null],
['true', true],
['42', 42],
['3.14', 3.14],
['NaN', NaN],
['42n', 42n],
["'hello'", 'hello'],
["''", ''],
["Symbol('id')", Symbol('id')],
['{ seats: 72 }', { seats: 72 }],
['[1, 2, 3]', [1, 2, 3]],
['new Date(0)', new Date(0)],
['function () {}', function () {}],
['class Train {}', class Train {}],
];
for (const [code, value] of values) {
console.log(code.padEnd(16), typeof value);
} Output
undefined undefined
null object
true boolean
42 number
3.14 number
NaN number
42n bigint
'hello' string
'' string
Symbol('id') symbol
{ seats: 72 } object
[1, 2, 3] object
new Date(0) object
function () {} function
class Train {} function
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node type-table.mjs
Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
typeof's answer for every type, with its two quirks
Text description of the diagram
The diagram has two columns: the type of a value on the left and the string typeof returns on the right, joined by arrows.
- The seven primitive types, shown in blue: undefined gives 'undefined', boolean gives 'boolean', number gives 'number', bigint gives 'bigint', string gives 'string' and symbol gives 'symbol'.
- The seventh primitive, null, has no string of its own: a dashed arrow, labelled "null: the old quirk", leads to 'object'.
- Objects, shown in grey: an ordinary object, which includes arrays and dates, gives 'object'; an object that can be called, a function or a class, gives 'function'.
So typeof tells the primitives apart except null, groups every other object under 'object', and singles out functions.
Each primitive type has its own answer, except null. All the objects, plain objects, arrays and dates alike, are
'object', except the ones that can be called: functions and classes are 'function'. NaN, “not a number”, is
still of type number, the value a failed calculation gives back; the next lesson covers it.
Values and identities
The line between primitives and objects shows up in how they are copied and compared. A primitive is copied as a value, and two primitives are equal when they hold the same value. An object is copied as a reference to the same object, and two objects are equal only when they are the very same object:
// Primitives are compared, and copied, as values.
const a = 'express';
const b = 'exp' + 'ress';
console.log('two equal strings:', a === b);
const fare = 120;
let discounted = fare; // copies the number 120
discounted = discounted - 30;
console.log('fare:', fare, '| discounted:', discounted);
// Objects are compared by identity: are these two names the same object?
const first = { coach: 'S1' };
const second = { coach: 'S1' };
const alias = first;
console.log('two objects with the same contents:', first === second);
console.log('two names for one object:', first === alias);
// Every symbol is unique, even when two have the same description.
// eslint-disable-next-line no-self-compare -- two separate Symbol() calls, which is the point
console.log('two symbols both called id:', Symbol('id') === Symbol('id')); Output
two equal strings: true fare: 120 | discounted: 90 two objects with the same contents: false two names for one object: true two symbols both called id: false
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node values-and-references.mjs
Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
Two separately built strings with the same characters are equal, and changing discounted leaves fare alone,
because the assignment copied the number. Two objects with identical contents are not equal: === asks whether both
sides are one object. Symbols are the odd primitive: each call to Symbol() creates a value that equals no other,
which is exactly what makes them useful as keys. A primitive also never changes. 'abc'.toUpperCase() returns a new
string, and x + 1 a new number, while the original stays as it was.
The two quirks of typeof
typeof null is ‘object’
null is a primitive with a type of its own, yet typeof null returns 'object'. This is a mistake from the
first implementation of the language, where a type tag of zero marked objects and null happened to be stored as
zero too. A change to return 'null' was proposed and turned down, because existing code depends on the old answer,
so the standard keeps it. Test for null directly with value === null, which no other value passes.
Functions are objects, but typeof says ‘function’
A function is an object: you can give it properties and pass it around like any other object. typeof makes an
exception and returns 'function' for any object that can be called, which includes classes and built-in functions
such as Math.max. Arrays get no exception. Browsers have one more special case, for compatibility with very old
pages: typeof document.all is 'undefined', although document.all is an object.
typeof on a name that was never declared
typeof also accepts a name that was never declared: where reading the name would throw a ReferenceError, it
returns 'undefined', which is why code checks for a global such as window with typeof window === 'undefined'. The exception is a let or const name in its temporal dead zone, which throws, as
the lesson on let and const shows.
Telling values apart
Since typeof lumps null, arrays and objects together, real code combines it with a few dedicated tests:
const list = [3, 1, 2];
const nothing = null;
console.log('typeof list:', typeof list, '| Array.isArray(list):', Array.isArray(list));
console.log('typeof nothing:', typeof nothing, '| nothing === null:', nothing === null);
console.log('typeof NaN:', typeof NaN, '| Number.isNaN(NaN):', Number.isNaN(NaN));
console.log('typeof Math.max:', typeof Math.max, '| typeof class {}:', typeof class {});
// The internal tag of an object names its kind more precisely than typeof can.
const tag = (value) => Object.prototype.toString.call(value);
console.log(tag(new Date(0)), tag(list), tag(nothing), tag({})); Output
typeof list: object | Array.isArray(list): true
typeof nothing: object | nothing === null: true
typeof NaN: number | Number.isNaN(NaN): true
typeof Math.max: function | typeof class {}: function
[object Date] [object Array] [object Null] [object Object]
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node telling-apart.mjs
Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
| To find out whether a value is | Use | Not |
|---|---|---|
null |
value === null |
typeof value === 'object', which is also true for every object |
| an array | Array.isArray(value) |
typeof, which says 'object' |
| a function or class | typeof value === 'function' |
value instanceof Function, which fails for functions from another frame |
a number that is not NaN |
typeof value === 'number' && !Number.isNaN(value) |
typeof alone, since NaN is a number |
| a date, a map or another built-in object | Object.prototype.toString.call(value) |
typeof, which says 'object' for all of them |
Object.prototype.toString reads an internal tag that built-in objects carry, [object Date], [object Array] or
[object Null], which makes it a handy tool for logging and debugging. The exercise below combines the tests into one
function.
Why a string has methods
'rajdhani express'.toUpperCase() calls a method on a primitive, and primitives have no properties of their own.
It works because reading a property of a primitive makes the engine wrap the value in a temporary object of the
matching kind, a String object here, whose methods come from String.prototype. The method runs, and the
temporary object is thrown away. Numbers, booleans, bigints and symbols are wrapped the same way.
const train = 'rajdhani express';
console.log(train.toUpperCase()); // a method of String.prototype, called on a primitive
console.log(train.length, (3.14159).toFixed(2), (255).toString(16), true.toString());
console.log('typeof train afterwards:', typeof train);
try {
train.platform = 4; // a property on a primitive would live on a temporary object
} catch (error) {
console.log('setting a property on a string:', error.name);
}
console.log('train.platform:', train.platform);
try {
const missing = null;
console.log(missing.length); // null and undefined have no properties at all
} catch (error) {
console.log('reading a property of null:', error.name);
} Output
RAJDHANI EXPRESS 16 3.14 ff true typeof train afterwards: string setting a property on a string: TypeError train.platform: undefined reading a property of null: TypeError
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node methods-on-primitives.mjs
Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
The temporary object explains the rest of the output. Setting train.platform would store the property on a
wrapper that disappears at once, so in strict mode, which every module uses, the assignment throws a TypeError, and
reading the property afterwards gives undefined. null and undefined have no wrapper type, so asking either for
a property throws a TypeError. Without a try block the program stops, and the message Node.js prints is one you
will meet often:
const booking = { passenger: null };
console.log('passenger name length:', booking.passenger.length); Output (exit status 1)
Printed as an error (standard error)
null-property.mjs:2
console.log('passenger name length:', booking.passenger.length);
^
TypeError: Cannot read properties of null (reading 'length')
at null-property.mjs:2:57
at ModuleJob.run (node:internal/modules/esm/module_job:561:25)
at async node:internal/modules/esm/loader:647:26
at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)
Node.js v24.21.0
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node null-property.mjs
The message names the property the code tried to read, length, and the caret under the copied source line points
at the spot. The fix is to find out why the value is null there, or to check for null before reading it.
Never create wrapper objects yourself
new Boolean, new String and new Number build the wrapper objects deliberately, and they behave like objects,
not like the values inside them:
const hidden = new Boolean(false); // an object that wraps false
if (hidden) {
console.log('the if block ran: an object is truthy, even one that wraps false');
}
console.log('typeof hidden:', typeof hidden, '| typeof false:', typeof false);
// eslint-disable-next-line eqeqeq -- the loose == is the point of this line
console.log('hidden == false:', hidden == false, '| hidden === false:', hidden === false);
const empty = new String('');
console.log("new String('') is truthy:", Boolean(empty), '| typeof:', typeof empty);
// Called without new, Boolean, String and Number convert a value and return a primitive.
console.log(typeof Boolean(0), typeof String(42), typeof Number('7')); Output
the if block ran: an object is truthy, even one that wraps false
typeof hidden: object | typeof false: boolean
hidden == false: true | hidden === false: false
new String('') is truthy: true | typeof: object
boolean string number
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node wrapper-trap.mjs
Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
An object is always truthy (document.all is again the browsers’ lone exception), so the if block runs although the
wrapped value is false, and typeof says 'object'. hidden == false is true only because == unwraps the
object first, while === compares the object with the primitive and says false. Called without new, Boolean(), String() and Number() convert a value and return a
primitive, which is useful; with new they are almost never what you want.
Exercise
Exercise · Easy · JavaScript
Name the type of any value
Write describeType(value) in describe.mjs. It returns a string that names the kind of value it is given, with the two answers that typeof cannot give on its own:
'null'fornull, wheretypeofsays'object';'array'for an array, wheretypeofalso says'object';- otherwise exactly what
typeofreturns:'undefined','boolean','number','bigint','string','symbol','function'or'object'.
For example, describeType(null) is 'null', describeType([1, 2]) is 'array', describeType(class {}) is 'function' and describeType(new Date(0)) is 'object'. The sample tests try about twenty values, among them NaN, a symbol, a boxed string made with new String('x') and an instance of a class.
Starter code · describe.mjs
/**
* The kind of a value: 'null' for null, 'array' for an array, and otherwise what typeof returns
* ('undefined', 'boolean', 'number', 'bigint', 'string', 'symbol', 'function' or 'object').
*/
export function describeType(value) {
// Replace this line with your code.
return typeof value;
} The sample tests · describe.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { describeType } from './describe.mjs';
test("null is 'null'", () => {
assert.equal(describeType(null), 'null');
});
test("arrays are 'array', empty or not", () => {
assert.equal(describeType([]), 'array');
assert.equal(describeType([1, 2]), 'array');
assert.equal(describeType(new Array(3)), 'array');
assert.equal(describeType(Array.from('abc')), 'array');
});
test("functions and classes are 'function'", () => {
assert.equal(describeType(() => 1), 'function');
assert.equal(describeType(function named() {}), 'function');
assert.equal(describeType(class Seat {}), 'function');
assert.equal(describeType(Math.max), 'function');
});
test("every other object is 'object'", () => {
class Seat {}
assert.equal(describeType({}), 'object');
assert.equal(describeType(new Date(0)), 'object');
assert.equal(describeType(new Map()), 'object');
assert.equal(describeType(new Seat()), 'object');
assert.equal(describeType(new String('x')), 'object');
assert.equal(describeType(Object.create(null)), 'object');
});
test('primitives other than null give their typeof', () => {
assert.equal(describeType(undefined), 'undefined');
assert.equal(describeType(false), 'boolean');
assert.equal(describeType(0), 'number');
assert.equal(describeType(NaN), 'number');
assert.equal(describeType(10n), 'bigint');
assert.equal(describeType(''), 'string');
assert.equal(describeType(Symbol('id')), 'symbol');
}); A hint
Test the two special cases first and return early: value === null is the only reliable test for null, and Array.isArray(value) is the test for arrays. Everything else is typeof value.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Check yourself
5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
Interview questions
Why does typeof null return ‘object’? It is a bug from the first version of JavaScript that was never fixed. That
implementation stored values with a type tag, the tag for objects was zero, and null was represented as a zero
value, so the type check read it as an object. A proposal to make typeof null return 'null' was rejected because
existing pages relied on the old answer, and the standard still specifies 'object'. null is nevertheless a
primitive of its own type, Null. To test for it, compare directly with value === null; to test for a real object,
use value !== null && typeof value === 'object', or typeof value === 'function' for callable ones.
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Mid to senior
Write typeName(value) for a logging library: it returns 'null', 'array', 'date', 'map', 'regexp', 'function', the class name for class instances and 'object' for plain objects. Explain why typeof alone is not enough, and how Symbol.toStringTag can mislead your checks.
-
Fresher to mid
A sign-up API accepts the age field when typeof body.age === 'number', yet testers still create accounts with impossible ages. Which values pass this check but are not valid ages, and how would you write the check, including for ages that a form sends as text?
Key takeaways
- JavaScript has seven primitive types (undefined, null, boolean, number, bigint, string, symbol) and objects; arrays, dates and functions are objects.
- Primitives are compared and copied as values and never change; objects are compared by identity.
typeofreturns a string for each type, except thattypeof nullis'object'and callable objects give'function'.- Test for
nullwithvalue === null, for arrays withArray.isArray, and forNaNwithNumber.isNaN. - Calling a method on a primitive works through a temporary wrapper object; in a module, setting a property on a primitive throws.
- Never write
new Boolean,new Stringornew Number: the wrapper is an object, and objects are truthy.
References
- ECMAScript 2026 Language Specification: ECMAScript Language Types (Ecma International)
- ECMAScript 2026 Language Specification: The typeof Operator (Ecma International)
- ECMAScript 2026 Language Specification: ToObject (Ecma International)
- ECMAScript 2026 Language Specification: Array.isArray (Ecma International)
- ECMAScript 2026 Language Specification: Object.prototype.toString (Ecma International)
- ECMAScript 2026 Language Specification: The [[IsHTMLDDA]] Internal Slot (Ecma International)
- typeof (MDN Web Docs)
- JavaScript data types and data structures (MDN Web Docs)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress