Skip to main content

🧱 Primitive Data Types

Every value in JavaScript is either a primitive or an object. Primitives are the simple, indivisible building blocks — text, numbers, true/false, and a few special "nothing" values. Master these seven types and you understand the atoms from which every program is built.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Name all seven primitive types and give an example of each
  • Explain what it means for a primitive to be immutable
  • Work confidently with strings, numbers, and booleans, including template literals and truthiness
  • Distinguish undefined from null and know when to use each
  • Recognise when to reach for symbol and bigint

Estimated Time: 30–40 minutes  •  Difficulty: Beginner

Hands-on: Build a small "value inspector" that reports the type and truthiness of any input.

In This Lesson

The Two Families of Values

JavaScript sorts every value into one of two families. Primitive types are simple, single values that are immutable and compared by their value. Reference types (objects, arrays, functions) are compound values compared by their identity in memory.

💡 A useful analogy: Primitives are the atoms of programming — the simplest units everything else is built from. Just as hydrogen and oxygen combine into water with brand-new properties, primitives combine inside objects and arrays to create richer structures.

There are exactly seven primitive types. This lesson focuses on them; objects get their own module.

graph TB A[JavaScript values] --> B[Primitive types] A --> C[Reference types] B --> D[string] B --> E[number] B --> F[boolean] B --> G[undefined] B --> H[null] B --> I[symbol] B --> J[bigint] C --> K[Object · Array · Function · ...]

📖 Key Terms

Primitive: a simple, immutable value that is not an object and has no methods of its own (though JavaScript temporarily wraps some in objects so you can call methods).

Immutable: the value itself cannot be altered in place — "changing" it actually produces a new value.

String

A string is text: a sequence of characters. You can write it three ways, but the modern default is the template literal (backticks), which supports interpolation and multi-line text.

const single = 'Hello world';
const double = "Hello world";
const template = `Hello world`;   // backticks — the modern default

const name = 'Ada';
const age = 28;

// Template literals interpolate expressions with ${ }
const greeting = `Hello, ${name}! You are ${age} years old.`;
console.log(greeting); // "Hello, Ada! You are 28 years old."

// They can span multiple lines and evaluate any expression
const summary = `${name.toUpperCase()} is ${age < 30 ? 'young' : 'seasoned'}
and lives on line two.`;

Common string operations

const phrase = 'The quick brown fox';

console.log(phrase.length);            // 19
console.log(phrase.toUpperCase());     // "THE QUICK BROWN FOX"
console.log(phrase.includes('brown')); // true
console.log(phrase.indexOf('quick'));  // 4
console.log(phrase.slice(4, 9));       // "quick"
console.log(phrase.split(' '));        // ["The","quick","brown","fox"]
console.log('  padded  '.trim());      // "padded"
console.log(phrase.replace('fox', 'dog')); // "The quick brown dog"

⚠️ Strings are immutable

You cannot change a character in place. String methods never modify the original — they always return a new string.

let text = 'JavaScript';
text[0] = 'j';        // silently does nothing
console.log(text);    // still "JavaScript"

text = text.toLowerCase(); // reassign to keep the new value
console.log(text);         // "javascript"

Number

JavaScript has a single number type for both integers and decimals — there is no separate int and float. Numbers are stored as 64-bit floating point, which is powerful but has two consequences you must know: limited integer range and tiny decimal rounding errors.

const integer = 42;
const decimal = 3.14159;
const negative = -17;
const scientific = 1.2e6;   // 1,200,000
const hex = 0xff;           // 255

// Special numeric values
console.log(1 / 0);         // Infinity
console.log(-1 / 0);        // -Infinity
console.log(Number('abc')); // NaN  ("Not a Number")

The famous floating-point surprise

console.log(0.1 + 0.2);         // 0.30000000000000004  (not exactly 0.3!)
console.log(0.1 + 0.2 === 0.3); // false

// Robust comparison uses a tiny tolerance
console.log(Math.abs((0.1 + 0.2) - 0.3) < Number.EPSILON); // true
💡 Why does 0.1 + 0.2 misbehave? It's like trying to write 1/3 in decimal: you get 0.3333… forever. Many decimals (including 0.1) have no exact binary representation, so a microscopic rounding error creeps in. For money, work in the smallest unit (cents) as integers to sidestep it.

Useful number tools

console.log(Number.isNaN(NaN));            // true — the reliable NaN check
console.log(Number.MAX_SAFE_INTEGER);      // 9007199254740991
console.log((42.3567).toFixed(2));         // "42.36" (a string)
console.log(parseInt('42px', 10));         // 42  (always pass the radix!)
console.log(parseFloat('3.14 is pi'));     // 3.14

console.log(Math.round(4.5));  // 5
console.log(Math.floor(4.9));  // 4
console.log(Math.max(3, 9, 1)); // 9
console.log(Math.random());     // 0 ≤ x < 1

Boolean, Truthy & Falsy

A boolean holds one of two values: true or false. It powers every decision your code makes. Crucially, when JavaScript needs a boolean (for example inside an if), it converts any value into one — that value's "truthiness."

const isActive = true;
const isGreater = 5 > 3;        // true
const isEqual = 10 === '10';    // false (strict — different types)

The falsy values — memorize these eight

Everything else is truthy. There are exactly eight falsy values:

Boolean(false);      // false
Boolean(0);          // false
Boolean(-0);         // false
Boolean(0n);         // false (BigInt zero)
Boolean('');         // false (empty string)
Boolean(null);       // false
Boolean(undefined);  // false
Boolean(NaN);        // false

// Surprising truthy values:
Boolean('false');    // true  (non-empty string)
Boolean([]);         // true  (empty array is still an object)
Boolean({});         // true  (empty object)
💡 Truthiness as light switches: the eight falsy values are switches wired "off"; every other value is wired "on." When JavaScript evaluates a condition it just checks whether the switch is on, regardless of the value's type.

Logical operators return values, not just true/false

console.log('Ada' && 42);   // 42     (&& returns the last truthy, or first falsy)
console.log(0 || 'fallback'); // 'fallback' (|| returns the first truthy)
console.log(!'');            // true   (! flips truthiness to a real boolean)

// Nullish coalescing (??) only falls back on null/undefined, not on 0 or ''
console.log(0 ?? 'default');   // 0          (0 is a real value)
console.log(null ?? 'default'); // 'default'

Undefined & Null

Both represent "no value," but they carry different meanings. undefined means "no value has been set yet" and is what JavaScript hands you automatically. null means "intentionally empty" and is something you assign on purpose.

// undefined appears on its own:
let box;                       // declared, not assigned
console.log(box);              // undefined
const user = { name: 'Ada' };
console.log(user.age);         // undefined (missing property)

// null is a deliberate choice:
let selectedItem = null;       // "nothing is selected right now"
function findUser() { return null; } // "searched, found nobody"
Aspectundefinednull
MeaningValue never assignedIntentional absence of value
Who sets itJavaScript, automaticallyYou, deliberately
typeof"undefined""object" (a historical bug)
Valid in JSONNoYes
null == undefinedtrue (loose) — but null === undefined is false
⚠️ The famous quirk: typeof null returns "object". This is a bug from JavaScript's first days that can never be fixed without breaking the web. To test for null, compare directly: value === null.

Modern syntax handles both gracefully with optional chaining (?.) and nullish coalescing (??):

const profile = { name: 'Ada', address: { city: 'Cebu' } };

// Safely reach into possibly-missing properties, with a fallback
const zip = profile?.address?.zip ?? 'Unknown';
console.log(zip); // "Unknown" — no crash even though zip is missing

Symbol & BigInt

The last two primitives are specialists. You'll use them rarely as a beginner, but you should recognise them.

Symbol — guaranteed-unique identifiers

Every Symbol() is unique, even if two symbols share the same description. That makes them ideal as object keys that can never accidentally collide with other keys.

const a = Symbol('id');
const b = Symbol('id');
console.log(a === b);   // false — always unique

const user = { name: 'Ada', [a]: 42 };
console.log(user[a]);           // 42
console.log(Object.keys(user)); // ["name"] — symbol keys stay hidden from normal iteration
💡 Symbols as VIP badges: each badge is unique and can't be forged, and it doesn't show up on the public guest list (normal property enumeration) — a tidy way to attach "hidden" keys to an object.

BigInt — integers of unlimited size

Regular numbers lose precision beyond Number.MAX_SAFE_INTEGER. BigInt (written with an n suffix) represents whole numbers of any size exactly — useful for cryptography, database IDs, and high-precision counters.

const huge = 9007199254740991n + 2n;
console.log(huge);            // 9007199254740993n — exact

// Regular numbers would lose that precision:
console.log(9007199254740991 + 2); // 9007199254740992  (wrong)

console.log(typeof huge);     // "bigint"
// You cannot mix BigInt and Number directly:
// console.log(huge + 1);     // ❌ TypeError
console.log(huge + BigInt(1)); // ✅ convert explicitly first

Immutability & typeof

All primitives share one defining behavior: they are immutable and compared by value. Objects, by contrast, are mutable and compared by reference. This one difference explains a huge share of JavaScript "gotchas."

// Primitives: compared by value
console.log(5 === 5);         // true
console.log('hi' === 'hi');   // true

// Objects: compared by reference (identity)
console.log({} === {});       // false — two different objects
const a = { x: 1 };
const b = a;                  // same reference
console.log(a === b);         // true

Checking a type with typeof

console.log(typeof 'hi');       // "string"
console.log(typeof 42);         // "number"
console.log(typeof true);       // "boolean"
console.log(typeof undefined);  // "undefined"
console.log(typeof 10n);        // "bigint"
console.log(typeof Symbol());   // "symbol"

// The two things to remember:
console.log(typeof null);       // "object"   ← historical bug
console.log(typeof function(){}); // "function" ← functions report specially

💡 Why immutability is a feature

Because a primitive can never change underneath you, passing it around is safe: no other part of the program can reach in and alter your 5 or your 'Ada'. That predictability is exactly what makes primitives reliable building blocks.

Hands-on Exercise

🏋️ Build a Value Inspector

Objective: Write a function inspect(value) that reports a value's primitive type and whether it is truthy or falsy.

Requirements

  1. Return an object { type, truthy }.
  2. type should be the result of typeof, except it must report 'null' for null (fixing the historical bug).
  3. truthy should be a real boolean.
  4. Test it against: 'hi', 0, null, undefined, [], and 42n.
💡 Hint

Start with let type = typeof value;, then special-case null with an if (value === null) check. Convert to a boolean with the double-NOT operator !!value or with Boolean(value).

✅ Solution
function inspect(value) {
  let type = typeof value;
  if (value === null) {
    type = 'null'; // correct the typeof-null quirk
  }
  return { type, truthy: Boolean(value) };
}

console.log(inspect('hi'));      // { type: 'string',    truthy: true  }
console.log(inspect(0));         // { type: 'number',    truthy: false }
console.log(inspect(null));      // { type: 'null',      truthy: false }
console.log(inspect(undefined)); // { type: 'undefined', truthy: false }
console.log(inspect([]));        // { type: 'object',    truthy: true  }
console.log(inspect(42n));       // { type: 'bigint',    truthy: true  }

Notice that [] reports 'object' (arrays are reference types, not primitives) and is truthy even though it's empty — a classic trap.

🎯 Quick Quiz

Question 1: How many primitive types does JavaScript have?

Question 2: Which of these values is truthy?

Question 3: What does typeof null return?

Summary & Quiz

🎉 Key Takeaways

  • JavaScript has seven primitives: string, number, boolean, undefined, null, symbol, bigint.
  • Primitives are immutable and compared by value; objects are mutable and compared by reference.
  • Numbers are 64-bit floats, so watch for precision issues like 0.1 + 0.2.
  • There are exactly eight falsy values; everything else is truthy.
  • undefined means "not set yet"; null means "intentionally empty" — and typeof null is a famous "object" quirk.

📚 Further Reading

🚀 What's Next?

Now that you know the types, the natural next question is what happens when they mix. Up next we'll explore type coercion and conversion — how JavaScript turns one type into another, both when you ask and when you don't.

🎉 Nice work!

You've met all seven atoms of JavaScript. Everything you build from here is a combination of these.