Learn JavaScript’s 7 primitive types: string, number, bigint, boolean, undefined, null, and symbol. Understand immutability, typeof quirks, and autoboxing.
What’s the difference between "hello" and { text: "hello" }? Why can you call "hello".toUpperCase() if strings aren’t objects? And why does typeof null return "object"?
These seven primitive types are the foundation of all data in JavaScript. Unlike objects, primitives are immutable (unchangeable) and compared by value. Every complex structure you build (arrays, objects, classes) ultimately relies on these simple building blocks.
What you’ll learn in this guide:
The 7 primitive types in JavaScript and when to use each
How typeof works (and its famous quirks)
Why primitives are “immutable” and what that means
The magic of autoboxing — how "hello".toUpperCase() works
The difference between null and undefined
Common mistakes to avoid with primitives
Famous JavaScript gotchas every developer should know
New to JavaScript? This guide is beginner-friendly! No prior knowledge required. We’ll explain everything from the ground up.
In JavaScript, a primitive is data that is not an object and has no methods of its own. As defined by the ECMAScript 2024 specification (ECMA-262), JavaScript has exactly 7 primitive types:
Think of data in JavaScript like chemistry class (but way more fun, and no lab goggles required). Primitives are like atoms: the fundamental, indivisible building blocks that cannot be broken down further. Objects are like molecules: complex structures made up of multiple atoms combined together.
Just like atoms are the foundation of all matter, primitives are the foundation of all data in JavaScript. Every complex data structure you create — arrays, objects, functions — is ultimately built on top of these simple primitive values.
Template literals (backticks) are not a separate type. They’re just a more powerful syntax for creating strings. The result is still a regular string primitive:
Copy
Ask AI
let name = "Alice";let age = 25;// String interpolation - embed expressionslet greeting = `Hello, ${name}! You are ${age} years old.`;console.log(greeting); // "Hello, Alice! You are 25 years old."console.log(typeof greeting); // "string" — it's just a string!// Multi-line stringslet multiLine = ` This is line 1 This is line 2`;console.log(typeof multiLine); // "string"
You cannot change individual characters in a string:
Copy
Ask AI
let str = "hello";str[0] = "H"; // Does nothing! No error, but no changeconsole.log(str); // Still "hello"// To "change" a string, create a new onestr = "H" + str.slice(1);console.log(str); // "Hello"
String methods like toUpperCase(), slice(), replace() always return new strings. They never modify the original.
JavaScript has only one number type for both integers and decimals. All numbers are stored as 64-bit floating-point (a standard way computers store decimals).
This isn’t a JavaScript bug — it follows the IEEE 754 double-precision floating-point standard used by virtually all modern programming languages. The decimal 0.1 cannot be perfectly represented in binary.
Working with money? Never use floating-point for calculations! Store amounts in cents as integers, then use JavaScript’s built-in Intl.NumberFormat for display.
Copy
Ask AI
// Bad: floating-point errors in calculationslet price = 0.1 + 0.2; // 0.30000000000000004// Good: calculate in cents, format for displaylet priceInCents = 10 + 20; // 30 (calculation is accurate!)// Use Intl.NumberFormat to display as currencyconst formatter = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD',});console.log(formatter.format(priceInCents / 100)); // "$0.30"// Works for any locale and currency!const euroFormatter = new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR',});console.log(euroFormatter.format(1234.56)); // "1.234,56 €"
Intl.NumberFormat is built into JavaScript. No external libraries needed! It handles currency symbols, decimal separators, and locale-specific formatting automatically.
let isLoggedIn = true;let hasPermission = false;// From comparisonslet isAdult = age >= 18; // true or falselet isEqual = name === "Alice"; // true or false
undefined means “no value has been assigned.” JavaScript uses it automatically in several situations:
Copy
Ask AI
// 1. Declared but not assignedlet x;console.log(x); // undefined// 2. Missing function parametersfunction greet(name) { console.log(name); // undefined if called without argument}greet();// 3. Function with no return statementfunction doNothing() { // no return}console.log(doNothing()); // undefined// 4. Accessing non-existent object propertylet person = { name: "Alice" };console.log(person.age); // undefined
Don’t explicitly assign undefined to variables. Use null instead to indicate “intentionally empty.”
Yes, really. This is one of JavaScript’s most famous quirks! It’s a historical mistake from JavaScript’s first implementation in 1995. It was never fixed because too much existing code depends on it.
Copy
Ask AI
// How to properly check for nulllet value = null;console.log(value === null); // true (use strict equality)
Symbol (ES6) creates unique identifiers. According to MDN, Symbol was the first new primitive type added to JavaScript since its creation in 1995. Even symbols with the same description are different.
Copy
Ask AI
let id1 = Symbol("id");let id2 = Symbol("id");console.log(id1 === id2); // false — always unique!console.log(id1.description); // "id" (the description)
const ID = Symbol("id");const user = { name: "Alice", [ID]: 12345 // Symbol as property key};console.log(user.name); // "Alice"console.log(user[ID]); // 12345// Symbol keys don't appear in normal iterationconsole.log(Object.keys(user)); // ["name"] — ID not included
JavaScript has built-in symbols for customizing object behavior:
Copy
Ask AI
// Symbol.iterator - make an object iterable// Symbol.toStringTag - customize Object.prototype.toString// Symbol.toPrimitive - customize type conversion
These are called well-known symbols and allow you to customize how objects behave with built-in operations.
Symbols are an advanced feature. As a beginner, focus on understanding that they exist and create unique values. You’ll encounter them when diving into advanced patterns and library code.
Since typeof has quirks, here are more reliable alternatives:
Copy
Ask AI
// Check for null specificallylet value = null;if (value === null) { console.log("It's null");}// Check for arraysArray.isArray([1, 2, 3]); // trueArray.isArray("hello"); // false// Get precise type with Object.prototype.toStringObject.prototype.toString.call(null); // "[object Null]"Object.prototype.toString.call([]); // "[object Array]"Object.prototype.toString.call(new Date()); // "[object Date]"
Array.isArray() is the reliable way to check for arrays, since typeof [] returns "object". For more complex type checking, Object.prototype.toString() gives precise type information.
let str = "hello";// These methods don't change 'str' — they return NEW stringsstr.toUpperCase(); // Returns "HELLO"console.log(str); // Still "hello"!// To capture the new value, you must reassignstr = str.toUpperCase();console.log(str); // Now "HELLO"
// Check for either null or undefined (loose equality)if (value == null) { console.log("Value is null or undefined");}// Check for specifically undefinedif (value === undefined) { console.log("Value is undefined");}// Check for specifically nullif (value === null) { console.log("Value is null");}// Check for "has a value" (not null/undefined)if (value != null) { console.log("Value exists");}
The #1 Primitive Mistake: Using Wrapper Constructors
The most common mistake developers make with primitives is using new String(), new Number(), or new Boolean() instead of literal values.
Copy
Ask AI
┌─────────────────────────────────────────────────────────────────────────┐│ PRIMITIVES VS WRAPPER OBJECTS │├─────────────────────────────────────────────────────────────────────────┤│ ││ WRONG WAY RIGHT WAY ││ ───────── ───────── ││ new String("hello") "hello" ││ new Number(42) 42 ││ new Boolean(true) true ││ ││ typeof new String("hi") → "object" typeof "hi" → "string" ││ new String("hi") === "hi" → false "hi" === "hi" → true ││ │└─────────────────────────────────────────────────────────────────────────┘
Copy
Ask AI
// ❌ WRONG - Creates an object, not a primitiveconst str = new String("hello");console.log(typeof str); // "object" (not "string"!)console.log(str === "hello"); // false (object vs primitive)// ✓ CORRECT - Use primitive literalsconst str2 = "hello";console.log(typeof str2); // "string"console.log(str2 === "hello"); // true
The Trap: Using new String(), new Number(), or new Boolean() creates wrapper objects, not primitives. This breaks equality checks (===), typeof comparisons, and can cause subtle bugs. Always use literal syntax: "hello", 42, true.
Rule of Thumb: Never use new with String, Number, or Boolean. The only exception is when you intentionally need the wrapper object (which is rare). For type conversion, use them as functions without new: String(123) returns "123" (a primitive).
JavaScript has some famous “weird parts” that every developer should know. Most relate to primitives and type coercion.
1. typeof null === 'object'
Copy
Ask AI
console.log(typeof null); // "object"
Why? This is a bug from JavaScript’s first implementation in 1995. In the original code, values had a small label to identify their type. Objects had the label 000, and null was represented as the NULL pointer (0x00), which also had 000.Why not fixed? A proposal to fix it was rejected because too much existing code checks typeof x === "object" and expects null to pass.Workaround:
Copy
Ask AI
// Always check for null explicitlyif (value !== null && typeof value === "object") { // It's a real object}
NaN is so confused about its identity that it doesn’t even equal itself!Why? By the IEEE 754 specification, NaN represents “Not a Number”, an undefined or unrepresentable result. Since it’s not a specific number, it can’t equal anything, including itself.How to check for NaN:
Copy
Ask AI
// Don't do thisif (value === NaN) { } // Never true!// Do this insteadif (Number.isNaN(value)) { } // ES6, recommendedif (isNaN(value)) { } // Older, has quirks
isNaN() converts the value first, so isNaN("hello") is true. Number.isNaN() only returns true for actual NaN.
Why? Computers store numbers in binary. Just like 1/3 can’t be perfectly represented in decimal (0.333…), 0.1 can’t be perfectly represented in binary.Solutions:
Copy
Ask AI
// 1. Work in integers (cents, not dollars) — RECOMMENDEDlet totalCents = 10 + 20; // 30 (accurate!)let dollars = totalCents / 100; // 0.3// 2. Use Intl.NumberFormat for displaynew Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD'}).format(0.30); // "$0.30"// 3. Compare with tolerance for equality checksMath.abs((0.1 + 0.2) - 0.3) < Number.EPSILON; // true (Number.EPSILON is the smallest difference)// 4. Use toFixed() for simple rounding(0.1 + 0.2).toFixed(2); // "0.30"
4. Empty String is Falsy, But...
Copy
Ask AI
console.log(Boolean("")); // false (empty string is falsy)console.log(Boolean(" ")); // true (space is truthy!)console.log(Boolean("0")); // true (string "0" is truthy!)console.log(Boolean(0)); // false (number 0 is falsy)console.log("" == false); // true (coercion)console.log("" === false); // false (different types)
The lesson: Be careful with truthy/falsy checks on strings. An empty string "" is falsy, but a string with just whitespace " " is truthy.
Copy
Ask AI
// Check for empty or whitespace-only stringif (str.trim() === "") { console.log("String is empty or whitespace");}
Why? The + operator does addition for numbers, but concatenation for strings. When mixed, JavaScript converts numbers to strings.Be explicit:
Copy
Ask AI
// Force number additionNumber("1") + Number("2"); // 3parseInt("1") + parseInt("2"); // 3// Force string concatenationString(1) + String(2); // "12"`${1}${2}`; // "12"
Want to go deeper? Kyle Simpson’s book “You Don’t Know JS: Types & Grammar” is the definitive guide to understanding these quirks. It’s free to read online!
Question 1: What are the 7 primitive types in JavaScript?
Answer:
string - text data
number - integers and decimals
bigint - large integers
boolean - true/false
undefined - no value assigned
null - intentionally empty
symbol - unique identifier
Remember: Everything else is an object (arrays, functions, dates, etc.).
Question 2: What does typeof null return and why?
Answer:typeof null returns "object".This is a bug from JavaScript’s original implementation in 1995. Values were stored with type tags, and both objects and null had the same 000 tag. The bug was never fixed because too much existing code depends on this behavior.To check for null, use value === null instead.
Question 3: Why does 0.1 + 0.2 !== 0.3?
Answer: Because JavaScript (like all languages) uses binary floating-point (IEEE 754) to store numbers.Just like 1/3 can’t be perfectly represented in decimal (0.333…), 0.1 can’t be perfectly represented in binary. The tiny rounding errors accumulate, giving us 0.30000000000000004 instead of 0.3.Solutions: Use integers (work in cents), use toFixed() for display, compare with tolerance, or use a decimal math library.
Question 4: What's the difference between null and undefined?
Answer:
undefined: Means “no value has been assigned.” JavaScript sets this automatically for uninitialized variables, missing function arguments, and non-existent properties.
null: Means “intentionally empty.” Developers use this explicitly to indicate “this has no value on purpose.”
Key difference: undefined is the default empty value; null is the intentional empty value.
Copy
Ask AI
let x; // undefined (automatic)let y = null; // null (explicit)
Question 5: How can 'hello'.toUpperCase() work if primitives have no methods?
Answer: Through autoboxing (also called “auto-wrapping”).When you call a method on a primitive:
JavaScript temporarily wraps it in a wrapper object (String, Number, etc.)
The method is called on the wrapper object
The result is returned
The wrapper object is discarded
So "hello".toUpperCase() becomes (new String("hello")).toUpperCase() behind the scenes. The original primitive "hello" is never changed.
Question 6: Why can't you use === to check if a value is NaN?
Answer: Because NaN is the only value in JavaScript that is not equal to itself!
Copy
Ask AI
console.log(NaN === NaN); // false!
This is per the IEEE 754 floating-point specification. NaN represents an undefined or unrepresentable mathematical result, so it can’t equal anything, including itself.How to check for NaN:
Copy
Ask AI
// ❌ WRONG - Never works!if (value === NaN) { }// ✓ CORRECT - Use Number.isNaN()if (Number.isNaN(value)) { }
JavaScript has exactly seven primitive types: string, number, bigint, boolean, undefined, null, and symbol. As defined by the ECMAScript specification, these are the fundamental building blocks of all data in JavaScript — everything else is an object.
Why does typeof null return 'object' in JavaScript?
This is a well-documented bug from JavaScript’s first implementation in 1995. In the original C source code, values were stored with type tags, and both objects and null shared the 000 tag. A proposal to fix it (typeof null === “null”) was rejected by TC39 because too much existing code depends on the current behavior.
What is autoboxing in JavaScript?
Autoboxing is the process where JavaScript temporarily wraps a primitive in its corresponding wrapper object (String, Number, Boolean) when you access a property or call a method. For example, "hello".toUpperCase() works because the engine briefly creates a String object, calls the method, and discards the wrapper. As documented in MDN, this is why primitives appear to have methods even though they are not objects.
What is the difference between null and undefined in JavaScript?
undefined means “no value has been assigned” and is set automatically by the JavaScript engine. null means “intentionally empty” and is always set explicitly by the developer. According to the ECMAScript specification, null == undefined is true under loose equality, but null === undefined is false because they are different types.
Why does 0.1 + 0.2 not equal 0.3 in JavaScript?
This happens because JavaScript uses the IEEE 754 double-precision floating-point standard to represent numbers. Certain decimal fractions like 0.1 cannot be represented exactly in binary, leading to tiny rounding errors. This is not a JavaScript-specific issue — the same behavior occurs in Python, Java, C++, and virtually every language that uses IEEE 754.