Knowledge Base/concepts/JavaScript Primitive Values Explained

JavaScript Primitive Values Explained

JavaScript primitive values are not "small objects." They are the values JavaScript can store and compare directly without object identity getting involved. That distinction explains why two equal strings compare as equal, why strings appear to have methods, why null and undefined mean different kinds of absence, and why typeof null lies to your face with impressive confidence.

The trick is to separate the value itself from the variable that points at it.

The seven primitive types

JavaScript has seven primitive types:

TypeExampleWhat it represents
string"Ada"Text
number42Floating-point numbers, including NaN and Infinity
bigint42nWhole numbers larger than number can safely represent
booleantrueLogical true or false
undefinedundefinedA missing value supplied by JavaScript
nullnullA deliberately empty value supplied by you
symbolSymbol("id")A unique primitive value often used as an object key

Everything else is an object, including arrays, functions, dates, regular expressions, maps, sets, and plain object literals.

typeof "Ada"; // "string"
typeof 42; // "number"
typeof 42n; // "bigint"
typeof true; // "boolean"
typeof undefined; // "undefined"
typeof Symbol("id"); // "symbol"

typeof null; // "object", sadly
typeof []; // "object"
typeof {}; // "object"
typeof function run() {}; // "function"

For the full set of typeof return values and its traps, read How typeof Works in JavaScript.

Primitive values are immutable

A primitive value cannot be changed in place.

const name = "ada";
const upperName = name.toUpperCase();

console.log(name); // "ada"
console.log(upperName); // "ADA"

toUpperCase() did not mutate the original string. It created a new string.

Numbers work the same way:

let count = 1;
count = count + 1;

console.log(count); // 2

The number 1 did not become the number 2. The variable count was reassigned to a different primitive value. Tiny distinction. Big consequences.

Objects are different because their contents can change while the variable still points at the same object:

const user = { name: "Ada" };
user.name = "Grace";

console.log(user); // { name: "Grace" }

That is mutation. Primitive values do not do that.

Assignment copies primitive values

When you assign a primitive value to another variable, JavaScript copies the value.

let first = "draft";
let second = first;

first = "published";

console.log(first); // "published"
console.log(second); // "draft"

Changing first later does not change second, because second received its own copy of the primitive value.

Objects behave differently because the variable stores a reference to the object:

const original = { status: "draft" };
const alias = original;

original.status = "published";

console.log(alias.status); // "published"

Both variables point at the same object. That is why object copying needs more care. For the practical side of object and array copying, see The Spread Operator.

Strings have methods, but strings are not objects

This looks like object behavior:

const language = "JavaScript";

console.log(language.toUpperCase()); // "JAVASCRIPT"
console.log(language.length); // 10

So why is typeof language still "string"?

console.log(typeof language); // "string"

JavaScript temporarily wraps primitive strings so you can read properties and call methods. You get useful conveniences like .toUpperCase(), .trim(), .includes(), and .length, but the value itself is still a primitive string.

That wrapper is temporary. You should not treat a primitive like a place to store custom data:

const languageObject = new String("JavaScript");

console.log(typeof languageObject); // "object"
console.log(languageObject === "JavaScript"); // false

Avoid new String(), new Number(), and new Boolean() in application code. They create wrapper objects, not primitive values, and wrapper objects make equality and truthiness more confusing than they need to be.

null and undefined are different absence values

undefined usually means JavaScript has no value to give you yet.

let selectedUser;

console.log(selectedUser); // undefined

null means you deliberately put an empty value there.

const selectedUser = null;

console.log(selectedUser); // null

That difference matters when reading code:

let result;

result = null;

The first line says "this variable exists, but no value has been assigned." The second says "the value is intentionally empty now."

Both are falsy, but they are not the same value:

undefined === null; // false
undefined == null; // true

Prefer === unless you are intentionally checking for both null and undefined. If you want the broader boolean rules, read Truthy and Falsy Values.

Numbers include NaN and Infinity

JavaScript has one ordinary numeric primitive type: number.

typeof 10; // "number"
typeof 3.14; // "number"
typeof -0; // "number"
typeof Infinity; // "number"
typeof NaN; // "number"

Yes, NaN has type "number". It represents a failed numeric result, not a separate type.

const result = Number("not a number");

console.log(result); // NaN
console.log(typeof result); // "number"
console.log(Number.isNaN(result)); // true

Use Number.isNaN(value) when you need to detect NaN. Do not use typeof value === "number" for that job. It will happily include NaN.

BigInt is for whole numbers, not decimals

bigint stores integers that can go beyond the safe range of number.

const safeLimit = Number.MAX_SAFE_INTEGER;
const next = BigInt(safeLimit) + 1n;

console.log(next); // 9007199254740992n
console.log(typeof next); // "bigint"

BigInts are useful for IDs, counters, and integer math where precision matters. They are not a drop-in replacement for numbers:

1n + 2n; // 3n
1n + 2; // TypeError

JavaScript does not let you mix bigint and number in arithmetic. Convert deliberately so the code says which numeric world it is using.

Symbols are unique primitive values

Every call to Symbol() creates a new unique value.

const firstId = Symbol("id");
const secondId = Symbol("id");

console.log(firstId === secondId); // false

The description helps humans debug the symbol. It does not make two symbols equal.

Symbols are often used as object keys when a property should avoid name collisions:

const internalId = Symbol("internalId");

const user = {
  name: "Ada",
  [internalId]: 123,
};

console.log(user[internalId]); // 123

You will not need symbols in most beginner code, but they belong in the primitive list.

Primitive conversion can be helpful or rude

JavaScript sometimes converts primitives for you. That can be convenient:

const message = "You have " + 3 + " messages";

console.log(message); // "You have 3 messages"

It can also create bugs:

const cartCount = "5";
const bonusItems = 3;

console.log(cartCount + bonusItems); // "53"

Because one side is a string, + performs string concatenation. Convert when you mean arithmetic:

const totalItems = Number(cartCount) + bonusItems;

console.log(totalItems); // 8

Template literals make string construction clearer when your actual goal is text:

const totalItems = 8;
const label = `Cart items: ${totalItems}`;

console.log(label); // "Cart items: 8"

For string interpolation details, see JavaScript Template Literals.

Key Takeaways

  • JavaScript primitive values are string, number, bigint, boolean, undefined, null, and symbol.
  • Primitive values are immutable. Variables can be reassigned, but the primitive value itself does not mutate.
  • Assignment copies primitive values, while object assignment copies a reference to the same object.
  • Strings can use methods because JavaScript temporarily wraps them, not because primitive strings are objects.
  • null, undefined, NaN, bigint, and symbol each solve a different problem. Treating them as interchangeable is where the bugs start.
Share this article:
javascriptfundamentalstypes