Labs ICT
Pro Login

Data Types

JavaScript data types are fundamental building blocks that define the kind of values a variable can hold. Understanding data types is crucial for writing effective, bug-free code and leveraging JavaScript's dynamic typing system.

In this tutorial, we'll explore JavaScript's primitive and reference data types, type conversion, type checking, and best practices for working with different data types.

Primitive Data Types

JavaScript has seven primitive data types that are immutable and stored directly in memory.

Number Type

// Number type - represents both integers and floating-point numbers
let integer = 42;
let float = 3.14;
let negative = -17;
let zero = 0;

// Scientific notation
let scientific = 5e3; // 5000
let small = 2e-3; // 0.002

// Special numeric values
let infinity = Infinity;
let negativeInfinity = -Infinity;
let notANumber = NaN; // Not a Number

// Number methods
let num = 123.456;
console.log(num.toFixed(2)); // "123.46"
console.log(num.toPrecision(3)); // "123"
console.log(num.toString()); // "123.456"

// Number properties
console.log(Number.MAX_VALUE); // Largest number
console.log(Number.MIN_VALUE); // Smallest positive number
console.log(Number.POSITIVE_INFINITY); // Infinity
console.log(Number.NEGATIVE_INFINITY); // -Infinity

String Type

// String type - sequence of characters
let singleQuote = 'Hello World';
let doubleQuote = "Hello World";
let backticks = `Hello World`;

// Template literals (ES6)
let name = 'John';
let age = 25;
let greeting = `My name is ${name} and I'm ${age} years old`;
console.log(greeting); // "My name is John and I'm 25 years old"

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

// String properties and methods
let text = "JavaScript";
console.log(text.length); // 10
console.log(text[0]); // "J"
console.log(text.toUpperCase()); // "JAVASCRIPT"
console.log(text.toLowerCase()); // "javascript"
console.log(text.includes('Script')); // true
console.log(text.split('a')); // ["J", "v", "Script"]

// String concatenation
let firstName = "John";
let lastName = "Doe";
let fullName = firstName + " " + lastName;
console.log(fullName); // "John Doe"

Boolean Type

// Boolean type - represents true or false
let isTrue = true;
let isFalse = false;

// Boolean conversion
console.log(Boolean(1)); // true
console.log(Boolean(0)); // false
console.log(Boolean("")); // false
console.log(Boolean("text")); // true
console.log(Boolean(null)); // false
console.log(Boolean(undefined)); // false
console.log(Boolean({})); // true
console.log(Boolean([])); // true

// Logical operators
let a = true;
let b = false;
console.log(a && b); // false (AND)
console.log(a || b); // true (OR)
console.log(!a); // false (NOT)

// Comparison operators
console.log(5 > 3); // true
console.log(5 < 3); // false
console.log(5 === 5); // true (strict equality)
console.log(5 == "5"); // true (loose equality)
console.log(5 === "5"); // false (strict equality)

Undefined and Null

// Undefined - variable declared but not assigned
let undefinedVar;
console.log(undefinedVar); // undefined

// Function without return value
function noReturn() {}
console.log(noReturn()); // undefined

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

// Null - intentional absence of value
let nullVar = null;
console.log(nullVar); // null

// Difference between undefined and null
console.log(typeof undefined); // "undefined"
console.log(typeof null); // "object" (historical bug)

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

if (value === undefined) {
  console.log("Value is undefined");
}

// Check for both null and undefined
if (value == null) {
  console.log("Value is null or undefined");
}

Symbol Type

// Symbol type - unique identifier (ES6)
let symbol1 = Symbol();
let symbol2 = Symbol('description');
let symbol3 = Symbol('description');

console.log(symbol1 === symbol2); // false
console.log(symbol2 === symbol3); // false

// Symbol as object property key
let id = Symbol('id');
let user = {
  name: 'John',
  [id]: 123
};

console.log(user[id]); // 123
console.log(user['id']); // undefined (regular string key)

// Symbol properties are not enumerable in for...in
for (let key in user) {
  console.log(key); // only "name"
}

// Well-known symbols
let arr = [1, 2, 3];
arr[Symbol.iterator] = function() {
  let index = 0;
  return {
    next: () => ({
      value: this[index++],
      done: index > this.length
    })
  };
};

BigInt Type

// BigInt type - for integers larger than Number.MAX_SAFE_INTEGER
let bigInt = 123456789012345678901234567890n;
let anotherBigInt = BigInt(123456789012345678901234567890);

// Operations with BigInt
let sum = bigInt + anotherBigInt;
console.log(sum); // 246913578024691357802469135780n

// Cannot mix BigInt and Number
// let invalid = bigInt + 42; // TypeError

// Comparison works
console.log(bigInt > 123n); // true
console.log(bigInt === 123456789012345678901234567890n); // true

// Convert to Number (with caution)
let bigNumber = 123n;
let regularNumber = Number(bigNumber); // 123
console.log(regularNumber);

// Large number operations
const maxSafeInteger = Number.MAX_SAFE_INTEGER; // 9007199254740991
const bigger = maxSafeInteger + 1;
console.log(bigger === maxSafeInteger + 2); // true (precision lost)

const bigIntMax = BigInt(Number.MAX_SAFE_INTEGER);
const biggerInt = bigIntMax + 1n;
console.log(biggerInt === bigIntMax + 2n); // false (precise)

Reference Data Types

Reference types store references to memory locations and include objects, arrays, functions, and more.

Object Type

// Object type - collection of key-value pairs
let person = {
  firstName: 'John',
  lastName: 'Doe',
  age: 30,
  isAdmin: true
};

// Access properties
console.log(person.firstName); // "John"
console.log(person['lastName']); // "Doe"

// Add/modify properties
person.email = 'john@example.com';
person.age = 31;
console.log(person);

// Delete properties
delete person.email;
console.log(person);

// Object methods
console.log(Object.keys(person)); // ["firstName", "lastName", "age", "isAdmin"]
console.log(Object.values(person)); // ["John", "Doe", 30, true]
console.log(Object.entries(person)); // Array of [key, value] pairs

// Check if property exists
console.log('firstName' in person); // true
console.log(person.hasOwnProperty('firstName')); // true

// Object spread (ES6)
let personCopy = { ...person, city: 'New York' };
console.log(personCopy);

Array Type

// Array type - ordered collection of values
let numbers = [1, 2, 3, 4, 5];
let mixed = [1, 'two', true, null, undefined];
let nested = [[1, 2], [3, 4]];

// Access elements
console.log(numbers[0]); // 1
console.log(numbers[2]); // 3
console.log(numbers[numbers.length - 1]); // 5

// Array methods
console.log(numbers.length); // 5
numbers.push(6); // Add to end
numbers.pop(); // Remove from end
numbers.unshift(0); // Add to beginning
numbers.shift(); // Remove from beginning

// Array iteration
numbers.forEach(num => console.log(num));
let doubled = numbers.map(num => num * 2);
let evens = numbers.filter(num => num % 2 === 0);
let sum = numbers.reduce((acc, num) => acc + num, 0);

// Check if array
console.log(Array.isArray(numbers)); // true
console.log(Array.isArray({})); // false

Function Type

// Function type - reusable block of code
function greet(name) {
  return `Hello, ${name}!`;
}

// Function expressions
const add = function(a, b) {
  return a + b;
};

// Arrow functions (ES6)
const multiply = (a, b) => a * b;

// Functions as values
let operation = add;
console.log(operation(5, 3)); // 8

// Higher-order functions
function createMultiplier(factor) {
  return function(number) {
    return number * factor;
  };
}

const double = createMultiplier(2);
console.log(double(5)); // 10

// Function properties
console.log(greet.name); // "greet"
console.log(greet.length); // 1 (number of parameters)
console.log(greet.toString()); // Function source code

Type Conversion

JavaScript automatically converts between types, but understanding these conversions is crucial.

Implicit Conversion

// String conversion
let num = 42;
let str = num + ''; // "42"
let bool = true + ''; // "true"
let obj = {} + ''; // "[object Object]"

// Numeric conversion
let strToNum = "123" - 0; // 123
let boolToNum = true - 0; // 1
let falseToNum = false - 0; // 0

// Boolean conversion
let numToBool = !!42; // true
let strToBool = !!""; // false
let objToBool = !!{}; // true

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

// Strict equality (===) does not perform conversion
console.log(5 === "5"); // false
console.log(0 === false); // false
console.log(null === undefined); // false

Explicit Conversion

// Convert to string
let num = 42;
console.log(String(num)); // "42"
console.log(num.toString()); // "42"

// Convert to number
let str = "123";
console.log(Number(str)); // 123
console.log(parseInt(str)); // 123
console.log(parseFloat("123.45")); // 123.45

// parseInt with radix
let binary = "1010";
console.log(parseInt(binary, 2)); // 10
let hex = "FF";
console.log(parseInt(hex, 16)); // 255

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

// Special conversions
console.log(Number(null)); // 0
console.log(Number(undefined)); // NaN
console.log(Number("")); // 0
console.log(Number("123abc")); // NaN

// Check for NaN
let result = parseInt("abc123");
console.log(isNaN(result)); // true
console.log(Number.isNaN(result)); // true (ES6)
console.log(Number.isFinite(123)); // true
console.log(Number.isFinite(Infinity)); // false

Type Checking

Various methods to check the type of values in JavaScript.

typeof Operator

// typeof operator
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" (historical bug)
console.log(typeof {}); // "object"
console.log(typeof []); // "object"
console.log(typeof function() {}); // "function"
console.log(typeof Symbol()); // "symbol"
console.log(typeof 42n); // "bigint"

// Practical type checking
function checkType(value) {
  return typeof value;
}

console.log(checkType(42)); // "number"
console.log(checkType("hello")); // "string"

// Type checking for null
function isNull(value) {
  return value === null && typeof value === "object";
}

console.log(isNull(null)); // true
console.log(isNull(undefined)); // false

Instanceof Operator

// instanceof operator
let date = new Date();
let array = [1, 2, 3];
let object = {};

console.log(date instanceof Date); // true
console.log(date instanceof Object); // true
console.log(array instanceof Array); // true
console.log(array instanceof Object); // true
console.log(object instanceof Object); // true

// Custom classes
class Person {}
let john = new Person();
console.log(john instanceof Person); // true
console.log(john instanceof Object); // true

// Primitive types are not instances
console.log("hello" instanceof String); // false
console.log(42 instanceof Number); // false

// But wrapper objects are
console.log(new String("hello") instanceof String); // true

Object.prototype.toString

// Object.prototype.toString for accurate type checking
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(true)); // "Boolean"
console.log(getType(undefined)); // "Undefined"
console.log(getType(null)); // "Null"
console.log(getType({})); // "Object"
console.log(getType([])); // "Array"
console.log(getType(new Date())); // "Date"
console.log(getType(/regex/)); // "RegExp"
console.log(getType(function() {})); // "Function"

// Type checking utilities
function isArray(value) {
  return Array.isArray(value);
}

function isDate(value) {
  return value instanceof Date;
}

function isFunction(value) {
  return typeof value === 'function';
}

function isObject(value) {
  return value !== null && typeof value === 'object';
}

Type Coercion Rules

Understanding JavaScript's type coercion rules helps avoid unexpected behavior.

Falsy Values

// Falsy values - values that evaluate to false in boolean context
const falsyValues = [
  false,
  0,
  -0,
  0n,
  "",
  null,
  undefined,
  NaN
];

// All falsy values are false in boolean context
falsyValues.forEach(value => {
  console.log(value, Boolean(value)); // All false
});

// Practical example
function getValue(value) {
  if (value) {
    return "Value is truthy";
  } else {
    return "Value is falsy";
  }
}

console.log(getValue(0)); // "Value is falsy"
console.log(getValue("hello")); // "Value is truthy"

// Truthy values - everything else
const truthyValues = [
  true,
  {},
  [],
  42,
  -42,
  3.14,
  -3.14,
  Infinity,
  -Infinity,
  "0",
  "false",
  new Date(),
  function() {}
];

Abstract Equality Comparison

// Abstract equality (==) algorithm
// 1. If types are same, use strict equality
// 2. If one is null and other is undefined, return true
// 3. If one is number and other is string, convert string to number
// 4. If one is boolean, convert both to number
// 5. If one is object and other is string/number/symbol, convert object to primitive

// Examples
console.log(null == undefined); // true
console.log(0 == false); // true
console.log("" == false); // true
console.log("0" == false); // true
console.log([] == false); // true
console.log([] == ""); // true
console.log({} == "[object Object]"); // false

// Dangerous comparisons
function dangerousComparison(value) {
  if (value == 0) {
    // This will be true for 0, false, "", [], "0"
    return "Zero or falsy";
  }
}

// Better to use strict equality
function safeComparison(value) {
  if (value === 0) {
    // Only true for exactly 0
    return "Exactly zero";
  }
}

Working with Types

Practical techniques for working with different data types.

Type Guards

// Type guards - runtime checks for types
function processValue(value) {
  if (typeof value === "string") {
    return value.toUpperCase();
  } else if (typeof value === "number") {
    return value * 2;
  } else if (Array.isArray(value)) {
    return value.length;
  } else {
    return "Unknown type";
  }
}

console.log(processValue("hello")); // "HELLO"
console.log(processValue(5)); // 10
console.log(processValue([1, 2, 3])); // 3

// Custom type guard
function isString(value) {
  return typeof value === "string";
}

function processString(value) {
  if (isString(value)) {
    // TypeScript knows value is string here
    return value.length;
  }
  return 0;
}

Type Validation

// Validate input types
function validateNumber(value) {
  if (typeof value !== "number" || isNaN(value)) {
    throw new Error("Expected a valid number");
  }
  return value;
}

function validateString(value) {
  if (typeof value !== "string") {
    throw new Error("Expected a string");
  }
  return value;
}

function validateArray(value) {
  if (!Array.isArray(value)) {
    throw new Error("Expected an array");
  }
  return value;
}

// Safe conversion functions
function safeParseInt(value, defaultValue = 0) {
  const parsed = parseInt(value, 10);
  return isNaN(parsed) ? defaultValue : parsed;
}

function safeParseFloat(value, defaultValue = 0) {
  const parsed = parseFloat(value);
  return isNaN(parsed) ? defaultValue : parsed;
}

console.log(safeParseInt("123")); // 123
console.log(safeParseInt("abc", 0)); // 0

Type Utilities

// Type utility functions
const TypeUtils = {
  // Check if value is primitive
  isPrimitive(value) {
    return value === null || (typeof value !== 'object' && typeof value !== 'function');
  },
  
  // Check if value is object-like
  isObjectLike(value) {
    return typeof value === 'object' && value !== null;
  },
  
  // Get detailed type
  getType(value) {
    if (value === null) return 'null';
    if (Array.isArray(value)) return 'array';
    return typeof value;
  },
  
  // Safe type conversion
  toNumber(value, defaultValue = 0) {
    const num = Number(value);
    return isNaN(num) ? defaultValue : num;
  },
  
  toString(value, defaultValue = '') {
    return value == null ? defaultValue : String(value);
  },
  
  toBoolean(value, defaultValue = false) {
    return value == null ? defaultValue : Boolean(value);
  }
};

console.log(TypeUtils.isPrimitive(42)); // true
console.log(TypeUtils.isPrimitive({})); // false
console.log(TypeUtils.getType([])); // "array"
console.log(TypeUtils.toNumber("123", 0)); // 123
console.log(TypeUtils.toNumber("abc", 0)); // 0

Modern Type Features

Recent additions to JavaScript's type system.

Optional Chaining

// Optional chaining (?.) - safe property access
const user = {
  name: 'John',
  address: {
    street: '123 Main St',
    city: 'New York'
  }
};

// Safe property access
console.log(user.address?.city); // "New York"
console.log(user.contact?.phone); // undefined
console.log(user.contact?.phone?.toString()); // undefined

// Safe method calls
const obj = { method: () => 'Hello' };
console.log(obj.method?.()); // "Hello"
console.log(obj.nonExistent?.()); // undefined

// Safe array access
const data = {
  items: [1, 2, 3]
};
console.log(data.items?.[0]); // 1
console.log(data.items?.[10]); // undefined

Nullish Coalescing

// Nullish coalescing (??) - fallback for null/undefined
const settings = {
  theme: null,
  fontSize: undefined,
  language: 'en'
};

// Use default value only for null or undefined
const theme = settings.theme ?? 'default'; // 'default'
const fontSize = settings.fontSize ?? 16; // 16
const language = settings.language ?? 'en'; // 'en'

// Different from || (logical OR)
const falsyValue = 0;
const result1 = falsyValue || 42; // 42 (0 is falsy)
const result2 = falsyValue ?? 42; // 0 (0 is not null/undefined)

// Practical usage
function getConfig(key, defaultValue) {
  return config[key] ?? defaultValue;
}

const config = {
  timeout: 0, // 0 is valid value
  retries: null
};

console.log(getConfig('timeout', 30)); // 0 (not 30)
console.log(getConfig('retries', 3)); // 3
console.log(getConfig('maxConnections', 10)); // 10

GlobalThis

// globalThis - standard way to access global object
// Works in all environments (browser, Node.js, web workers)

// In browser
console.log(globalThis === window); // true

// In Node.js
// console.log(globalThis === global); // true

// Use instead of window/global/self
function getGlobalProperty(property) {
  return globalThis[property];
}

// Cross-environment code
const timer = globalThis.setTimeout || globalThis.setTimeout;
timer(() => console.log('Hello'), 1000);

Best Practices

Guidelines for working effectively with JavaScript data types.

Use Strict Equality

// Always use === instead of ==
// Bad
if (value == null) { /* ... */ }
if (value == '0') { /* ... */ }

// Good
if (value === null || value === undefined) { /* ... */ }
if (value === '0') { /* ... */ }

// Exception: intentional null/undefined check
if (value == null) { /* Checks both null and undefined */ }

Type Validation

// Validate inputs before processing
function calculateArea(width, height) {
  if (typeof width !== 'number' || typeof height !== 'number') {
    throw new TypeError('Width and height must be numbers');
  }
  if (isNaN(width) || isNaN(height)) {
    throw new TypeError('Width and height must not be NaN');
  }
  return width * height;
}

// Use type guards
function process(data) {
  if (Array.isArray(data)) {
    return data.map(item => item * 2);
  } else if (typeof data === 'object' && data !== null) {
    return Object.values(data);
  } else {
    return [];
  }
}

Avoid Type Coercion Issues

// Be explicit about conversions
// Bad
if (user.age) { /* Might fail if age is 0 */ }

// Good
if (typeof user.age === 'number' && user.age >= 0) { /* ... */ }

// Use appropriate comparison
// Bad
if (value == '0') { /* Matches 0, false, '', [] */ }

// Good
if (value === '0' || value === 0) { /* Explicit */ }

Summary

Key Takeaways

  • JavaScript has 7 primitive types and reference types
  • Type coercion can cause unexpected behavior
  • Use === for strict equality comparisons
  • Modern features like optional chaining and nullish coalescing improve safety
  • Always validate input types in production code

Best Practices

  • Use strict equality (===) instead of loose equality (==)
  • Validate function inputs and handle type conversion explicitly
  • Use typeof and instanceof for type checking
  • Leverage optional chaining and nullish coalescing for safety
  • Understand falsy values and their behavior

Common Pitfalls

  • Using == instead of === causing unexpected type coercion
  • Forgetting that typeof null returns "object"
  • Not validating inputs before processing
  • Assuming falsy values are always "empty" or "invalid"
  • Not handling NaN in numeric calculations