Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

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.

  • Beginner
  • 14 minutes
  • Examples run with Node.js 24.21.0 and quickjs 0.32.0
  • By MySmartCoPilot
Free lesson, Pro answers: 14 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

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:

typeof for fifteen values JavaScript · type-table.mjs
// 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

Each of the seven primitive types maps to its own typeof string; null maps to object, and functions and classes to function.What typeof returns for each typethe value’s typetypeof givesundefined'undefined'boolean'boolean'number'number'bigint'bigint'string'string'symbol'symbol'nullobject'object'function or class'function'null: the old quirkthe seven primitive typesobjects (arrays and dates too)

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:

Comparing and copying primitives and objects JavaScript · values-and-references.mjs
// 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

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:

Tests that typeof cannot do JavaScript · telling-apart.mjs
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

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.

Methods and properties of primitives JavaScript · methods-on-primitives.mjs
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

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:

Reading a property of null, uncaught JavaScript · null-property.mjs
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:

A Boolean object that wraps false JavaScript · wrapper-trap.mjs
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

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.

JavaScript Linter The Strict rule set reports new Boolean, new String and new Number calls (no-new-wrappers) before they reach an if statement.

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' for null, where typeof says 'object';
  • 'array' for an array, where typeof also says 'object';
  • otherwise exactly what typeof returns: '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.

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.

  1. Question 1 of 5 What does typeof null return?

    Choose one answer.

    Show the answer to question 1

    Answer: 'object'

    typeof null is 'object', a mistake from the first implementation that the standard keeps because existing code depends on it. null is still a primitive of its own type; test for it with value === null.

  2. Question 2 of 5 Which of these values are primitives?

    Choose every answer that is right.

    Show the answer to question 2

    Answer:

    • Symbol('id')
    • 'hello'
    • null
    • 42n

    Strings, bigints, symbols and null are four of the seven primitive types. An array, a String object made with new and a function are objects: typeof says 'object' for the first two and 'function' for the third.

  3. Question 3 of 5 What does this program print?

    Read the code, then choose one answer.

    const flag = new Boolean(false);
    if (flag) {
      console.log('yes');
    } else {
      console.log('no');
    }
    Show the answer to question 3

    Answer: yes

    new Boolean(false) creates an object that wraps false, and every ordinary object is truthy, so the if branch runs. Boolean(false) without new would return the primitive false, and the else branch would run.

  4. Question 4 of 5 What does this program print?

    What does this program print? Choose one answer.

    // 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'));
    Show the answer to question 4

    Answer: it prints

    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

    Primitives compare and copy as values: the two strings are equal and changing discounted leaves fare at 120. Objects compare by identity, so two objects with the same contents differ while two names for one object are equal. Every symbol is unique, whatever its description.

  5. Question 5 of 5 Which test tells you reliably that a value is an array?

    Choose one answer.

    Show the answer to question 5

    Answer: Array.isArray(value)

    typeof never returns 'array': for an array it says 'object', as it does for every other object. A length property proves nothing either, since strings and many objects have one. Array.isArray checks for a real array.

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.

  1. 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.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. 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?

    Model answer, Pro. First step and follow-ups

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.
  • typeof returns a string for each type, except that typeof null is 'object' and callable objects give 'function'.
  • Test for null with value === null, for arrays with Array.isArray, and for NaN with Number.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 String or new Number: the wrapper is an object, and objects are truthy.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.