Primitive types and typeof

Beginner25 min

Name the seven primitive types and the object type, and use typeof while knowing where it gives odd answers.

What you will learn
  • Name JavaScript's seven primitive types
  • Use typeof and recognise its two famous quirks
  • Explain what dynamic and weak typing mean in JavaScript
On this page

Every value in a JavaScript program has a type, and the type decides what you can do with the value. The general ideas, such as what a type is and the difference between static and dynamic typing, are in the fundamentals lesson on values and types. This lesson lists the types JavaScript actually has and the operator that reports them.

The seven primitives and objects

JavaScript has seven primitive types. A primitive is a single, simple value that cannot be changed from the inside. Everything else is an object.

Type Example Holds
undefined undefined A variable that has no value yet
null null A deliberate “no value”
boolean true, false Yes or no
number 42, 3.14, NaN All ordinary numbers, whole or fractional
bigint 10n Whole numbers of any size
string "text" Text
symbol Symbol("id") A unique value, used as a hidden key

Objects cover everything else: plain objects {} (a record with named parts), arrays [] (an ordered list), functions, dates, maps and so on. Arrays and functions are objects too, with extra abilities. They get their own lessons, starting with arrays and objects. In this lesson you only need to recognise them.

Note that there is only one type for ordinary numbers. 42 and 3.14 are both number. There is no separate integer type. The next lesson explains what that means.

typeof

The typeof operator gives back the type of a value as a string.

typeof_values.mjs
console.log("typeof 42:", typeof 42);
console.log("typeof 3.14:", typeof 3.14);
console.log('typeof "text":', typeof "text");
console.log("typeof true:", typeof true);
console.log("typeof undefined:", typeof undefined);
console.log("typeof null:", typeof null);
console.log("typeof 10n:", typeof 10n);
console.log('typeof Symbol("id"):', typeof Symbol("id"));
console.log("typeof {}:", typeof {});
console.log("typeof []:", typeof []);
console.log("typeof console.log:", typeof console.log);
console.log("typeof NaN:", typeof NaN);
Output
typeof 42: number
typeof 3.14: number
typeof "text": string
typeof true: boolean
typeof undefined: undefined
typeof null: object
typeof 10n: bigint
typeof Symbol("id"): symbol
typeof {}: object
typeof []: object
typeof console.log: function
typeof NaN: number

typeof is an operator, not a function, so typeof 42 needs no parentheses. They are harmless, and sometimes needed to group an expression.

Three results deserve a second look.

  • typeof null is "object". This is a mistake from the first version of the language that can never be fixed without breaking old programs. null is a primitive. To test for it, compare directly with value === null.
  • typeof [] is "object". An array is an object. Use Array.isArray(value) to find out whether something is an array.
  • typeof NaN is "number". NaN stands for “not a number”, and it is still a value of the number type. The next lesson explains how it arises.

A function such as console.log reports "function", even though functions are objects underneath. That is a convenience, not a separate type.

Telling null, arrays and objects apart

Because typeof calls all three "object", use a direct test for each. This program checks three values with both approaches:

null_array_checks.mjs
const nothing = null;
const list = [1, 2];
const record = { a: 1 };
console.log(typeof nothing === "object", nothing === null);
console.log(typeof list === "object", Array.isArray(list));
console.log(typeof record === "object", Array.isArray(record));
Output
true true
true true
true false

In each line the first answer comes from typeof and is true every time, so it tells you nothing. The second answer separates the cases: === null finds the null, and Array.isArray finds the array. Once you have learned functions, you can wrap these tests in a helper of your own.

Names that do not exist

Reading a variable that was never declared throws a ReferenceError. But typeof is safe on such a name, and answers "undefined". It is how code checks whether a runtime feature exists, as the first lesson did with window. The one exception is a let or const name that is declared later in the same block: typeof on it throws too. The lesson on let and const explains why.

Dynamic and weak typing

In JavaScript the type belongs to the value, not to the variable. A variable can hold a number now and a string a moment later. The keyword let declares a variable that can be given a new value; the lesson on let and const covers it.

dynamic_typing.mjs
let value = 5;
console.log(typeof value);
value = "five";
console.log(typeof value);
console.log(typeof neverDeclared);
Output
number
string
undefined

That is called dynamic typing. Nothing checks types before the program runs, so a type mistake shows up as wrong behaviour or a runtime error. TypeScript, a separate language that compiles to JavaScript, adds static checks.

JavaScript is also weakly typed: operators accept values of the wrong type and quietly convert them, so "5" * 2 gives 10 instead of an error. The coercion lesson shows the rules.

Primitives and wrapper objects

A primitive has no properties of its own, yet "text".length and "text".toUpperCase() work. The language briefly wraps the primitive in an object to look the property up, then discards the wrapper. You never have to think about this, with one exception: you can create a wrapper by hand with new String(...), and you should not.

wrapper_objects.mjs
console.log(typeof "text", typeof new String("text"));
console.log("text".length, "text".toUpperCase());
Output
string object
4 TEXT

A new String("text") is an object that behaves differently in comparisons and conditions. Write plain "text".

Common pitfalls

  • Testing for null with typeof. typeof value === "object" is also true for null, arrays and every plain object.
  • Treating NaN as “not a number” in the type sense. Its type is number.
  • Using new Number, new String or new Boolean. Use the plain values. Call Number(...), String(...) and Boolean(...) without new when you want to convert.
  • Expecting a variable to keep its type. Nothing stops a reassignment from changing it.
ExercisePredict typeof for 13 values

Without running anything, write down what each line prints, then run the program and compare.

console.log(typeof 0);
console.log(typeof "0");
console.log(typeof 0n);
console.log(typeof null);
console.log(typeof undefined);
console.log(typeof NaN);
console.log(typeof []);
console.log(typeof {});
console.log(typeof Math.max);
console.log(typeof Symbol("x"));
console.log(typeof true);
console.log(typeof typeof 42);
console.log(typeof Infinity);

Input: none. Expected output: 13 lines, from number to number, with null, [] and {} all giving object.

Show solution
typeof_quiz.mjs
console.log(typeof 0);
console.log(typeof "0");
console.log(typeof 0n);
console.log(typeof null);
console.log(typeof undefined);
console.log(typeof NaN);
console.log(typeof []);
console.log(typeof {});
console.log(typeof Math.max);
console.log(typeof Symbol("x"));
console.log(typeof true);
console.log(typeof typeof 42);
console.log(typeof Infinity);
Output
number
string
bigint
object
undefined
number
object
object
function
symbol
boolean
string
number

typeof typeof 42 is typeof "number", which is "string": the inner typeof returns a string. Math.max is a function, and Infinity is a number.

ExerciseThe type of a result

typeof also works on the result of a calculation. Predict the type of each of these eight results, then print them.

  1. 5 + "5"
  2. 5 * "2"
  3. 5 > 3
  4. String(5)
  5. Number("5")
  6. "a" * 2
  7. 10n * 2n
  8. 1 / 0

Input: none. Expected output, one per line: string, number, boolean, string, number, number, bigint, number.

Show solution
result_types.mjs
console.log(typeof (5 + "5"));
console.log(typeof (5 * "2"));
console.log(typeof (5 > 3));
console.log(typeof String(5));
console.log(typeof Number("5"));
console.log(typeof ("a" * 2));
console.log(typeof (10n * 2n));
console.log(typeof (1 / 0));
Output
string
number
boolean
string
number
number
bigint
number

+ with a string joins text, so the first result is a string. * always works on numbers, so "5" * 2 is the number 10 and "a" * 2 is NaN, which is still a number. A comparison gives a boolean. 1 / 0 is Infinity, also a number.

ExerciseIs it really null?

Declare const maybe = null;. Print two lines: first typeof maybe, then the result of testing maybe with === null. Then change the declaration to const maybe = undefined; and run it again.

Input: none. Expected output for null: object and true. Expected output for undefined: undefined and false.

Show solution
really_null.mjs
const maybe = null;
console.log(typeof maybe);
console.log(maybe === null);
Output
object
true

With undefined the program prints undefined and false. Only the direct comparison tells null from a missing value reliably, because typeof null is "object".

Summary

  • JavaScript has seven primitive types (undefined, null, boolean, number, bigint, string, symbol) and the object type for everything else.
  • typeof returns a string. Remember typeof null === "object" and typeof [] === "object". Use === null and Array.isArray instead.
  • Types belong to values, not variables, and operators convert between them when they can.

Next: numbers and BigInt.