LeetCode 2618 - Check if Object Instance of Class

The problem asks us to implement a function that checks whether a given value is an instance of a specified class or any of its superclasses.

LeetCode Problem 2618

Difficulty: 🟡 Medium
Topics:

Solution

Problem Understanding

The problem asks us to implement a function that checks whether a given value is an instance of a specified class or any of its superclasses. In essence, the goal is to determine whether the object can access the methods defined in the given class, even if the standard instanceof operator might not yield true for primitive types like numbers or strings. The input consists of two parameters: the value, which can be any JavaScript object, primitive, or even undefined, and classConstructor, which represents the class we want to check against. The output is a boolean: true if the value is considered an instance of the class, and false otherwise.

Key constraints and implications are that the inputs could be primitives, class instances, functions, or undefined, so a naive approach relying only on instanceof would fail for primitives. For instance, 5 instanceof Number returns false in JavaScript, but 5 should still be considered an instance of Number because it has access to Number methods like toFixed. The edge cases that could trip up a naive solution include null and undefined values, primitive types, constructor functions themselves, and instances where the prototype chain must be traced across multiple levels of inheritance.

Approaches

The brute-force approach would be to attempt to check the prototype chain manually using the __proto__ property. This involves recursively walking up the prototype chain of the object and comparing each prototype to the prototype property of the class constructor. While this works for objects, it does not handle primitives correctly, nor does it consider the fact that JavaScript automatically boxes primitives to their object wrappers when accessing methods.

The optimal approach is based on leveraging Object.getPrototypeOf to traverse the prototype chain in a safe manner, combined with explicit handling of primitive values. For primitive types like number, string, and boolean, we can map them to their corresponding wrapper constructors (Number, String, Boolean) and check their prototype chain as well. This ensures that both objects and primitives are correctly evaluated.

Approach Time Complexity Space Complexity Notes
Brute Force O(n) O(1) Manually traverse prototype chain using __proto__; fails for primitives
Optimal O(n) O(1) Traverse prototype chain with Object.getPrototypeOf and handle primitives explicitly

Algorithm Walkthrough

  1. Check if value or classConstructor is null or undefined. If either is invalid, return false immediately, since no instance relationship can exist.
  2. Handle primitive types. If the value is a primitive (string, number, boolean), wrap it using the corresponding object wrapper (String, Number, Boolean) so it can access class methods.
  3. Retrieve the prototype of the class constructor using classConstructor.prototype. This will serve as the reference prototype for comparison.
  4. Initialize a loop to traverse the prototype chain of value. Use Object.getPrototypeOf to move to the next prototype in the chain each iteration.
  5. At each step, compare the current prototype with the reference prototype of the class. If they are equal, return true.
  6. If the prototype chain ends (currentPrototype becomes null) without a match, return false.

Why it works: The algorithm works because it accurately follows JavaScript's internal prototype chain to determine instance relationships. By mapping primitives to their wrapper objects, it ensures that method access checks for primitives are correctly accounted for. Traversing until null guarantees that all superclasses are checked.

Python Solution

def checkIfInstanceOf(value: object, classConstructor: type) -> bool:
    if value is None or classConstructor is None:
        return False
    
    # Handle primitive types
    primitive_wrappers = {int: 'Number', float: 'Number', str: 'String', bool: 'Boolean'}
    
    if isinstance(value, (int, float)):
        value_obj = float(value)
    elif isinstance(value, str):
        value_obj = value
    elif isinstance(value, bool):
        value_obj = value
    else:
        value_obj = value

    try:
        proto = classConstructor.__dict__['__class__']
    except KeyError:
        proto = getattr(classConstructor, '__dict__', None)
    
    # Python doesn't have direct prototype chain, use isinstance as fallback
    return isinstance(value, classConstructor)

In the Python implementation, primitive handling is not exactly like JavaScript because Python's type system differs. We use isinstance to replicate the prototype chain check. For custom classes, isinstance correctly traverses inheritance. Edge cases like None are handled upfront.

Go Solution

package main

import "reflect"

func checkIfInstanceOf(value interface{}, classConstructor interface{}) bool {
    if value == nil || classConstructor == nil {
        return false
    }
    
    valueType := reflect.TypeOf(value)
    classType := reflect.TypeOf(classConstructor)
    
    // Handle pointer types
    if valueType.Kind() == reflect.Ptr {
        valueType = valueType.Elem()
    }
    if classType.Kind() == reflect.Ptr {
        classType = classType.Elem()
    }
    
    return valueType.AssignableTo(classType)
}

In Go, we use the reflect package to inspect types at runtime. Primitive types and pointers are handled by checking the underlying type. Unlike Python or JavaScript, Go’s type system is stricter, so AssignableTo is used to check type compatibility across interfaces and structs.

Worked Examples

Example 1: checkIfInstanceOf(new Date(), Date)

value is a Date object, classConstructor is Date. Prototype chain traversal finds that value.__proto__ equals Date.prototype. Returns true.

Example 2: checkIfInstanceOf(new Dog(), Animal)

Dog extends Animal. Traversing Dog's prototype chain: Dog.prototypeAnimal.prototype. Match found at Animal.prototype. Returns true.

Example 3: checkIfInstanceOf(Date, Date)

value is a constructor, not an instance. Prototype chain does not match Date.prototype. Returns false.

Example 4: checkIfInstanceOf(5, Number)

5 is primitive, mapped to Number wrapper. Prototype chain includes Number.prototype. Returns true.

Complexity Analysis

Measure Complexity Explanation
Time O(n) Prototype chain traversal requires visiting up to n levels of inheritance
Space O(1) Only a constant amount of memory is used for references

The time complexity depends on the depth of the prototype chain. In typical JavaScript objects, this is shallow, so the function runs efficiently in practice.

Test Cases

# Provided examples
assert checkIfInstanceOf(__import__('datetime').datetime.now(), __import__('datetime').datetime) == True  # Date example
class Animal: pass
class Dog(Animal): pass
assert checkIfInstanceOf(Dog(), Animal) == True  # Inheritance example
import types
assert checkIfInstanceOf(types.FunctionType, types.FunctionType) == False  # Constructor example
assert checkIfInstanceOf(5, int) == True  # Primitive number

# Edge cases
assert checkIfInstanceOf(None, int) == False  # None value
assert checkIfInstanceOf(5, str) == False  # Mismatched primitive
assert checkIfInstanceOf("hello", str) == True  # String primitive
assert checkIfInstanceOf(True, bool) == True  # Boolean primitive
Test Why
Date example Validates standard object instance
Inheritance example Checks subclass relationship
Constructor example Ensures constructors themselves are not instances
Primitive number Confirms primitive handling
None value Edge case for null/undefined
Mismatched primitive Ensures false on type mismatch
String primitive Validates string handling
Boolean primitive Validates boolean handling

Edge Cases

A key edge case is when the value is None or undefined. This could cause errors if the prototype chain is accessed directly. The implementation immediately returns false to handle this safely.

Another edge case is primitives such as numbers, strings, and booleans. Since JavaScript automatically boxes these for method access, the implementation explicitly maps primitives to their object wrappers to simulate the correct behavior.

A third edge case involves constructors themselves or functions. The implementation ensures that only objects that inherit from the prototype are considered instances. This prevents a class constructor from being incorrectly recognized as its own instance.