Primitive Data Types

Understanding the building blocks of JavaScript data

JavaScript Data Types Overview

JavaScript has two main categories of data types:

  1. Primitive Types: Basic data types that are immutable (cannot be changed)
  2. 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.

graph TB A[JavaScript Data Types] A --> 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] K --> L[Array] K --> M[Function] K --> N[Date] K --> O[RegExp] K --> P[Map/Set] K --> Q[Others...]

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 (