Mert Özen Further With Every Line
Frontend JavaScript Basics — #04

JavaScript Data Types: Primitives, Objects and typeof

Mert Özen Nov 3, 2026 8 min 6 views
JavaScript Data Types: Primitives, Objects and typeof

We meet JavaScript's seven primitive types and objects, and use examples to see why typeof says "object" for null, why primitives are immutable, and how values differ from references.

Version note: The code in this post targets ES2026 and current browsers (Chrome, Edge, Firefox, Safari).

Predict First

The typeof operator tells you a value's type as a string. Let's ask it about three values:

console.log(typeof null);
console.log(typeof []);
console.log(typeof function () {});

What do these three lines print to the console? Are you expecting "null" for null, "array" for the array and "function" for the function? Make a guess before you keep reading. We'll answer all three once we've met JavaScript's types one by one.

Values Have Types, Variables Don't

We compared the variables we saw earlier to labels. The label itself has no type; the type belongs to the value inside the box. That's why you can put a number into a variable first and a piece of text later. Paste this into your browser's console and try it:

let value = 42;
console.log(typeof value);
value = "forty-two";
console.log(typeof value);

You'll see "number" first, then "string". This behavior is called dynamic typing. On every operation, the engine looks at the value's current type and acts accordingly. It's a flexible system, but it comes at a price: if you don't keep track of types, the engine won't warn you on your behalf. That's why knowing which types JavaScript has, and how each one behaves, is essential.

JavaScript has eight types. Seven of them are primitives, types that carry a single, simple value: string, number, bigint, boolean, undefined, null and symbol. The eighth is object. Let's look at the primitives first.

string, number, boolean: Your Everyday Trio

These three will make up most of your code:

const city = "Istanbul";
const district = 'Kadikoy';
const floorCount = 12;
const temperature = 18.5;
const isOpen = true;
console.log(typeof city, typeof floorCount, typeof temperature, typeof isOpen);

The output is string number number boolean. A string is a piece of text, written between double quotes or single quotes; both do the same job. We'll cover backtick template literals and all the finer points of strings in detail in upcoming posts.

Number covers both integers and decimals. In many languages those are separate types, but JavaScript has a single number type: 12 and 18.5 are both numbers. Decimals also come with small precision surprises; we'll see those in the post on numbers.

A boolean has only two values: true and false. Yes or no, on or off. You'll run into it all the time in conditions and comparisons.

undefined and null: Two Different Kinds of Empty

JavaScript has two ways of saying "no value," and they're not the same thing:

let address;
console.log(address);
let selectedProduct = null;
console.log(selectedProduct);

The first line prints undefined, the second null. Picture a hotel front desk to see the difference. undefined is a blank line on a registration form nobody has filled in yet; no one wrote anything there. null is the line where the clerk deliberately wrote "no rooms available"; there is an answer, and the answer is "nothing."

In practice, the engine produces undefined on its own: a variable with no value assigned, a property the object doesn't have, the result of a function that doesn't return anything. null is usually something you write yourself, to say "there's intentionally no value here." A simple rule: if you're emptying something on purpose, use null, and leave undefined to the engine.

bigint and symbol: The Rarely Seen Pair

The number type has a limit. Past a certain size, it can't hold integers accurately. Try it in the console:

console.log(Number.MAX_SAFE_INTEGER);
console.log(9007199254740991 + 2);
console.log(9007199254740991n + 2n);

Number.MAX_SAFE_INTEGER is the largest integer a number can safely hold: 9007199254740991. Add 2 to it and you'd expect 9007199254740993, but you get 9007199254740992. Numbers with an n at the end belong to the bigint type, which arrived with ES2020 and holds integers of any size without errors; the third line gives the correct result, 9007199254740993n. Careful: you can't mix bigint and number in the same operation. Write 10n + 1 and you get TypeError: Cannot mix BigInt and other types, use explicit conversions.

Symbol arrived with ES2015, and each one produces a unique value:

const id1 = Symbol("id");
const id2 = Symbol("id");
console.log(id1 === id2);
console.log(typeof id1);

Even though their descriptions match, the two symbols aren't equal; the output is false and "symbol". Symbols are mostly used to add hidden keys to objects that won't clash with any other code. You'll rarely write them in everyday code; we'll see where they really shine in upcoming posts, when we talk about iterators.

object: Everything Else

Everything that isn't a primitive is an object. Objects you write with curly braces, arrays you write with square brackets, functions, dates: they all live under this roof:

const user = { name: "Ada", age: 31 };
const colors = ["red", "green"];
const today = new Date();
console.log(typeof user, typeof colors, typeof today);

The answer for all three is "object". While a primitive carries a single value, an object is a container that holds several values together. We'll cover objects, arrays and dates in detail one by one in upcoming posts. What you need to know in this post is that this distinction also changes how values get copied; we'll get to that shortly.

typeof and null's Strange Answer

Now we can go back to the opening question. The answers are "object", "object" and "function", in that order.

The array coming out as "object" makes sense, because as we just saw, an array is an object too. If you want to know whether a value is an array, typeof won't help; use Array.isArray(colors) instead, and you'll get true.

Functions are actually objects as well, but since they're a special callable kind, typeof gives them their own answer: "function". That's a convenience; it lets you tell in one line whether a value can be called.

typeof null giving "object", on the other hand, is a plain bug. In the first version of JavaScript, values were stored in memory with a small type tag, and the tag for objects was zero. Since null was represented as a value made entirely of zeros, typeof looked at it and said "object." The bug was noticed years ago, but fixing it would have broken countless websites that relied on this behavior. Because the web's ground rule is "don't break old pages," the bug went into the standard as it was, and it's still there.

The practical takeaway: check whether a value is null directly with value === null, not with typeof. We'll look at === in detail in the next post. One more thing: typeof always returns a string. So if you write typeof typeof 42, the answer is "string".

Primitives Don't Change, Values Get Copied

Primitives have an important property: they're immutable. You can't change a single letter of a string in place:

let word = "desk";
word[0] = "K";
console.log(word);
console.log(word.toUpperCase());
console.log(word);

The output is desk, DESK, desk. The second line changed nothing, and in normal mode it didn't even complain; in the strict mode we saw earlier, you'd get a TypeError. toUpperCase() didn't touch the original string either; it produced and returned a new, uppercase string. The only way to change the string is to assign a new value to the variable: word = "disk".

So how could we call a method like toUpperCase() on word while it's a primitive? For that brief moment, the engine creates a temporary wrapper object behind the scenes, runs the method and throws the object away. You never notice.

This immutability also shapes how copying works. Compare these in the console:

let a = 10;
let b = a;
b = 20;
console.log(a, b);

const box1 = { color: "red" };
const box2 = box1;
box2.color = "blue";
console.log(box1.color);

The first output is 10 20. When you said b = a, a copy of the value was taken; changing b didn't affect a. The second output, however, is blue. box2 = box1 didn't copy the object; it created a second name pointing to the same object.

Think of it as the difference between a photocopy and a home address. Copying a primitive is making a photocopy; scribble whatever you like on the copy, the original stays clean. Copying an object is writing the home address on another piece of paper. Both papers point to the same house; if you go to one address and paint the wall blue, whoever goes to the other address sees a blue wall too. This is called working with references. We'll go deeper into it in the posts on objects and arrays, along with ways to copy them.

Common Pitfalls

Checking for null with typeof. The check typeof value === "object" also returns true for null. When null shows up where you expected an object, this check misleads you and you get an error on the next line.

Trying to recognize an array with typeof. For an array, the answer is always "object". Use Array.isArray to check for arrays.

Treating "42" and 42 as the same. One is a string, the other a number. Data coming from the user, like a number typed into a form field, usually arrives as a string. It's exactly why the "2" + 2 example we saw earlier gave "22".

Guessing NaN's type wrong. NaN stands for "Not a Number," yet oddly, typeof NaN answers "number". The result of an invalid numeric operation is still a number. We'll come back to this in the post on numbers.

Thinking you copied an object. const copy = original doesn't copy an object; it just gives it a second name. Every change you make through the new name also changes the original.

A Small Experiment

★☆☆ Create a variable for each of the seven primitive types and print them all to the console with typeof. Which one didn't give the answer you expected?

★★☆ Predict the output of each line before running it:

console.log(typeof typeof 42);
console.log(typeof NaN);
console.log(typeof undefined);
console.log(typeof 10n);
console.log(Array.isArray([]));

★★★ Before running the code below, predict the output of the two console.log calls. Then run it and explain the result in your own words, using the photocopy and address analogy:

let firstName = "Grace";
let nickname = firstName;
nickname = "Gracie";

const profile = { name: "Grace" };
const backup = profile;
backup.name = "Gracie";

console.log(firstName, nickname);
console.log(profile.name, backup.name);

You now know what type each of your values is and how it gets copied. In the next post we move on to doing things with these values: arithmetic and comparison operators, the difference between == and ===, truthy/falsy values, and the ?? operator.