Skip to main content

JavaScript Data Types: Primitive Types and Objects

Every value in JavaScript has a type. When you write 42, JavaScript knows it is a number. When you write "hello", it knows it is a string. Understanding data types is essential because the type of a value determines what operations you can perform on it, how it behaves in comparisons, and how it is stored in memory.

JavaScript has eight data types: seven primitives (number, bigint, string, boolean, null, undefined, symbol) and one non-primitive (object). This guide explores each type in depth, explains how JavaScript's dynamic type system works, and covers the critical difference between how primitives and objects are stored in memory.

JavaScript's Type System: Dynamic and Weakly Typed

Before diving into individual types, you need to understand two defining characteristics of JavaScript's type system.

Dynamically Typed

In JavaScript, variables do not have fixed types. A variable can hold a number one moment and a string the next. The type belongs to the value, not to the variable:

let data = 42;              // data holds a number
console.log(typeof data); // "number"

data = "hello"; // now data holds a string
console.log(typeof data); // "string"

data = true; // now data holds a boolean
console.log(typeof data); // "boolean"

In statically typed languages like Java or TypeScript, this would be an error. In JavaScript, it is perfectly valid. This flexibility makes JavaScript easy to get started with, but it also means the language will not catch type-related mistakes at development time.

Weakly Typed (Loose Type Coercion)

JavaScript automatically converts values between types when it thinks a conversion makes sense. This is called type coercion, and it can produce surprising results:

console.log("5" + 3);     // "53" (number 3 converted to string, concatenated)
console.log("5" - 3); // 2 (string "5" converted to number, subtracted)
console.log(true + 1); // 2 (true converted to 1)
console.log("5" * "3"); // 15 (both strings converted to numbers)
console.log("hello" * 2); // NaN ("hello" can't convert to a number)

Compare this with Python, which is strongly typed and would throw an error instead of guessing:

# Python raises an error
"5" + 3 # TypeError: can only concatenate str to str
warning

Implicit type coercion is one of the most common sources of bugs in JavaScript. We cover this topic in full detail in the Type Conversions chapter. For now, be aware that JavaScript will silently convert types, and the results are not always what you expect.

Primitive Types Overview

JavaScript has seven primitive types. A primitive is a value that is not an object, has no methods of its own (though it can use methods via auto-boxing), and is immutable (cannot be changed, only replaced).

TypeExample Valuestypeof Result
number42, 3.14, NaN, Infinity"number"
bigint9007199254740993n, 0n"bigint"
string"hello", 'world', `template`"string"
booleantrue, false"boolean"
nullnull"object" (bug!)
undefinedundefined"undefined"
symbolSymbol("id")"symbol"

Plus one non-primitive:

TypeExample Valuestypeof Result
object{}, [], new Date(), function(){}"object" (or "function")

number: Integers, Floats, Infinity, NaN, and Numeric Limits

The number type represents both integers and floating-point numbers. Unlike many languages that have separate int and float types, JavaScript uses a single type for all numeric values.

Integers and Floating-Point Numbers

let integer = 42;
let negative = -17;
let float = 3.14;
let scientific = 2.5e6; // 2,500,000 (2.5 × 10^6)
let tiny = 1.5e-3; // 0.0015 (1.5 × 10^-3)

console.log(typeof integer); // "number"
console.log(typeof float); // "number" (same type for both)

Numeric Separators (ES2021)

For large numbers, you can use underscores as visual separators. They have no effect on the value:

let billion = 1_000_000_000;
let bytes = 0xFF_FF_FF;
let budget = 9_999.99;

console.log(billion); // 1000000000

Special Numeric Values

JavaScript has three special numeric values that are still of type number:

Infinity and -Infinity:

console.log(1 / 0);               // Infinity
console.log(-1 / 0); // -Infinity
console.log(Infinity + 100); // Infinity

console.log(typeof Infinity); // "number"

// Checking for infinity
console.log(isFinite(42)); // true
console.log(isFinite(Infinity)); // false
console.log(isFinite(NaN)); // false

NaN (Not a Number):

NaN represents a failed numeric operation. Despite its name, typeof NaN returns "number":

console.log("hello" * 2);     // NaN
console.log(Math.sqrt(-1)); // NaN
console.log(0 / 0); // NaN
console.log(parseInt("abc")); // NaN

console.log(typeof NaN); // "number" (yes, really)

NaN has a uniquely strange property: it is not equal to anything, including itself:

console.log(NaN === NaN);  // false (the only value in JS not equal to itself!)
console.log(NaN == NaN); // false

To check if a value is NaN, use Number.isNaN():

// WRONG way to check for NaN
let result = "hello" * 2;
console.log(result === NaN); // false (always false!)

// RIGHT way to check for NaN
console.log(Number.isNaN(result)); // true
console.log(Number.isNaN(42)); // false
console.log(Number.isNaN("hello")); // false (it's a string, not NaN)
caution

Do not confuse Number.isNaN() with the older global isNaN() function. The global version converts its argument to a number first, which leads to misleading results:

isNaN("hello");         // true  ("hello" is converted to NaN first)
Number.isNaN("hello"); // false ("hello" is a string, not the value NaN)

isNaN("123"); // false ("123" converts to 123)
Number.isNaN("123"); // false (correct for the same reason, but more reliable)

Always use Number.isNaN().

Floating-Point Precision

JavaScript uses the IEEE 754 double-precision format (64-bit floating point), which means some decimal numbers cannot be represented exactly:

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

This is not a JavaScript bug. It happens in virtually every programming language that uses floating-point arithmetic. The fix depends on context:

// Solution 1: Round the result
console.log((0.1 + 0.2).toFixed(2)); // "0.30" (string)
console.log(Number((0.1 + 0.2).toFixed(2))); // 0.3 (number)

// Solution 2: Compare with a small tolerance
console.log(Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON); // true

// Solution 3: Work in cents/integers for money
let priceInCents = 499; // $4.99 stored as 499 cents

Safe Integer Range

The number type can safely represent integers between -(2^53 - 1) and 2^53 - 1:

console.log(Number.MAX_SAFE_INTEGER);                 // 9007199254740991
console.log(Number.MIN_SAFE_INTEGER); // -9007199254740991

// Beyond this range, precision is lost
console.log(9007199254740991 + 1); // 9007199254740992 (correct)
console.log(9007199254740991 + 2); // 9007199254740992 (WRONG! should be ...993)

console.log(Number.isSafeInteger(9007199254740991)); // true
console.log(Number.isSafeInteger(9007199254740992)); // false

For numbers beyond this range, use BigInt.

bigint: Working with Arbitrarily Large Numbers

BigInt was introduced in ES2020 to represent integers of arbitrary size, beyond the safe integer limit of the regular number type.

Creating BigInts

// Add 'n' suffix to create a BigInt literal
let big = 9007199254740993n;
console.log(big); // 9007199254740993n
console.log(typeof big); // "bigint"

// Or use the BigInt() constructor
let alsoBig = BigInt("9007199254740993");
console.log(alsoBig); // 9007199254740993n

// Regular number loses precision, BigInt does not
console.log(9007199254740993); // 9007199254740992 (wrong!)
console.log(9007199254740993n); // 9007199254740993n (correct)

BigInt Operations

BigInts support standard arithmetic, but cannot be mixed with regular numbers:

let a = 100n;
let b = 200n;

console.log(a + b); // 300n
console.log(a * b); // 20000n
console.log(b / a); // 2n (integer division, no decimals)
console.log(b ** 3n); // 8000000n

// Mixing BigInt and number throws an error
console.log(100n + 50); // TypeError: Cannot mix BigInt and other types

To mix them, you must explicitly convert:

let bigVal = 100n;
let numVal = 50;

console.log(bigVal + BigInt(numVal)); // 150n
console.log(Number(bigVal) + numVal); // 150 (only if bigVal is within safe range)

When to Use BigInt

BigInt is needed for very specific use cases: cryptographic calculations, database IDs larger than 2^53, financial computations requiring exact large integers, or working with APIs that return large numbers as strings.

For everyday programming, the regular number type is sufficient.

string: Text Data, Quotes, and Template Literals

Strings represent text data. Every piece of text in JavaScript, from a single character to an entire book, is a string.

Three Ways to Create Strings

JavaScript offers three quoting styles:

let single = 'Hello, World!';
let double = "Hello, World!";
let backtick = `Hello, World!`;

// Single and double quotes are functionally identical
// Backticks have special features (template literals)

Single and double quotes behave the same way. The choice between them is purely a style preference. Most JavaScript projects use single quotes (Airbnb style guide) or double quotes (Prettier default) consistently.

Template Literals (Backticks)

Backticks create template literals, which have two powerful features:

String interpolation with ${ }:

let name = "Alice";
let age = 30;
let greeting;

// Without template literals (concatenation)
greeting = "Hello, " + name + "! You are " + age + " years old.";

// With template literals (interpolation) -> cleaner and easier to read
greeting = `Hello, ${name}! You are ${age} years old.`;
console.log(greeting); // "Hello, Alice! You are 30 years old."

// Any expression works inside ${}
console.log(`2 + 3 = ${2 + 3}`); // "2 + 3 = 5"
console.log(`Uppercase: ${name.toUpperCase()}`); // "Uppercase: ALICE"
console.log(`Is adult: ${age >= 18}`); // "Is adult: true"

Multi-line strings:

let poem;

// Without backticks: must use \n for newlines
poem = "Roses are red,\nViolets are blue,\nJavaScript is awesome,\nAnd so are you.";

// With backticks: line breaks are preserved naturally
poem = `Roses are red,
Violets are blue,
JavaScript is awesome,
And so are you.`;

console.log(poem);
// Roses are red,
// Violets are blue,
// JavaScript is awesome,
// And so are you.

Escape Characters

Special characters are inserted using a backslash \:

EscapeCharacter
\nNew line
\tTab
\\Backslash
\'Single quote (in single-quoted strings)
\"Double quote (in double-quoted strings)
\`Backtick (in template literals)
let path = "C:\\Users\\Alice\\Documents";
console.log(path); // C:\Users\Alice\Documents

let quote = 'She said, "It\'s amazing!"';
console.log(quote); // She said, "It's amazing!"

String Immutability

Strings in JavaScript are immutable. You cannot change individual characters. Any operation that appears to modify a string actually creates a new one:

let greeting = "Hello";
greeting[0] = "J"; // No error, but no effect either
console.log(greeting); // "Hello" (unchanged)

// To "change" a string, create a new one
let newGreeting = "J" + greeting.slice(1);
console.log(newGreeting); // "Jello"

boolean: true, false, and Truthy/Falsy Values

The boolean type has exactly two values: true and false. Booleans are the foundation of all conditional logic.

let isActive = true;
let isDeleted = false;

console.log(typeof isActive); // "boolean"

Boolean from Comparisons

Comparisons always produce boolean values:

console.log(5 > 3);       // true
console.log(10 === 10); // true
console.log("a" < "b"); // true
console.log(1 === "1"); // false (strict equality, different types)

Truthy and Falsy Values

In JavaScript, every value has an inherent boolean nature. When used in a boolean context (like an if condition), values are automatically converted to true or false.

Falsy values (exactly 8 values that convert to false):

// These are ALL the falsy values in JavaScript:
console.log(Boolean(false)); // false
console.log(Boolean(0)); // false
console.log(Boolean(-0)); // false
console.log(Boolean(0n)); // false (BigInt zero)
console.log(Boolean("")); // false (empty string)
console.log(Boolean(null)); // false
console.log(Boolean(undefined)); // false
console.log(Boolean(NaN)); // false

Everything else is truthy:

// These might surprise you, they are all truthy:
console.log(Boolean("0")); // true - non-empty string!
console.log(Boolean(" ")); // true - string with a space!
console.log(Boolean("false")); // true - string containing the word "false"!
console.log(Boolean([])); // true - empty array!
console.log(Boolean({})); // true - empty object!
console.log(Boolean(function(){})); // true - function!
console.log(Boolean(-1)); // true any non-zero number!
console.log(Boolean(Infinity)); // true
warning

The values "0", " ", "false", [], and {} are all truthy. This catches many developers off guard. An empty array and an empty object are truthy because they are objects, and all objects are truthy.

Practical Usage

let username = "";

if (username) {
console.log(`Welcome, ${username}!`);
} else {
console.log("Please enter a username."); // This runs -> empty string is falsy
}

let items = [];
if (items.length) {
console.log("You have items in your cart.");
} else {
console.log("Your cart is empty."); // This runs -> 0 is falsy
}

null: Intentional Absence of Value

null represents the deliberate absence of a value. It is used when you want to explicitly say "this variable has no value" or "this value is empty on purpose."

let selectedUser = null;          // No user is selected yet
console.log(selectedUser); // null
console.log(typeof selectedUser); // "object" (this is a known bug, see below)

// Practical usage
function findUser(id) {
let user = database.find(u => u.id === id);
if (user) {
return user;
}
return null; // Explicitly signals: "I searched, but found nothing"
}

let result = findUser(999);
if (result === null) {
console.log("User not found");
}

null is a value you assign intentionally. If a variable is null, it means a developer put it there.

undefined: Uninitialized and Missing Values

undefined means a value has not been assigned. It is JavaScript's way of saying "this exists, but nothing has been put here yet."

let age;
console.log(age); // undefined -> declared but never assigned
console.log(typeof age); // "undefined"

Where undefined Appears

JavaScript returns undefined in several situations:

// 1. Declared but uninitialized variables
let name;
console.log(name); // undefined

// 2. Missing function parameters
function greet(name) {
console.log(name);
}
greet(); // undefined -> no argument was passed

// 3. Functions that don't return a value
function doSomething() {
let x = 1 + 2;
// no return statement
}
console.log(doSomething()); // undefined

// 4. Accessing non-existent object properties
let user = { name: "Alice" };
console.log(user.age); // undefined -> property doesn't exist

// 5. Array elements that don't exist
let arr = [1, 2, 3];
console.log(arr[10]); // undefined -> index out of bounds

null vs. undefined: The Difference

Aspectnullundefined
MeaningIntentional empty valueValue not yet assigned
Set byThe developer, deliberatelyJavaScript, automatically
Use case"I checked, and there's nothing here""This hasn't been initialized"
typeof"object" (bug)"undefined"
In arithmeticConverts to 0Converts to NaN
console.log(null + 5);            // 5   (null → 0)
console.log(undefined + 5); // NaN (undefined → NaN)

console.log(null == undefined); // true (loose equality)
console.log(null === undefined); // false (strict equality, different types)

symbol: Unique Identifiers (Introduction)

Symbol is a primitive type introduced in ES6 that creates guaranteed unique identifiers. Even if you create two symbols with the same description, they are different values.

let id1 = Symbol("id");
let id2 = Symbol("id");

console.log(id1 === id2); // false (every Symbol is unique)
console.log(typeof id1); // "symbol"

Symbols are primarily used as unique object property keys that do not conflict with other properties and are not visible in normal iteration:

let id = Symbol("userId");

let user = {
name: "Alice",
[id]: 12345 // Symbol as a property key
};

console.log(user.name); // "Alice"
console.log(user[id]); // 12345

// Symbols are hidden from normal enumeration
console.log(Object.keys(user)); // ["name"] (symbol key not listed)

Symbols are an advanced topic. They are used for creating hidden properties, implementing well-known protocols (like making objects iterable), and avoiding property name collisions. We cover them in full detail in a dedicated chapter later in the course.

object: The Non-Primitive Type (Preview)

The object type is the only non-primitive type in JavaScript. While primitives hold a single value, objects can hold collections of values and more complex entities.

// Object literal
let user = {
name: "Alice",
age: 30,
isAdmin: false
};

// Array (a special type of object)
let colors = ["red", "green", "blue"];

// Function (also an object)
function greet(name) {
return `Hello, ${name}!`;
}

// Date (built-in object)
let now = new Date();

console.log(typeof user); // "object"
console.log(typeof colors); // "object"
console.log(typeof greet); // "function" (special case, technically an object)
console.log(typeof now); // "object"

Objects are covered extensively in their own module. The key point for now is that everything that is not a primitive is an object, including arrays, functions, dates, regular expressions, and errors.

The typeof Operator and Its Quirks

The typeof operator returns a string indicating the type of a value. It is the primary way to check types at runtime.

console.log(typeof 42);           // "number"
console.log(typeof 3.14); // "number"
console.log(typeof NaN); // "number"
console.log(typeof Infinity); // "number"
console.log(typeof 42n); // "bigint"
console.log(typeof "hello"); // "string"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof Symbol("id")); // "symbol"
console.log(typeof {}); // "object"
console.log(typeof []); // "object" (arrays are objects!)
console.log(typeof null); // "object" (this is a BUG)
console.log(typeof function(){}); // "function" (special case)

The typeof null Bug

The most famous typeof quirk:

console.log(typeof null);  // "object" (this is WRONG)

null is not an object. This is a bug from the very first version of JavaScript (1995) that was never fixed because changing it would break too much existing code. The null type is its own primitive type.

To check for null, compare directly instead of using typeof:

let value = null;

// WRONG: doesn't work for null
if (typeof value === "object") {
// This runs for null too. Misleading!
}

// CORRECT: check for null explicitly
if (value === null) {
console.log("Value is null");
}

// SAFE: check for object that is not null
if (value !== null && typeof value === "object") {
console.log("Value is a real object");
}

Checking for Arrays

Since typeof [] returns "object", you need Array.isArray() to check if something is an array:

let items = [1, 2, 3];
let user = { name: "Alice" };

console.log(typeof items); // "object" (not helpful!)
console.log(typeof user); // "object" (same result!)

console.log(Array.isArray(items)); // true
console.log(Array.isArray(user)); // false

Complete typeof Results Table

Valuetypeof ResultNotes
42"number"
NaN"number"NaN is technically a number
42n"bigint"
"hello""string"
true"boolean"
undefined"undefined"
Symbol()"symbol"
null"object"Bug! Should be "null"
{}"object"
[]"object"Use Array.isArray() instead
function(){}"function"Functions are objects, but get special treatment
new Date()"object"

Primitive vs. Reference Types: Understanding Memory

This is one of the most important concepts in JavaScript. Primitives and objects are stored and copied differently, and misunderstanding this leads to some of the most common bugs.

Primitives: Stored by Value

When you assign a primitive value to a variable, the actual value is copied:

let a = 10;
let b = a; // The VALUE 10 is copied to b

b = 20; // Changing b does NOT affect a

console.log(a); // 10 (unchanged)
console.log(b); // 20

Each variable has its own independent copy of the value. Changing one has no effect on the other.

let greeting = "Hello";
let copy = greeting;

copy = "World";

console.log(greeting); // "Hello" (unchanged)
console.log(copy); // "World"

Objects: Stored by Reference

When you assign an object to a variable, the variable does not contain the object itself. It contains a reference (a pointer, an address) to where the object lives in memory:

let user = { name: "Alice", age: 30 };
let admin = user; // The REFERENCE is copied, not the object

admin.name = "Bob"; // Modifying through 'admin'...

console.log(user.name); // "Bob" (user is affected too!)
console.log(admin.name); // "Bob"
// Both variables point to the SAME object in memory

Visualize it like this:

user  ──────┐
├──→ { name: "Bob", age: 30 } (one object in memory)
admin ──────┘

Comparing Primitives vs. Objects

Primitives are compared by value, objects are compared by reference:

// Primitives: compared by value
let x = "hello";
let y = "hello";
console.log(x === y); // true (same value)

// Objects: compared by reference
let obj1 = { name: "Alice" };
let obj2 = { name: "Alice" };
console.log(obj1 === obj2); // false (different objects in memory!)

let obj3 = obj1;
console.log(obj1 === obj3); // true (same reference)

Even though obj1 and obj2 contain identical data, they are different objects stored at different memory locations. The === operator checks whether both variables point to the same object, not whether the objects have the same contents.

Arrays Are Objects

The same reference behavior applies to arrays:

let original = [1, 2, 3];
let copy = original; // Reference copied, not the array

copy.push(4);

console.log(original); // [1, 2, 3, 4] (original is modified!)
console.log(copy); // [1, 2, 3, 4]

To create an independent copy of an array, use the spread operator or slice():

let original = [1, 2, 3];
let trueCopy = [...original]; // Spread creates a new array

trueCopy.push(4);

console.log(original); // [1, 2, 3] (unchanged)
console.log(trueCopy); // [1, 2, 3, 4]

Common Mistake: Confusing null and undefined

Mistake 1: Using undefined as an Intentional Empty Value

// BAD: using undefined to "reset" a variable
let currentUser = getUser();
// later...
currentUser = undefined; // Don't do this

Fix: Use null to intentionally indicate "no value":

// GOOD: null clearly communicates intent
let currentUser = getUser();
// later...
currentUser = null; // Explicitly set to "no user"

undefined should be reserved for JavaScript's own use (uninitialized variables, missing parameters). When you want to set something to empty, use null.

Mistake 2: Checking for Both null and undefined Incorrectly

let value = null;

// Overly verbose: checking each separately
if (value === null || value === undefined) {
console.log("No value");
}

// Simpler: loose equality covers both
if (value == null) {
console.log("No value"); // Catches both null AND undefined
}
note

This is one of the FEW acceptable uses of == instead of ===:

  • null == undefined is true
  • null == 0 is false
  • null == "" is false
  • null == false is false

It ONLY matches null and undefined, nothing else

Mistake 3: Not Checking for null Before Accessing Properties

let user = null;

// This crashes
console.log(user.name); // TypeError: Cannot read properties of null

// Always check first
if (user !== null) {
console.log(user.name);
}

// Or use optional chaining (ES2020)
console.log(user?.name); // undefined (no error)

Mistake 4: Assuming typeof Detects null

let value = null;

if (typeof value === "null") {
// This NEVER runs: typeof null returns "object", not "null"
}

// Correct check
if (value === null) {
console.log("Value is null");
}

Mistake 5: Confusing Falsy With null/undefined

let count = 0;
let name = "";

// BAD: this treats 0 and "" the same as null/undefined
if (!count) {
console.log("No count"); // Runs! But 0 might be a valid count
}
if (!name) {
console.log("No name"); // Runs! But "" might be an intentional empty string
}

// BETTER: check specifically for null/undefined when 0 or "" are valid values
if (count == null) {
console.log("Count is null or undefined"); // Does NOT run: 0 is not null
}
if (name == null) {
console.log("Name is null or undefined"); // Does NOT run: "" is not null
}

This becomes especially important with the nullish coalescing operator ??, which you will learn about in the logical operators chapter.

Summary

JavaScript has eight data types, and understanding each one is foundational to everything else you will learn:

  • number handles integers, floats, Infinity, and NaN. Be aware of floating-point precision issues and the safe integer range.
  • bigint represents integers beyond Number.MAX_SAFE_INTEGER. Use the n suffix to create BigInt literals.
  • string represents text. Use template literals (backticks) for interpolation and multi-line strings. Strings are immutable.
  • boolean has two values: true and false. Every value in JavaScript is either truthy or falsy. Memorize the eight falsy values.
  • null is an intentional empty value assigned by the developer.
  • undefined means a value has not been assigned. It is JavaScript's default for uninitialized variables, missing parameters, and non-existent properties.
  • symbol creates unique identifiers, primarily used as hidden object property keys.
  • object is the only non-primitive type. Arrays, functions, dates, and everything that is not a primitive are objects.
  • typeof checks the type of a value, but has quirks: typeof null === "object" (bug) and typeof [] === "object" (use Array.isArray()).
  • Primitives are copied by value, meaning each variable gets its own independent copy. Objects are copied by reference, meaning multiple variables can point to the same object in memory.
  • Use null for intentional empty values, and let JavaScript use undefined for uninitialized state.

With a solid grasp of data types, you are ready to learn about type conversions, where JavaScript's dynamic nature truly reveals its quirks and powers.