JavaScript Data Types Overview
JavaScript has two main categories of data types:
- Primitive Types: Basic data types that are immutable (cannot be changed)
- Reference Types: Complex data types like objects, arrays, and functions
Today, we'll focus on the primitive data types, which are the fundamental building blocks of JavaScript.
Analogy: Primitive Types as Atoms
Think of primitive data types as the atoms of programming—they're the simplest, indivisible units that everything else is built from. Just as atoms combine to form molecules with completely different properties (like hydrogen and oxygen forming water), JavaScript primitives combine to create complex data structures with entirely new behaviors and capabilities.
String Type
Strings represent text data in JavaScript. They are sequences of characters enclosed in quotes.
Creating Strings
// Three ways to create a string
const singleQuotes = 'Hello world';
const doubleQuotes = "Hello world";
const backticks = `Hello world`; // Template literals (ES6)
String Properties
const text = "JavaScript";
// Length property
console.log(text.length); // 10
// Accessing characters
console.log(text[0]); // "J"
console.log(text[4]); // "S"
// Strings are immutable - they cannot be changed
text[0] = "j"; // This does nothing
console.log(text); // Still "JavaScript"
Common String Methods
const phrase = "The quick brown fox";
// Converting case
console.log(phrase.toUpperCase()); // "THE QUICK BROWN FOX"
console.log(phrase.toLowerCase()); // "the quick brown fox"
// Finding substrings
console.log(phrase.indexOf("quick")); // 4
console.log(phrase.includes("brown")); // true
console.log(phrase.startsWith("The")); // true
console.log(phrase.endsWith("dog")); // false
// Extracting substrings
console.log(phrase.slice(4, 9)); // "quick"
console.log(phrase.substring(4, 9)); // "quick"
console.log(phrase.split(" ")); // ["The", "quick", "brown", "fox"]
// Trimming whitespace
const paddedText = " spaced out ";
console.log(paddedText.trim()); // "spaced out"
console.log(paddedText.trimStart()); // "spaced out "
console.log(paddedText.trimEnd()); // " spaced out"
// Replacing content
console.log(phrase.replace("fox", "dog")); // "The quick brown dog"
console.log(phrase.replaceAll("o", "0")); // "The quick br0wn f0x"
Template Literals (ES6+)
const name = "Alice";
const age = 28;
// String concatenation (old way)
const greeting1 = "Hello, " + name + "! You are " + age + " years old.";
// Template literals with variable interpolation
const greeting2 = `Hello, ${name}! You are ${age} years old.`;
console.log(greeting1); // "Hello, Alice! You are 28 years old."
console.log(greeting2); // "Hello, Alice! You are 28 years old."
// Multi-line strings
const multiLine = `This is a string
that spans across
multiple lines.`;
console.log(multiLine);
// This is a string
// that spans across
// multiple lines.
// Expression evaluation in template literals
console.log(`2 + 2 = ${2 + 2}`); // "2 + 2 = 4"
console.log(`${name.toUpperCase()} is ${age < 30 ? 'young' : 'mature'}`); // "ALICE is young"
Real-World Example: Form Validation
Strings are frequently used for form validation in web applications:
function validateEmail(email) {
// Remove whitespace
const trimmedEmail = email.trim();
// Check if empty
if (trimmedEmail.length === 0) {
return "Email is required";
}
// Check for @ symbol
if (!trimmedEmail.includes('@')) {
return "Email must contain an @ symbol";
}
// Check for domain
const parts = trimmedEmail.split('@');
if (parts.length !== 2 || parts[1].length === 0) {
return "Email must have a valid domain";
}
// Check domain has at least one dot
if (!parts[1].includes('.')) {
return "Email domain must contain a dot";
}
// Additional check for domain extension
const domainParts = parts[1].split('.');
const extension = domainParts[domainParts.length - 1];
if (extension.length < 2) {
return "Email domain must have a valid extension";
}
return null; // No errors
}
// Test the validation
const emails = [
"user@example.com",
"invalid-email",
"no@domain@test.com",
"missing.extension@test",
" spaces@example.com "
];
emails.forEach(email => {
const error = validateEmail(email);
if (error) {
console.log(`"${email}" is invalid: ${error}`);
} else {
console.log(`"${email}" is valid`);
}
});
Number Type
The Number type represents both integer and floating-point numbers in JavaScript. Unlike some other programming languages, JavaScript doesn't have separate types for integers and floats.
Creating Numbers
// Integer values
const integer = 42;
const negativeNum = -17;
// Floating point (decimal) values
const float = 3.14159;
const negativeFloat = -2.718;
// Scientific notation
const largeNumber = 1.2e6; // 1.2 * 10^6 = 1,200,000
const tinyNumber = 5e-7; // 5 * 10^-7 = 0.0000005
// Binary, octal and hexadecimal notations
const binary = 0b1010; // 10 in decimal
const octal = 0o744; // 484 in decimal
const hex = 0xFF; // 255 in decimal
Special Number Values
// Infinity values
const positiveInfinity = Infinity; // or Number.POSITIVE_INFINITY
const negativeInfinity = -Infinity; // or Number.NEGATIVE_INFINITY
// Not a Number (NaN)
const notANumber = NaN; // or Number.NaN
// Checking for NaN
console.log(isNaN(NaN)); // true
console.log(isNaN("hello")); // true (string isn't a number)
console.log(isNaN(42)); // false
// More precise check
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN("hello")); // false (specifically checks for NaN value)
Number Range and Precision
// Smallest and largest safe integers
console.log(Number.MIN_SAFE_INTEGER); // -9007199254740991
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
// Smallest and largest values
console.log(Number.MIN_VALUE); // 5e-324 (closest to zero)
console.log(Number.MAX_VALUE); // 1.7976931348623157e+308
// Precision issues with floating point
console.log(0.1 + 0.2); // 0.30000000000000004, not exactly 0.3
console.log(0.1 + 0.2 === 0.3); // false
// Handling precision issues
console.log(Math.abs((0.1 + 0.2) - 0.3) < Number.EPSILON); // true
console.log(Number((0.1 + 0.2).toFixed(1)) === 0.3); // true
Number Methods
const num = 42.3567;
// Conversion methods
console.log(num.toString()); // "42.3567"
console.log(num.toFixed(2)); // "42.36" (string with 2 decimal places)
console.log(num.toPrecision(3)); // "42.4" (string with 3 significant digits)
console.log(num.toExponential()); // "4.23567e+1" (exponential notation)
// Parsing strings to numbers
console.log(parseInt("42")); // 42
console.log(parseInt("42.95")); // 42 (truncates decimal part)
console.log(parseInt("42px")); // 42 (stops at non-numeric character)
console.log(parseInt("0xFF", 16)); // 255 (parses hex with radix 16)
console.log(parseFloat("42.95")); // 42.95
console.log(parseFloat("42")); // 42
console.log(parseFloat("42.0px")); // 42 (stops at non-numeric character)
// Number constructor
console.log(Number("42")); // 42
console.log(Number("42.5")); // 42.5
console.log(Number(" 42 ")); // 42 (trims whitespace)
console.log(Number("42px")); // NaN (doesn't parse partial numbers)
console.log(Number(true)); // 1
console.log(Number(false)); // 0
console.log(Number(null)); // 0
console.log(Number(undefined)); // NaN
The Math Object
// Mathematical constants
console.log(Math.PI); // 3.141592653589793
console.log(Math.E); // 2.718281828459045
// Rounding methods
console.log(Math.round(4.7)); // 5
console.log(Math.round(4.4)); // 4
console.log(Math.floor(4.9)); // 4 (rounds down)
console.log(Math.ceil(4.1)); // 5 (rounds up)
console.log(Math.trunc(4.9)); // 4 (removes decimal part)
// Min, max and absolute
console.log(Math.min(5, 10, 3, 8)); // 3
console.log(Math.max(5, 10, 3, 8)); // 10
console.log(Math.abs(-42)); // 42
// Power and square root
console.log(Math.pow(2, 3)); // 8 (2^3)
console.log(Math.sqrt(16)); // 4
console.log(Math.cbrt(27)); // 3 (cube root)
// Random numbers
console.log(Math.random()); // Random number between 0 (inclusive) and 1 (exclusive)
// Generate random integer between min and max (inclusive)
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
console.log(getRandomInt(1, 6)); // Random dice roll (1-6)
Analogy: Floating-Point Precision
JavaScript's floating-point precision issues are like trying to represent 1/3 in decimal form: you can get close (0.333333...), but you can never represent it exactly with a finite number of digits. Similarly, many decimal fractions (like 0.1 and 0.2) can't be represented exactly in binary floating-point format, leading to tiny rounding errors that become visible when you perform calculations.
Real-World Example: Shopping Cart Total
When building an e-commerce application, you need to handle monetary calculations carefully:
function calculateCartTotal(items) {
// Calculate subtotal
let subtotal = 0;
items.forEach(item => {
// Price in dollars, e.g. 10.99
const itemTotal = item.price * item.quantity;
subtotal += itemTotal;
});
// Fix precision issues by converting to cents for calculations
const subtotalCents = Math.round(subtotal * 100);
// Calculate tax (for example, 8.25%)
const taxRate = 0.0825;
const taxCents = Math.round(subtotalCents * taxRate);
// Calculate total
const totalCents = subtotalCents + taxCents;
// Convert back to dollars for display
return {
subtotal: (subtotalCents / 100).toFixed(2),
tax: (taxCents / 100).toFixed(2),
total: (totalCents / 100).toFixed(2)
};
}
// Test with a sample cart
const cart = [
{ name: 'Widget', price: 9.99, quantity: 2 },
{ name: 'Gadget', price: 12.50, quantity: 1 },
{ name: 'Doohickey', price: 5.95, quantity: 3 }
];
console.log(calculateCartTotal(cart));
Boolean Type
The Boolean type has only two possible values: true and false. Booleans are used for conditional logic and represent the concept of truth and falsehood in JavaScript.
Creating Booleans
// Boolean literals
const isActive = true;
const isComplete = false;
// From comparison operations
const isGreater = 5 > 3; // true
const isEqual = 10 === '10'; // false
// Using the Boolean constructor
const boolTrue = Boolean(1); // true
const boolFalse = Boolean(0); // false
Truthy and Falsy Values
In JavaScript, all values have an inherent Boolean value when used in a Boolean context, known as "truthy" or "falsy."
Falsy values (convert to false):
// All of these are falsy:
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
Truthy values (convert to true):
// All other values are truthy, including:
console.log(Boolean(true)); // true
console.log(Boolean(42)); // true (any non-zero number)
console.log(Boolean("false")); // true (non-empty string, even "false")
console.log(Boolean({})); // true (empty object)
console.log(Boolean([])); // true (empty array)
console.log(Boolean(function(){})); // true (functions)
Logical Operators
// Logical AND (&&) - returns first falsy value or last truthy value
console.log(true && false); // false
console.log(false && anyExpression); // false (short-circuits, doesnt evaluate second expression)
console.log("Hello" && 42 && true); // true (last value)
console.log("Hello" && 0 && true); // 0 (first falsy value)
// Logical OR (||) - returns first truthy value or last falsy value
console.log(true || false); // true
console.log(true || anyExpression); // true (short-circuits)
console.log(false || "" || null); // null (last value)
console.log(false || 0 || "Hello"); // "Hello" (first truthy value)
// Logical NOT (!) - inverts truthiness
console.log(!true); // false
console.log(!false); // true
console.log(!"Hello"); // false (string is truthy)
console.log(!""); // true (empty string is falsy)
// Double NOT (!!) - converts to Boolean
console.log(!!"Hello"); // true
console.log(!!0); // false
Nullish Coalescing Operator (??)
ES2020 introduced the nullish coalescing operator, which is similar to OR but only treats null and undefined as falsy.
// The ?? operator only considers null and undefined as falsy
const value1 = 0 ?? "default"; // 0 (0 is a valid value)
const value2 = "" ?? "default"; // "" (empty string is a valid value)
const value3 = null ?? "default"; // "default"
const value4 = undefined ?? "default"; // "default"
// Compare with OR
const withOr1 = 0 || "default"; // "default" (0 is falsy)
const withOr2 = "" || "default"; // "default" (empty string is falsy)
Analogy: Truthy and Falsy as Light Switches
Think of JavaScript's truthiness as light switches. Some values are like switches that are definitely "off" (falsy values like false, 0, "", null, undefined, and NaN), and all other values are like switches that are definitely "on" (truthy). When JavaScript needs to make a decision based on a condition, it checks whether the switch is on or off, regardless of what type of value it is.
Real-World Example: Form Validation with Conditional Logic
Boolean logic is essential for form validation:
function validateUserInput(username, email, password) {
const errors = {};
// Username validation
if (!username) {
errors.username = "Username is required";
} else if (username.length < 3) {
errors.username = "Username must be at least 3 characters";
}
// Email validation
if (!email) {
errors.email = "Email is required";
} else if (!email.includes('@') || !email.includes('.')) {
errors.email = "Email format is invalid";
}
// Password validation
if (!password) {
errors.password = "Password is required";
} else {
const hasMinLength = password.length >= 8;
const hasUppercase = /[A-Z]/.test(password);
const hasLowercase = /[a-z]/.test(password);
const hasNumber = /[0-9]/.test(password);
if (!(hasMinLength && hasUppercase && hasLowercase && hasNumber)) {
errors.password = "Password must be at least 8 characters with uppercase, lowercase, and numbers";
}
}
// Return validation result
return {
isValid: Object.keys(errors).length === 0,
errors: errors
};
}
// Test the validation
const result = validateUserInput("joe", "invalid-email", "password123");
console.log(result.isValid); // false
console.log(result.errors); // Object with validation errors
Undefined Type
The undefined type has only one value: undefined. It represents a variable that has been declared but not assigned a value.
When Undefined Occurs
// Variable declared but not initialized
let uninitializedVar;
console.log(uninitializedVar); // undefined
// Missing function parameters
function greet(name) {
console.log(`Hello, ${name}`);
}
greet(); // "Hello, undefined"
// Missing object properties
const user = { name: "Alice" };
console.log(user.age); // undefined
// Function with no return statement
function noReturn() {
// No return statement
}
console.log(noReturn()); // undefined
Checking for Undefined
// Using strict equality
function processValue(value) {
if (value === undefined) {
return "No value provided";
}
return `Processing: ${value}`;
}
// Using typeof
function safelyProcessValue(value) {
if (typeof value === "undefined") {
return "No value provided";
}
return `Processing: ${value}`;
}
// Avoid this pattern (global undefined can be overwritten in older browsers)
function badCheck(value) {
if (value == undefined) { // uses loose equality, also matches null
return "No value provided";
}
return `Processing: ${value}`;
}
Optional Chaining (?.) for Undefined
ES2020 introduced optional chaining to safely access nested properties that might be undefined.
// Object with nested properties
const user = {
name: "Alice",
address: {
city: "Wonderland"
}
};
// Old way (error-prone)
let zipCode;
if (user && user.address && user.address.zipCode) {
zipCode = user.address.zipCode;
} else {
zipCode = "Unknown";
}
// With optional chaining
const zipCode2 = user?.address?.zipCode ?? "Unknown";
// Also works with function calls
const result = user.getDetails?.() ?? "Method not available";
Analogy: Undefined as an Empty Slot
Think of undefined as an empty slot in a mailbox. The slot exists (the variable has been declared), but there's no mail in it yet (no value has been assigned). It's different from not having a slot at all (undeclared variable), and it's also different from having a slot with an explicit "no mail" sign (which would be null).
Real-World Example: Default Function Parameters
Handling undefined is common when working with function parameters:
// Old way: Check for undefined and provide defaults
function createUser(username, role, isActive) {
// Check each parameter and provide default if undefined
username = typeof username !== 'undefined' ? username : 'guest';
role = typeof role !== 'undefined' ? role : 'user';
isActive = typeof isActive !== 'undefined' ? isActive : true;
return {
username,
role,
isActive,
createdAt: new Date()
};
}
// ES6+ way: Default parameters
function createUser(username = 'guest', role = 'user', isActive = true) {
return {
username,
role,
isActive,
createdAt: new Date()
};
}
console.log(createUser('alice'));
// { username: 'alice', role: 'user', isActive: true, createdAt: Date }
console.log(createUser(undefined, 'admin'));
// { username: 'guest', role: 'admin', isActive: true, createdAt: Date }
// Note: null will not trigger default parameters
console.log(createUser(null, 'admin'));
// { username: null, role: 'admin', isActive: true, createdAt: Date }
Null Type
The null type has only one value: null. It represents the intentional absence of any object value or intentional non-value.
Using Null
// Explicitly assigning null
let user = null; // User data not yet loaded
// Returning null from a function to indicate "no valid result"
function findUser(id) {
// No user found
return null;
}
// Checking for null
const result = findUser(42);
if (result === null) {
console.log("User not found");
}
Null vs Undefined
| Aspect | null |
undefined |
|---|---|---|
| Meaning | Intentional absence of value | Variable declared but not assigned |
| Assignment | Must be explicitly assigned | Automatically assigned |
| Type | typeof null is "object" (historical bug) |
typeof undefined is "undefined" |
| JSON | Valid JSON value | Not valid in JSON |
| Equality | null == undefined is true (loose equality) |
|
| Strict Equality | null === undefined is false (strict equality) |
|
Checking for Null or Undefined
// Checking for null specifically
if (value === null) {
console.log("Value is null");
}
// Checking for either null or undefined (not recommended)
if (value == null) {
console.log("Value is null or undefined");
}
// Better approach - check each explicitly
if (value === null || value === undefined) {
console.log("Value is null or undefined");
}
// Using nullish coalescing operator
const result = value ?? defaultValue; // result is defaultValue if value is null or undefined
Analogy: null vs undefined
Think of undefined and null like different types of empty:
undefinedis like an empty space on a form that hasn't been filled out yet—nobody has written anything there.nullis like writing "N/A" on a form—you've explicitly indicated that there is no applicable value for this field.
Real-World Example: API Data Handling
When working with APIs, null is often used to represent missing data:
// Function to fetch user data from an API
async function fetchUserProfile(userId) {
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
if (response.status === 404) {
// User not found
return null;
}
throw new Error(`API error: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error("Failed to fetch user:", error);
return null;
}
}
// Using the function
async function displayUserProfile(userId) {
const profile = await fetchUserProfile(userId);
if (profile === null) {
// Handle the "no user" case
displayErrorMessage("User not found or could not be loaded");
return;
}
// Handle the valid user case
displayUserData(profile);
}
// Optional chaining makes dealing with potentially null data easier
function renderUserLocation(user) {
const locationText = user?.address?.city ?? "Location unknown";
return locationText;
}
Symbol Type
Symbols, introduced in ES6, are a primitive type for unique identifiers. Each Symbol value is unique and immutable, making them useful for object property keys that shouldn't conflict with other properties.
Creating Symbols
// Creating a Symbol
const sym1 = Symbol();
const sym2 = Symbol();
console.log(sym1 === sym2); // false, all Symbols are unique
// Symbols with descriptions (for debugging)
const userIdSymbol = Symbol('userId');
console.log(userIdSymbol.toString()); // "Symbol(userId)"
console.log(userIdSymbol.description); // "userId"
// Symbol.for() creates global Symbols
const globalSym1 = Symbol.for('globalId');
const globalSym2 = Symbol.for('globalId');
console.log(globalSym1 === globalSym2); // true, same Symbol from registry
// Get Symbol key from global registry
console.log(Symbol.keyFor(globalSym1)); // "globalId"
console.log(Symbol.keyFor(sym1)); // undefined (not in global registry)
Using Symbols as Object Keys
// Using Symbols as object property keys
const id = Symbol('id');
const user = {
name: "Alice",
[id]: 42 // Symbol as property key
};
console.log(user.name); // "Alice"
console.log(user[id]); // 42
// Symbol properties are not enumerable in typical iterations
console.log(Object.keys(user)); // ["name"]
console.log(Object.getOwnPropertyNames(user)); // ["name"]
// To get Symbol properties
console.log(Object.getOwnPropertySymbols(user)); // [Symbol(id)]
// To get all properties, including Symbols
console.log(Reflect.ownKeys(user)); // ["name", Symbol(id)]
Built-in Symbols
JavaScript has several built-in "well-known" Symbols that allow you to customize object behavior.
// Custom iteration behavior using Symbol.iterator
const customIterable = {
items: ['a', 'b', 'c'],
// Define custom iterator
[Symbol.iterator]: function* () {
for (let item of this.items) {
yield item.toUpperCase();
}
}
};
// Now we can iterate
for (const item of customIterable) {
console.log(item); // "A", "B", "C"
}
// Custom object conversion using Symbol.toPrimitive
const customObject = {
name: "Special Object",
value: 42,
// Define conversion behavior
[Symbol.toPrimitive](hint) {
switch (hint) {
case 'number':
return this.value;
case 'string':
return this.name;
default: // 'default' hint
return `${this.name} (${this.value})`;
}
}
};
console.log(+customObject); // 42 (number conversion)
console.log(`${customObject}`); // "Special Object" (string conversion)
console.log(customObject + ''); // "Special Object (42)" (default conversion)
Analogy: Symbols as VIP Access Badges
Think of Symbols as VIP access badges at an exclusive event. Each badge is unique and can't be duplicated or forged. If you have the badge, you can access special features (object properties) that aren't available to the general public. Also, these badges don't show up in the public guest list (regular enumeration of object properties), giving you a way to add "hidden" properties to objects.
Real-World Example: Creating a Singleton Pattern
Symbols can be used to implement the Singleton pattern in JavaScript:
// Using Symbol for a private instance holder
const instance = Symbol('instance');
class Database {
constructor(connectionString) {
if (Database[instance]) {
return Database[instance];
}
this.connectionString = connectionString;
this.isConnected = false;
console.log("Creating new database connection");
// Store the instance
Database[instance] = this;
}
connect() {
if (this.isConnected) {
console.log("Already connected");
return;
}
console.log(`Connecting to ${this.connectionString}`);
this.isConnected = true;
}
query(sql) {
if (!this.isConnected) {
this.connect();
}
console.log(`Executing query: ${sql}`);
return [`Result for ${sql}`];
}
}
// Usage
const db1 = new Database("mongodb://example.com/mydb");
db1.connect();
const db2 = new Database("mysql://example.com/db"); // No new connection
console.log(db1 === db2); // true - same instance
db2.query("SELECT * FROM users"); // Uses the existing connection
BigInt Type
The BigInt type, introduced in ES2020, represents integers with arbitrary precision, overcoming the limitations of the Number type which can only safely represent integers between -(2^53-1) and 2^53-1.
Creating BigInts
// Using the n suffix
const bigInt1 = 1234567890123456789012345678901234567890n;
// Using the BigInt() function
const bigInt2 = BigInt("9007199254740991");
const bigInt3 = BigInt(9007199254740991); // From Number
// Maximum safe integer in Number type
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
// BigInt can represent integers beyond this limit
const beyondSafeInteger = BigInt(Number.MAX_SAFE_INTEGER) + 1n;
console.log(beyondSafeInteger); // 9007199254740992n
// Compare with regular Number behavior
console.log(Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2); // true (precision loss)
console.log(BigInt(Number.MAX_SAFE_INTEGER) + 1n === BigInt(Number.MAX_SAFE_INTEGER) + 2n); // false (correct)
Operations with BigInt
// Arithmetic operations
console.log(5n + 3n); // 8n
console.log(5n - 3n); // 2n
console.log(5n * 3n); // 15n
console.log(5n / 3n); // 1n (truncates, no floating point)
console.log(5n % 3n); // 2n
console.log(5n ** 3n); // 125n
// Cannot mix BigInt and Number
// console.log(5n + 3); // TypeError
// Workaround: Convert explicitly
console.log(5n + BigInt(3)); // 8n
console.log(Number(5n) + 3); // 8
// Comparison operators work
console.log(5n > 3); // true
console.log(5n === 5); // false (different types)
console.log(5n == 5); // true (loose equality)
// Bitwise operations also work
console.log(8n >> 1n); // 4n
console.log(8n << 1n); // 16n
console.log(8n & 7n); // 0n
console.log(8n | 7n); // 15n
Limitations of BigInt
// Cannot use with Math object
// console.log(Math.max(1n, 2n)); // TypeError
// No decimal point - not for floating point operations
// console.log(5n / 2n); // 2n, not 2.5n
// JSON serialization doesn't work directly
const obj = { value: 123456789123456789n };
// console.log(JSON.stringify(obj)); // TypeError
// Workaround: convert to string
const jsonSafe = { value: obj.value.toString() };
console.log(JSON.stringify(jsonSafe)); // {"value":"123456789123456789"}
Analogy: BigInt as Expandable Storage
Think of regular Numbers as a fixed-size storage box that can only fit values up to a certain size. Once you exceed that size, items get misplaced or damaged (precision loss). BigInts are like expandable storage boxes that can grow to accommodate numbers of any size, no matter how large they get—but they can only store whole items (integers), not partial ones (decimals).
Real-World Example: Cryptography and Financial Calculations
BigInt is particularly useful for cryptography, where large integers are common:
// Basic modular exponentiation (common in crypto)
function modPow(base, exponent, modulus) {
// Convert to BigInt to handle large numbers
base = BigInt(base);
exponent = BigInt(exponent);
modulus = BigInt(modulus);
let result = 1n;
base = base % modulus;
while (exponent > 0n) {
if (exponent % 2n === 1n) {
result = (result * base) % modulus;
}
exponent = exponent >> 1n;
base = (base * base) % modulus;
}
return result;
}
// RSA-like operation (simplified)
// Public key components
const e = 65537n;
const n = 999331999191171934862582056475721892679569n;
// Message to encrypt (convert text to number for demo)
const message = 123456789n;
// Encrypt: (message^e) % n
const encrypted = modPow(message, e, n);
console.log("Encrypted:", encrypted);
// Financial calculations with precise integers
function calculateCompoundInterest(principal, ratePercent, years, compoundingsPerYear) {
// Convert to BigInt and use a scaling factor for decimal calculations
const SCALE = 1000000n; // 6 decimal places of precision
const p = BigInt(Math.floor(principal * 1000000)) * SCALE / 1000000n;
const r = BigInt(Math.floor(ratePercent * 1000000)) * SCALE / 1000000n;
const t = BigInt(Math.floor(years * 1000000)) * SCALE / 1000000n;
const n = BigInt(Math.floor(compoundingsPerYear * 1000000)) * SCALE / 1000000n;
// Calculate (1 + r/n)^(n*t)
const base = SCALE * n + r * SCALE / (n * 100n); // (1 + r/n) scaled
const exponent = n * t / SCALE;
// Integer power calculation
let result = SCALE;
let expBase = base;
let exp = exponent;
while (exp > 0n) {
if (exp % 2n === 1n) {
result = (result * expBase) / SCALE;
}
exp = exp / 2n;
expBase = (expBase * expBase) / SCALE;
}
// Final amount = principal * result
const amount = (p * result) / (SCALE * SCALE);
return Number(amount) / 1000000;
}
Type Checking and Conversion
Being able to check and convert between primitive types is essential for robust JavaScript programming.
The typeof Operator
// Using typeof to check types
console.log(typeof "Hello"); // "string"
console.log(typeof 42); // "number"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof Symbol()); // "symbol"
console.log(typeof 42n); // "bigint"
// Quirks of typeof
console.log(typeof null); // "object" (historical bug)
console.log(typeof []); // "object" (arrays are objects)
console.log(typeof function() {}); // "function"
console.log(typeof NaN); // "number" (NaN is a number)
Type Conversion
// String conversions
console.log(String(42)); // "42"
console.log(String(true)); // "true"
console.log(String(null)); // "null"
console.log(String(undefined)); // "undefined"
console.log(String(Symbol('sym'))); // "Symbol(sym)"
console.log(String(123n)); // "123"
// Number conversions
console.log(Number("42")); // 42
console.log(Number("42.5")); // 42.5
console.log(Number("")); // 0
console.log(Number(true)); // 1
console.log(Number(false)); // 0
console.log(Number(null)); // 0
console.log(Number(undefined)); // NaN
console.log(Number("hello")); // NaN
console.log(Number(42n)); // 42
// Boolean conversions
console.log(Boolean(42)); // true
console.log(Boolean(0)); // false
console.log(Boolean("hello")); // true
console.log(Boolean("")); // false
console.log(Boolean(null)); // false
console.log(Boolean(undefined)); // false
console.log(Boolean(NaN)); // false
// BigInt conversions
console.log(BigInt(42)); // 42n
console.log(BigInt("42")); // 42n
console.log(BigInt(true)); // 1n
// console.log(BigInt(1.5)); // RangeError
// console.log(BigInt("1.5")); // SyntaxError
Implicit Type Conversion (Coercion)
// String coercion with +
console.log("2" + 3); // "23" (number converted to string)
console.log(2 + "3"); // "23" (number converted to string)
// Numeric coercion with comparison
console.log("2" > 1); // true (string converted to number)
console.log("2" < "12"); // false (string comparison, not numeric)
// Boolean coercion in logical operations
console.log("hello" && 42); // 42 (both truthy)
console.log(0 || "hello"); // "hello" (0 is falsy)
console.log(null || undefined); // undefined (both falsy)
// Numeric coercion with mathematical operators
console.log("5" - 2); // 3 (string converted to number)
console.log("5" * 2); // 10 (string converted to number)
console.log("5" / 2); // 2.5 (string converted to number)
Real-World Example: Processing User Input
Type checking and conversion is essential when dealing with user input:
// Form processing function
function processUserSettings(formData) {
// Validate and normalize settings
const settings = {
// Username - ensure string, trim, default if empty
username: typeof formData.username === 'string'
? formData.username.trim() || 'Anonymous'
: 'Anonymous',
// Age - convert to number, ensure valid
age: Number(formData.age),
// Theme - ensure valid option
theme: ['light', 'dark', 'system'].includes(formData.theme)
? formData.theme
: 'system',
// Notifications - convert to boolean
notifications: Boolean(formData.notifications),
// Max file size - ensure valid number or use default
maxFileSize: typeof formData.maxFileSize !== 'undefined'
? Number(formData.maxFileSize) || 5
: 5,
// Processed timestamp
processedAt: new Date().toISOString()
};
// Validate age specifically
if (isNaN(settings.age) || settings.age < 13) {
throw new Error('Age must be a number and at least 13');
}
return settings;
}
// Test with user input
const userInput = {
username: ' Alice ',
age: '28',
theme: 'dark',
notifications: 1,
maxFileSize: '10'
};
try {
const settings = processUserSettings(userInput);
console.log(settings);
} catch (error) {
console.error('Invalid settings:', error.message);
}
Primitive vs Reference Types
Understanding the difference between primitive and reference types is crucial for avoiding bugs in JavaScript.
Value Comparison
// Primitive types are compared by value
const num1 = 5;
const num2 = 5;
console.log(num1 === num2); // true
const str1 = "hello";
const str2 = "hello";
console.log(str1 === str2); // true
// Reference types are compared by reference (memory address)
const obj1 = { name: "Alice" };
const obj2 = { name: "Alice" };
console.log(obj1 === obj2); // false (different objects)
const arr1 = [1, 2, 3];
const arr2 = [1, 2, 3];
console.log(arr1 === arr2); // false (different arrays)
// Same reference
const obj3 = obj1;
console.log(obj1 === obj3); // true (same object reference)
Immutability of Primitives
// Primitives are immutable
let str = "hello";
str[0] = "H"; // No effect
console.log(str); // still "hello"
let num = 42;
num.newProp = "test"; // No effect, no error in non-strict mode
console.log(num.newProp); // undefined
// When we "modify" primitives, we're actually creating new values
let message = "hello";
message = message + " world"; // Creates a new string
console.log(message); // "hello world"
// References can be modified
const user = { name: "Alice" };
user.name = "Bob"; // Modifies the object
console.log(user.name); // "Bob"
Passing to Functions
// Primitive types are passed by value
function modifyPrimitive(value) {
value = 100; // Modifies the local copy only
}
let num = 42;
modifyPrimitive(num);
console.log(num); // Still 42
// Reference types are passed by reference
function modifyObject(obj) {
obj.modified = true; // Modifies the original
}
const data = { name: "Original" };
modifyObject(data);
console.log(data.modified); // true
// Reassigning the parameter doesn't affect the original reference
function replaceObject(obj) {
obj = { name: "Replacement" }; // Local reassignment only
}
const original = { name: "Original" };
replaceObject(original);
console.log(original.name); // Still "Original"
Analogy: Primitives vs References
Think of primitive values like numbers on a piece of paper: when you pass the number to someone, you're giving them a copy of the number. If they change their copy, your original remains unchanged.
Reference types are more like a remote control: when you pass an object to a function, you're passing the same remote control, not a copy. If the receiver presses buttons (modifies properties), those changes affect the actual device (the original object).
Real-World Example: User Profile Update
// A profile update function
function updateUserProfile(profile, updates) {
// Common mistake: assuming primitive properties are automatically updated
if (updates.name) {
profile.name = updates.name;
}
// References get merged, not copied
if (updates.preferences) {
// WRONG approach (reference gets overwritten)
// profile.preferences = updates.preferences;
// CORRECT approach (merge properties)
Object.assign(profile.preferences, updates.preferences);
}
// Arrays need special handling
if (updates.tags) {
// WRONG approach (direct reference assignment)
// profile.tags = updates.tags;
// CORRECT approach (create a new array)
profile.tags = [...updates.tags];
}
// Nested objects need deep cloning
if (updates.address) {
// Deep clone
profile.address = JSON.parse(JSON.stringify(updates.address));
}
// Add update timestamp
profile.lastUpdated = new Date();
return profile;
}
// Test the function
const userProfile = {
id: 42,
name: "Alice",
preferences: {
theme: "light",
notifications: true
},
tags: ["developer", "javascript"],
address: {
city: "San Francisco",
country: "USA"
}
};
const updates = {
name: "Alice Smith",
preferences: {
theme: "dark"
},
tags: ["developer", "javascript", "react"],
address: {
city: "New York",
country: "USA"
}
};
updateUserProfile(userProfile, updates);
console.log(userProfile);
// The profile now has updated values while preserving other properties
Practical Exercise
Let's practice working with primitive types through exercises of increasing complexity.
Exercise 1: Type Identification and Conversion
Create a function that identifies the type of a value and converts it to a specified target type.
function convertValue(value, targetType) {
// Get the current type
let currentType = typeof value;
// Special case for null
if (value === null) {
currentType = "null";
}
// Handle conversion
switch (targetType) {
case "string":
return String(value);
case "number":
return Number(value);
case "boolean":
return Boolean(value);
case "bigint":
try {
// BigInt only works with integers
if (Number.isInteger(Number(value))) {
return BigInt(value);
}
throw new Error("Value cannot be converted to BigInt");
} catch (e) {
throw new Error(`Cannot convert ${value} to BigInt: ${e.message}`);
}
default:
throw new Error(`Unsupported target type: ${targetType}`);
}
}
// Test the function with various inputs
console.log(convertValue(42, "string")); // "42"
console.log(convertValue("42", "number")); // 42
console.log(convertValue(0, "boolean")); // false
console.log(convertValue("true", "boolean")); // true
console.log(convertValue(123, "bigint")); // 123n
try {
console.log(convertValue(1.5, "bigint")); // Error
} catch (e) {
console.error(e.message);
}
Exercise 2: String Manipulation Challenge
Create a set of string utility functions for common operations:
const StringUtils = {
// Capitalize the first letter of each word
capitalize(str) {
if (typeof str !== 'string') return '';
return str
.split(' ')
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(' ');
},
// Truncate string with ellipsis if too long
truncate(str, maxLength = 20) {
if (typeof str !== 'string') return '';
if (str.length <= maxLength) return str;
return str.slice(0, maxLength - 3) + '...';
},
// Count occurrences of a substring
countOccurrences(str, substr) {
if (typeof str !== 'string' || typeof substr !== 'string') return 0;
if (substr.length === 0) return 0;
let count = 0;
let position = str.indexOf(substr);
while (position !== -1) {
count++;
position = str.indexOf(substr, position + 1);
}
return count;
},
// Convert camelCase to snake_case
camelToSnake(str) {
if (typeof str !== 'string') return '';
return str.replace(/([A-Z])/g, '_$1').toLowerCase();
},
// Convert snake_case to camelCase
snakeToCamel(str) {
if (typeof str !== 'string') return '';
return str.replace(/_([a-z])/g, (match, letter) => letter.toUpperCase());
}
};
// Test the string utilities
console.log(StringUtils.capitalize("hello world")); // "Hello World"
console.log(StringUtils.truncate("This is a very long sentence that needs truncating", 20)); // "This is a very lon..."
console.log(StringUtils.countOccurrences("hello hello world", "hello")); // 2
console.log(StringUtils.camelToSnake("thisIsCamelCase")); // "this_is_camel_case"
console.log(StringUtils.snakeToCamel("this_is_snake_case")); // "thisIsSnakeCase"
Exercise 3: Advanced Number Formatting
Create a comprehensive number formatting utility that handles different formats and scales:
const NumberFormatter = {
// Format as currency
currency(value, currency = 'USD', locale = 'en-US') {
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: currency
}).format(value);
},
// Format with specified decimal places
decimal(value, decimalPlaces = 2, locale = 'en-US') {
return new Intl.NumberFormat(locale, {
minimumFractionDigits: decimalPlaces,
maximumFractionDigits: decimalPlaces
}).format(value);
},
// Format as percentage
percent(value, decimalPlaces = 0, locale = 'en-US') {
return new Intl.NumberFormat(locale, {
style: 'percent',
minimumFractionDigits: decimalPlaces,
maximumFractionDigits: decimalPlaces
}).format(value);
},
// Format with unit
unit(value, unit, locale = 'en-US') {
return new Intl.NumberFormat(locale, {
style: 'unit',
unit: unit
}).format(value);
},
// Format large numbers with K, M, B suffixes
compact(value, locale = 'en-US') {
return new Intl.NumberFormat(locale, {
notation: 'compact',
compactDisplay: 'short'
}).format(value);
},
// Format as file size (B, KB, MB, GB)
fileSize(bytes) {
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(size < 10 && unitIndex > 0 ? 1 : 0)} ${units[unitIndex]}`;
}
};
// Test the number formatter
console.log(NumberFormatter.currency(1234.56)); // "$1,234.56"
console.log(NumberFormatter.currency(1234.56, 'EUR')); // "€1,234.56"
console.log(NumberFormatter.decimal(1234.5678, 3)); // "1,234.568"
console.log(NumberFormatter.percent(0.1234)); // "12%"
console.log(NumberFormatter.percent(0.1234, 1)); // "12.3%"
console.log(NumberFormatter.unit(25, 'celsius')); // "25°C"
console.log(NumberFormatter.compact(1234567)); // "1.2M"
console.log(NumberFormatter.fileSize(1536)); // "1.5 KB"
console.log(NumberFormatter.fileSize(1073741824)); // "1 GB"
Key Takeaways
- JavaScript has seven primitive types: String, Number, Boolean, Undefined, Null, Symbol, and BigInt
- Strings represent text data and provide methods for manipulation and searching
- Numbers in JavaScript represent both integers and floating-point values, with some precision limitations
- Booleans have two values (true/false), but all JavaScript values have an inherent truthiness
- Undefined represents variables that have been declared but not assigned a value
- Null represents an intentional absence of any value
- Symbols provide unique identifiers, useful for non-enumerable object properties
- BigInt allows representation of integers with arbitrary precision
- Type checking is done with the typeof operator, with some quirks to be aware of
- Primitive types are immutable and compared by value, unlike reference types