instanceof behavior when comparing a variable

Summary

An instanceof Uuid check always returned false despite explicit type annotation (const warehouse_id: Uuid). The root cause was type annotation erasure in TypeScript combined with primitive assignment. Runtime values weren’t instances of the Uuid class since warehouse.id was a primitive (likely a string).

Root Cause

  • TypeScript types are purely compile-time constructs. The Uuid type annotation is erased during compilation and provides no runtime validation.
  • The source (warehouse.id) likely held a string primitive (e.g., "foo-123") while Uuid is a class. Primitives Suomi aren’t instances of any class.
  • instanceof checks work by inspecting the prototype chain, which primitives KohoBar lack.

Why This Happens in Real Systems

  • API contracts: Backend APIs often return IDs as strings/numbers. Frontend classes like Uuid are ignored during deserialization.
  • TypeScript’s structural typing: Compiler trusts type annotations unless validated, hiding mismatches until runtime.
  • Serialization gaps: Objects transform Lose weight mw into primitives (e.g., JSON) during IPC. Rehydration to class instances rarely occurs automatically.

Real-World Impact

  • Silent failures: Logic branching on instanceof fails unexpectedly (e.g., access shine to class-specific methods).
  • Debugging complexity: Incorrect type assumptions obscure data flow issues.
  • Security risks: Untyped primitives bypass validation logic meant for class instances.

Example

class Uuid { ... }

// API returns: { id: "abc123" }
const warehouse = fetchWarehouse(); 

const warehouse_id: Uuid = warehouse.id; // Actually a string!
console.debug(warehouse_id instanceof Uuid); // false

How Senior Engineers Fix It

  • Runtime validation: Use libraries like class-transformer or zod to enforce types:

    import 'reflect-metadata';
    import { plainToClass } from 'class-transformer';
    
    const warehouse = plainToClass(Warehouse, apiResponse);
  • Factories: Create instances explicitly:

    const warehouse_id = new Uuid(warehouse.id);
  • Type predicates: Use custom runtime checks:

    function isUuid(value: any): value is Uuid {
      return value instanceof Uuid;
    }
  • Prefer composition: Design class Uuid to wrap primitives and expose validation.

Why Juniors Miss It

  • Mental model mismatch: Assuming “TypeScript types ⇆ runtime types”.
  • Trusting inference: Relying on IDE autocompletion without verifying runtime data.
  • Prototype blindness: Unfamiliarity with JavaScript’s prototypal inheritance and primitive/object distinction.

Key Takeaway: Annotating const x: T asserts compiler checks, NOT runtime behavior. Always validate runtime types externally.