Learning Objectives

  • Understand all seven primitive data types
  • Learn about reference types: objects, arrays, and functions
  • Master type checking with typeof and other methods
  • Understand type coercion and conversion
  • Avoid common type-related pitfalls

Overview of JavaScript Data Types

JavaScript has two main categories of data types:

  • Primitive types - Immutable values stored directly in memory
  • Reference types - Mutable objects stored by reference
Category Types
Primitives (7) Number, String, Boolean, Undefined, Null, Symbol, BigInt
Reference Types Object, Array, Function, Date, RegExp, etc.

Primitive Data Types

1. Number

JavaScript has only one number type - all numbers are 64-bit floating point values.

// Integers
let age = 25;
let count = 100;

// Floating point
let price = 19.99;
let pi = 3.14159;

// Special numeric values
let infinity = Infinity;
let negInfinity = -Infinity;
let notANumber = NaN;

// Number operations
console.log(10 / 3);        // 3.3333333333333335
console.log(0.1 + 0.2);     // 0.30000000000000004 (floating point precision)
console.log(5 / 0);         // Infinity
console.log("hello" / 2);   // NaN

Number Methods and Properties

// Checking for NaN
console.log(isNaN(NaN));           // true
console.log(isNaN("hello"));       // true
console.log(Number.isNaN(NaN));    // true (more strict)
console.log(Number.isNaN("hello")); // false

// Checking for finite numbers
console.log(isFinite(100));        // true
console.log(isFinite(Infinity));   // false

// Parsing
console.log(parseInt("42"));       // 42
console.log(parseInt("42.5"));     // 42
console.log(parseFloat("42.5"));   // 42.5
console.log(parseInt("42px"));     // 42

// Converting to fixed decimals
let num = 3.14159;
console.log(num.toFixed(2));       // "3.14"

2. String

Strings represent text and are immutable sequences of characters.

// String literals
let single = 'Hello';
let double = "World";
let template = `Hello, World!`;

// String concatenation
let greeting = "Hello" + " " + "World";  // "Hello World"

// Template literals (ES6)
let name = "Alice";
let age = 25;
let message = `My name is ${name} and I'm ${age} years old`;

// Multi-line strings
let multiline = `
    This is a
    multi-line
    string
`;

// Escape characters
let escaped = "She said, \"Hello!\"";
let newline = "Line 1\nLine 2";
let tab = "Column1\tColumn2";

String Methods

let str = "Hello, World!";

// Length
console.log(str.length);              // 13

// Accessing characters
console.log(str[0]);                  // "H"
console.log(str.charAt(0));           // "H"

// Case conversion
console.log(str.toLowerCase());       // "hello, world!"
console.log(str.toUpperCase());       // "HELLO, WORLD!"

// Searching
console.log(str.indexOf("World"));    // 7
console.log(str.includes("World"));   // true
console.log(str.startsWith("Hello")); // true
console.log(str.endsWith("!"));       // true

// Extracting
console.log(str.slice(0, 5));         // "Hello"
console.log(str.substring(7, 12));    // "World"
console.log(str.substr(7, 5));        // "World" (deprecated)

// Replacing
console.log(str.replace("World", "JavaScript")); // "Hello, JavaScript!"

// Splitting
console.log(str.split(", "));         // ["Hello", "World!"]

// Trimming
let padded = "  hello  ";
console.log(padded.trim());           // "hello"

3. Boolean

Booleans represent logical values: true or false.

let isActive = true;
let isComplete = false;

// Boolean operations
console.log(true && false);   // false (AND)
console.log(true || false);   // true (OR)
console.log(!true);           // false (NOT)

// Comparison operators return booleans
console.log(5 > 3);           // true
console.log(10 === 10);       // true
console.log("a" < "b");       // true

// Truthy and Falsy values
// Falsy: false, 0, "", null, undefined, NaN
// Everything else is truthy

if ("hello") {
    console.log("Strings are truthy");
}

if (0) {
    console.log("This won't run");
}

// Converting to boolean
console.log(Boolean(1));      // true
console.log(Boolean(0));      // false
console.log(Boolean(""));     // false
console.log(Boolean("text")); // true

4. Undefined

undefined means a variable has been declared but not assigned a value.

let x;
console.log(x);               // undefined
console.log(typeof x);        // "undefined"

// Function with no return value
function doNothing() {}
console.log(doNothing());     // undefined

// Accessing non-existent property
let obj = {};
console.log(obj.name);        // undefined

// Function parameter not provided
function greet(name) {
    console.log(name);
}
greet();                      // undefined

5. Null

null represents the intentional absence of a value.

let empty = null;
console.log(empty);           // null
console.log(typeof null);     // "object" (historical bug!)

// null vs undefined
let notAssigned;              // undefined
let intentionallyEmpty = null; // null

// Checking for null
if (empty === null) {
    console.log("Value is null");
}

// null in operations
console.log(null + 5);        // 5 (null converts to 0)
console.log(null == undefined); // true (loose equality)
console.log(null === undefined); // false (strict equality)

6. Symbol (ES6)

Symbols are unique and immutable identifiers, often used as object property keys.

// Creating symbols
let sym1 = Symbol();
let sym2 = Symbol("description");
let sym3 = Symbol("description");

console.log(sym2 === sym3);   // false (each symbol is unique)

// Using symbols as object keys
let id = Symbol("id");
let user = {
    name: "Alice",
    [id]: 123
};

console.log(user[id]);        // 123
console.log(user.id);         // undefined

// Symbols are not enumerable
for (let key in user) {
    console.log(key);         // Only "name" is logged
}

// Global symbol registry
let globalSym1 = Symbol.for("app.id");
let globalSym2 = Symbol.for("app.id");
console.log(globalSym1 === globalSym2); // true

7. BigInt (ES2020)

BigInt allows you to work with integers larger than Number.MAX_SAFE_INTEGER.

// Creating BigInt
let bigNum1 = 9007199254740991n;
let bigNum2 = BigInt("9007199254740991");

console.log(typeof bigNum1);  // "bigint"

// BigInt operations
let sum = 100n + 200n;        // 300n
let product = 50n * 2n;       // 100n

// Cannot mix BigInt and Number
// console.log(100n + 50);    // TypeError

// Must convert explicitly
console.log(100n + BigInt(50)); // 150n
console.log(Number(100n) + 50); // 150

// Comparison works
console.log(100n > 50);       // true
console.log(100n === 100);    // false (different types)
console.log(100n == 100);     // true (loose equality)

Reference Types

Objects

Objects are collections of key-value pairs.

// Object literal
let person = {
    name: "Alice",
    age: 25,
    isStudent: true,
    greet: function() {
        console.log("Hello!");
    }
};

// Accessing properties
console.log(person.name);     // "Alice"
console.log(person["age"]);   // 25

// Adding properties
person.email = "alice@example.com";

// Deleting properties
delete person.isStudent;

// Checking for properties
console.log("name" in person);        // true
console.log(person.hasOwnProperty("age")); // true

// Object methods
console.log(Object.keys(person));     // ["name", "age", "email", "greet"]
console.log(Object.values(person));   // ["Alice", 25, "alice@example.com", function]
console.log(Object.entries(person));  // [["name", "Alice"], ...]

Arrays

Arrays are ordered collections of values.

// Array literal
let numbers = [1, 2, 3, 4, 5];
let mixed = [1, "hello", true, null, {name: "Alice"}];

// Accessing elements
console.log(numbers[0]);      // 1
console.log(numbers.length);  // 5

// Modifying arrays
numbers.push(6);              // Add to end
numbers.pop();                // Remove from end
numbers.unshift(0);           // Add to beginning
numbers.shift();              // Remove from beginning

// Array methods
let doubled = numbers.map(n => n * 2);
let evens = numbers.filter(n => n % 2 === 0);
let sum = numbers.reduce((acc, n) => acc + n, 0);

// Checking if array
console.log(Array.isArray(numbers));  // true
console.log(typeof numbers);          // "object"

Functions

Functions are first-class objects in JavaScript.

// Function declaration
function add(a, b) {
    return a + b;
}

// Function expression
let multiply = function(a, b) {
    return a * b;
};

// Arrow function
let divide = (a, b) => a / b;

// Functions are objects
console.log(typeof add);      // "function"
add.customProperty = "value";
console.log(add.customProperty); // "value"

Type Checking

Using typeof

console.log(typeof 42);           // "number"
console.log(typeof "hello");      // "string"
console.log(typeof true);         // "boolean"
console.log(typeof undefined);    // "undefined"
console.log(typeof null);         // "object" (bug!)
console.log(typeof Symbol());     // "symbol"
console.log(typeof 100n);         // "bigint"
console.log(typeof {});           // "object"
console.log(typeof []);           // "object"
console.log(typeof function(){}); // "function"

Better Type Checking

// Check for null
let value = null;
console.log(value === null);      // true

// Check for array
console.log(Array.isArray([]));   // true
console.log(Array.isArray({}));   // false

// Check for object (not null, not array)
function isObject(value) {
    return value !== null && 
           typeof value === 'object' && 
           !Array.isArray(value);
}

// Using Object.prototype.toString
function getType(value) {
    return Object.prototype.toString.call(value).slice(8, -1);
}

console.log(getType(42));         // "Number"
console.log(getType("hello"));    // "String"
console.log(getType([]));         // "Array"
console.log(getType(null));       // "Null"
console.log(getType(new Date())); // "Date"

Type Coercion

JavaScript automatically converts types in certain situations (implicit coercion).

String Coercion

// + operator with strings
console.log("5" + 3);         // "53" (number to string)
console.log("Hello" + true);  // "Hellotrue"
console.log("Value: " + null); // "Value: null"

// Template literals
console.log(`Count: ${5}`);   // "Count: 5"

Number Coercion

// Arithmetic operators (except +)
console.log("5" - 2);         // 3
console.log("10" * "2");      // 20
console.log("20" / "4");      // 5
console.log("5" % 2);         // 1

// Unary + operator
console.log(+"42");           // 42
console.log(+true);           // 1
console.log(+false);          // 0
console.log(+null);           // 0
console.log(+undefined);      // NaN

Boolean Coercion

// Falsy values: false, 0, "", null, undefined, NaN
// Everything else is truthy

if ("") {
    console.log("Won't run");
}

if ("hello") {
    console.log("Will run");
}

// Double NOT for explicit conversion
console.log(!!"hello");       // true
console.log(!!0);             // false
console.log(!!null);          // false

Equality Coercion

// Loose equality (==) performs coercion
console.log(5 == "5");        // true
console.log(true == 1);       // true
console.log(false == 0);      // true
console.log(null == undefined); // true
console.log("" == 0);         // true

// Strict equality (===) does NOT coerce
console.log(5 === "5");       // false
console.log(true === 1);      // false
console.log(null === undefined); // false

// Always prefer === for clarity

Type Conversion

Explicit type conversion is clearer and safer than relying on coercion.

// To String
String(123);                  // "123"
String(true);                 // "true"
String(null);                 // "null"
(123).toString();             // "123"

// To Number
Number("123");                // 123
Number("12.5");               // 12.5
Number("hello");              // NaN
Number(true);                 // 1
Number(false);                // 0
Number(null);                 // 0
Number(undefined);            // NaN

parseInt("42");               // 42
parseInt("42.5");             // 42
parseFloat("42.5");           // 42.5

// To Boolean
Boolean(1);                   // true
Boolean(0);                   // false
Boolean("hello");             // true
Boolean("");                  // false
Boolean(null);                // false
Boolean(undefined);           // false

Primitive vs Reference Types

Primitives are Immutable

let str = "hello";
str[0] = "H";                 // Doesn't work
console.log(str);             // "hello"

// String methods return new strings
let upper = str.toUpperCase();
console.log(str);             // "hello" (original unchanged)
console.log(upper);           // "HELLO" (new string)

Primitives are Copied by Value

let a = 5;
let b = a;                    // Copy value
b = 10;

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

Objects are Copied by Reference

let obj1 = { name: "Alice" };
let obj2 = obj1;              // Copy reference
obj2.name = "Bob";

console.log(obj1.name);       // "Bob" (changed!)
console.log(obj2.name);       // "Bob"

// Arrays too
let arr1 = [1, 2, 3];
let arr2 = arr1;
arr2.push(4);

console.log(arr1);            // [1, 2, 3, 4] (changed!)
console.log(arr2);            // [1, 2, 3, 4]

Cloning Objects and Arrays

// Shallow clone object
let original = { name: "Alice", age: 25 };
let clone1 = { ...original };           // Spread operator
let clone2 = Object.assign({}, original);

clone1.name = "Bob";
console.log(original.name);   // "Alice" (unchanged)

// Shallow clone array
let arr = [1, 2, 3];
let arrClone1 = [...arr];
let arrClone2 = arr.slice();

// Deep clone (for nested objects)
let nested = { user: { name: "Alice" } };
let deepClone = JSON.parse(JSON.stringify(nested));

deepClone.user.name = "Bob";
console.log(nested.user.name); // "Alice" (unchanged)

Common Pitfalls

Pitfall 1: typeof null

console.log(typeof null);     // "object" (bug!)

// Correct way to check for null
if (value === null) {
    // Handle null
}

Pitfall 2: NaN Comparison

console.log(NaN === NaN);     // false!

// Use isNaN or Number.isNaN
console.log(isNaN(NaN));      // true
console.log(Number.isNaN(NaN)); // true (more strict)

Pitfall 3: Floating Point Precision

console.log(0.1 + 0.2);       // 0.30000000000000004

// Solution: Use toFixed or round
console.log((0.1 + 0.2).toFixed(2)); // "0.30"
console.log(Math.round((0.1 + 0.2) * 100) / 100); // 0.3

Pitfall 4: Array Type Checking

console.log(typeof []);       // "object"

// Use Array.isArray
console.log(Array.isArray([])); // true

Pitfall 5: Truthy/Falsy Confusion

// Empty array and object are truthy!
if ([]) {
    console.log("Empty array is truthy");
}

if ({}) {
    console.log("Empty object is truthy");
}

// Check length for arrays
let arr = [];
if (arr.length > 0) {
    // Array has items
}

// Check keys for objects
let obj = {};
if (Object.keys(obj).length > 0) {
    // Object has properties
}

Best Practices

1. Use Strict Equality

// Good
if (value === 5) { }

// Avoid
if (value == 5) { }

2. Explicit Type Conversion

// Good
let num = Number(input);
let str = String(value);

// Avoid relying on coercion
let num = +input;
let str = value + "";

3. Use const and let

// Good
const PI = 3.14159;
let counter = 0;

// Avoid
var x = 5;

4. Check Types Before Operations

function divide(a, b) {
    if (typeof a !== 'number' || typeof b !== 'number') {
        throw new TypeError('Arguments must be numbers');
    }
    if (b === 0) {
        throw new Error('Cannot divide by zero');
    }
    return a / b;
}

Key Takeaways

  • ✅ JavaScript has 7 primitive types and reference types
  • ✅ Primitives are immutable and copied by value
  • ✅ Objects are mutable and copied by reference
  • ✅ Use typeof for basic type checking
  • ✅ Use === for strict equality without coercion
  • ✅ Be aware of falsy values: false, 0, "", null, undefined, NaN
  • ✅ Prefer explicit type conversion over implicit coercion

Conclusion

Understanding JavaScript's type system is fundamental to writing reliable code. While JavaScript's dynamic typing and type coercion can be convenient, they can also lead to subtle bugs. By knowing the differences between primitive and reference types, using strict equality, and performing explicit type conversions, you'll write more predictable and maintainable code.

Practice tip: When debugging unexpected behavior, always check the types of your variables using typeof and console.log(). Many bugs stem from incorrect type assumptions.