Skip to main content

Introspection and Type Checking in JavaScript

Created: May 8, 2026 Larry Qu 9 min read

Introspection enables examining objects and values at runtime. This article covers type checking, property inspection, and advanced type detection patterns.

Introduction

Introspection allows:

  • Runtime type detection
  • Property inspection
  • Method discovery
  • Object analysis
  • Type validation

Understanding introspection helps you:

  • Build robust type checking
  • Create flexible APIs
  • Implement validation systems
  • Debug complex objects
  • Write defensive code

typeof Operator

Basic Type Detection

// ✅ Good: Basic typeof usage
console.log(typeof 42); // 'number'
console.log(typeof 'hello'); // 'string'
console.log(typeof true); // 'boolean'
console.log(typeof undefined); // 'undefined'
console.log(typeof Symbol('id')); // 'symbol'
console.log(typeof BigInt(42)); // 'bigint'

// Functions
console.log(typeof function() {}); // 'function'
console.log(typeof (() => {})); // 'function'

// Objects
console.log(typeof {}); // 'object'
console.log(typeof []); // 'object'
console.log(typeof null); // 'object' (quirk!)

typeof Limitations

// ❌ Bad: typeof doesn't distinguish object types
console.log(typeof []); // 'object' (not 'array')
console.log(typeof {}); // 'object'
console.log(typeof null); // 'object' (not 'null')
console.log(typeof new Date()); // 'object' (not 'date')

// ✅ Good: Use other methods for specific types
console.log(Array.isArray([])); // true
console.log(Object.prototype.toString.call(null)); // '[object Null]'
console.log(new Date() instanceof Date); // true

instanceof Operator

Basic instanceof Usage

// ✅ Good: Check object types
class Animal {}
class Dog extends Animal {}

const dog = new Dog();
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(dog instanceof Object); // true

// Built-in types
console.log([] instanceof Array); // true
console.log({} instanceof Object); // true
console.log(new Date() instanceof Date); // true
console.log(/regex/ instanceof RegExp); // true

instanceof with Inheritance

// ✅ Good: Check inheritance chain
class Vehicle {
  constructor(type) {
    this.type = type;
  }
}

class Car extends Vehicle {
  constructor() {
    super('car');
  }
}

const car = new Car();
console.log(car instanceof Car); // true
console.log(car instanceof Vehicle); // true
console.log(car instanceof Object); // true

// Check constructor
console.log(car.constructor === Car); // true
console.log(car.constructor.name); // 'Car'

instanceof Limitations

// ❌ Bad: instanceof fails across realms
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
const iframeArray = iframe.contentWindow.Array;
const arr = new iframeArray();

console.log(arr instanceof Array); // false (different realm)
console.log(Array.isArray(arr)); // true (works across realms)

// ✅ Good: Use Array.isArray for arrays
console.log(Array.isArray([])); // true
console.log(Array.isArray(new Array())); // true

Object Introspection Methods

Object.keys, values, entries

// ✅ Good: Inspect object properties
const user = {
  name: 'John',
  age: 30,
  email: '[email protected]'
};

// Get property names
console.log(Object.keys(user));
// ['name', 'age', 'email']

// Get property values
console.log(Object.values(user));
// ['John', 30, '[email protected]']

// Get key-value pairs
console.log(Object.entries(user));
// [['name', 'John'], ['age', 30], ['email', '[email protected]']]

Object.getOwnPropertyNames

// ✅ Good: Get all own properties
class Person {
  constructor(name) {
    this.name = name;
  }

  greet() {
    return `Hello, ${this.name}`;
  }
}

const person = new Person('John');

// Own properties only
console.log(Object.getOwnPropertyNames(person));
// ['name']

// All properties including methods
console.log(Object.getOwnPropertyNames(Person.prototype));
// ['constructor', 'greet']

Object.getOwnPropertyDescriptors

// ✅ Good: Inspect property descriptors
const obj = {
  name: 'John',
  age: 30
};

Object.defineProperty(obj, 'id', {
  value: 123,
  writable: false,
  enumerable: false,
  configurable: false
});

console.log(Object.getOwnPropertyDescriptors(obj));
// {
//   name: { value: 'John', writable: true, enumerable: true, configurable: true },
//   age: { value: 30, writable: true, enumerable: true, configurable: true },
//   id: { value: 123, writable: false, enumerable: false, configurable: false }
// }

Advanced Type Checking

Comprehensive Type Checker

// ✅ Good: Robust type checking function
function getType(value) {
  // Handle null
  if (value === null) return 'null';

  // Handle undefined
  if (value === undefined) return 'undefined';

  // Handle primitives
  const primitiveType = typeof value;
  if (primitiveType !== 'object') {
    return primitiveType;
  }

  // Handle objects
  if (Array.isArray(value)) return 'array';
  if (value instanceof Date) return 'date';
  if (value instanceof RegExp) return 'regexp';
  if (value instanceof Map) return 'map';
  if (value instanceof Set) return 'set';
  if (value instanceof WeakMap) return 'weakmap';
  if (value instanceof WeakSet) return 'weakset';
  if (value instanceof Error) return 'error';

  return 'object';
}

// Usage
console.log(getType(42)); // 'number'
console.log(getType('hello')); // 'string'
console.log(getType([])); // 'array'
console.log(getType({})); // 'object'
console.log(getType(new Date())); // 'date'
console.log(getType(null)); // 'null'

Type Validation

// ✅ Good: Type validation function
function validateType(value, expectedType) {
  const actualType = typeof value;

  if (expectedType === 'array') {
    return Array.isArray(value);
  }

  if (expectedType === 'null') {
    return value === null;
  }

  if (expectedType === 'object') {
    return value !== null && typeof value === 'object';
  }

  return actualType === expectedType;
}

// Usage
console.log(validateType(42, 'number')); // true
console.log(validateType('hello', 'string')); // true
console.log(validateType([], 'array')); // true
console.log(validateType({}, 'object')); // true
console.log(validateType(null, 'null')); // true

Runtime Type Checking

// ✅ Good: Runtime type checking with validation
class TypeChecker {
  static isNumber(value) {
    return typeof value === 'number' && !isNaN(value);
  }

  static isString(value) {
    return typeof value === 'string';
  }

  static isArray(value) {
    return Array.isArray(value);
  }

  static isObject(value) {
    return value !== null && typeof value === 'object' && !Array.isArray(value);
  }

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

  static isBoolean(value) {
    return typeof value === 'boolean';
  }

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

  static isRegExp(value) {
    return value instanceof RegExp;
  }

  static isPromise(value) {
    return value instanceof Promise;
  }

  static isIterable(value) {
    return value != null && typeof value[Symbol.iterator] === 'function';
  }
}

// Usage
console.log(TypeChecker.isNumber(42)); // true
console.log(TypeChecker.isArray([])); // true
console.log(TypeChecker.isPromise(Promise.resolve())); // true
console.log(TypeChecker.isIterable([1, 2, 3])); // true

Property Inspection

Checking Property Existence

// ✅ Good: Check if property exists
const user = {
  name: 'John',
  age: 30
};

// Using 'in' operator
console.log('name' in user); // true
console.log('email' in user); // false

// Using hasOwnProperty
console.log(user.hasOwnProperty('name')); // true
console.log(user.hasOwnProperty('email')); // false

// Using Object.prototype.hasOwnProperty
console.log(Object.prototype.hasOwnProperty.call(user, 'name')); // true

// Using optional chaining
console.log(user?.name); // 'John'
console.log(user?.email); // undefined

Enumerating Properties

// ✅ Good: Enumerate object properties
const obj = {
  a: 1,
  b: 2,
  c: 3
};

// for...in loop
for (const key in obj) {
  if (obj.hasOwnProperty(key)) {
    console.log(`${key}: ${obj[key]}`);
  }
}

// Object.keys
Object.keys(obj).forEach(key => {
  console.log(`${key}: ${obj[key]}`);
});

// Object.entries
Object.entries(obj).forEach(([key, value]) => {
  console.log(`${key}: ${value}`);
});

Property Descriptors

// ✅ Good: Inspect property descriptors
const obj = {};

Object.defineProperty(obj, 'readonly', {
  value: 'immutable',
  writable: false,
  enumerable: true,
  configurable: false
});

const descriptor = Object.getOwnPropertyDescriptor(obj, 'readonly');
console.log(descriptor);
// {
//   value: 'immutable',
//   writable: false,
//   enumerable: true,
//   configurable: false
// }

// Check if property is writable
console.log(descriptor.writable); // false

Practical Introspection Patterns

Object Cloner

// ✅ Good: Deep clone using introspection
function deepClone(obj, seen = new WeakSet()) {
  // Handle primitives
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }

  // Handle circular references
  if (seen.has(obj)) {
    return obj;
  }

  seen.add(obj);

  // Handle arrays
  if (Array.isArray(obj)) {
    return obj.map(item => deepClone(item, seen));
  }

  // Handle dates
  if (obj instanceof Date) {
    return new Date(obj.getTime());
  }

  // Handle objects
  const cloned = {};
  for (const key of Object.keys(obj)) {
    cloned[key] = deepClone(obj[key], seen);
  }

  return cloned;
}

// Usage
const original = {
  name: 'John',
  hobbies: ['reading', 'coding'],
  date: new Date()
};

const cloned = deepClone(original);
console.log(cloned);
console.log(cloned === original); // false
console.log(cloned.hobbies === original.hobbies); // false

Object Serializer

// ✅ Good: Serialize objects with introspection
class ObjectSerializer {
  static serialize(obj) {
    const type = this.getType(obj);

    switch (type) {
      case 'null':
        return { type: 'null', value: null };
      case 'undefined':
        return { type: 'undefined' };
      case 'date':
        return { type: 'date', value: obj.toISOString() };
      case 'array':
        return {
          type: 'array',
          value: obj.map(item => this.serialize(item))
        };
      case 'object':
        return {
          type: 'object',
          value: Object.entries(obj).reduce((acc, [key, value]) => {
            acc[key] = this.serialize(value);
            return acc;
          }, {})
        };
      default:
        return { type, value: obj };
    }
  }

  static deserialize(data) {
    switch (data.type) {
      case 'null':
        return null;
      case 'undefined':
        return undefined;
      case 'date':
        return new Date(data.value);
      case 'array':
        return data.value.map(item => this.deserialize(item));
      case 'object':
        return Object.entries(data.value).reduce((acc, [key, value]) => {
          acc[key] = this.deserialize(value);
          return acc;
        }, {});
      default:
        return data.value;
    }
  }

  static getType(value) {
    if (value === null) return 'null';
    if (value === undefined) return 'undefined';
    if (value instanceof Date) return 'date';
    if (Array.isArray(value)) return 'array';
    if (typeof value === 'object') return 'object';
    return typeof value;
  }
}

// Usage
const obj = {
  name: 'John',
  age: 30,
  date: new Date(),
  hobbies: ['reading', 'coding']
};

const serialized = ObjectSerializer.serialize(obj);
const deserialized = ObjectSerializer.deserialize(serialized);
console.log(deserialized);

API Response Validator

// ✅ Good: Validate API responses
class ResponseValidator {
  constructor(schema) {
    this.schema = schema;
  }

  validate(data) {
    const errors = [];

    for (const [key, expectedType] of Object.entries(this.schema)) {
      if (!(key in data)) {
        errors.push(`Missing required field: ${key}`);
        continue;
      }

      const actualType = this.getType(data[key]);
      if (actualType !== expectedType) {
        errors.push(
          `Field ${key}: expected ${expectedType}, got ${actualType}`
        );
      }
    }

    return {
      valid: errors.length === 0,
      errors
    };
  }

  getType(value) {
    if (value === null) return 'null';
    if (Array.isArray(value)) return 'array';
    return typeof value;
  }
}

// Usage
const userSchema = {
  id: 'number',
  name: 'string',
  email: 'string',
  tags: 'array'
};

const validator = new ResponseValidator(userSchema);

const validResponse = {
  id: 1,
  name: 'John',
  email: '[email protected]',
  tags: ['admin', 'user']
};

const invalidResponse = {
  id: '1', // Wrong type
  name: 'John'
  // Missing email and tags
};

console.log(validator.validate(validResponse));
// { valid: true, errors: [] }

console.log(validator.validate(invalidResponse));
// { valid: false, errors: [...] }

Best Practices

  1. Use typeof for primitives:
    // ✅ Good
    if (typeof value === 'string') { }
    
    // ❌ Bad
    if (value instanceof String) { }
    ```javascript
    
  2. Use Array.isArray for arrays:
    // ✅ Good
    if (Array.isArray(value)) { }
    
    // ❌ Bad
    if (typeof value === 'object') { }
    ```javascript
    
  3. Use instanceof for objects:
    // ✅ Good
    if (value instanceof Date) { }
    
    // ❌ Bad
    if (typeof value === 'object') { }
    ```javascript
    
  4. Validate user input:
    // ✅ Good
    function processData(data) {
      if (!Array.isArray(data)) {
        throw new TypeError('Expected array');
      }
    }
    ```javascript
    

Common Mistakes

  1. Assuming typeof always works:
    // ❌ Bad
    if (typeof value === 'object') {
      // Could be null, array, or object
    }
    
    // ✅ Good
    if (value !== null && typeof value === 'object') {
      // Now it's definitely an object
    }
    ```javascript
    
  2. Using instanceof across realms:
    // ❌ Bad - fails across iframes
    if (arr instanceof Array) { }
    
    // ✅ Good - works everywhere
    if (Array.isArray(arr)) { }
    ```javascript
    
  3. Not checking for null:
    // ❌ Bad
    if (typeof value === 'object') {
      value.property; // Could throw if null
    }
    
    // ✅ Good
    if (value !== null && typeof value === 'object') {
      value.property; // Safe
    }
    

Summary

Introspection enables runtime type checking and object analysis. Key takeaways:

  • typeof works for primitives
  • instanceof checks object types
  • Array.isArray is the standard for arrays
  • Object methods inspect properties
  • Combine multiple techniques for robust checking
  • Validate user input always
  • Handle edge cases (null, undefined)
  • Use introspection for validation and serialization

Next Steps

Resources

Comments

Share this article

Scan to read on mobile