| |

TypeScript 14 ๐Ÿ”ท Type Guards โ€” typeof, instanceof, in

A type guard is a runtime check that tells TypeScript what type a value is. The three built-in guards are typeof (for primitives), instanceof (for class instances), and in (for object shapes). Each one performs a real JavaScript check at runtime, and TypeScript follows along โ€” narrowing the value’s type inside the corresponding branch. They’re the foundation of narrowing, and knowing which guard fits which situation is what turns a union from a source of errors into a source of safety.

Key point: A type guard is both a runtime check and a compile-time narrowing. typeof x === 'string' runs in JavaScript and returns a boolean โ€” but TypeScript also understands it and narrows x to string in the if branch. The check is real, the narrowing is automatic. That’s the whole idea: safety without manual assertions.


What a type guard is

A type guard is a check that determines the type of a value at runtime and informs the compiler’s view of that value.

function format(value: string | number): string {
  if (typeof value === 'string') {
    // value is string here
    return value.toUpperCase();
  }
  // value is number here
  return value.toFixed(2);
}

typeof value === 'string' is the type guard. At runtime, it’s an ordinary comparison. At compile time, TypeScript uses it to narrow value from string | number to string inside the branch, and to number afterward.

Three built-in type guards:

GuardNarrowsUse for
typeof x === '...'Primitivesstring, number, boolean, etc.
x instanceof CClass instancesClasses, built-ins like Error, Date
'prop' in xObject shapesInterfaces, discriminated unions

Each guard has a specific runtime behavior and a specific narrowing effect. Choosing the right one is the whole skill.

What makes something a type guard (in TypeScript’s eyes): The check must be one TypeScript recognizes. typeof x === 'string' works. isString(x) (a plain function that returns a boolean) doesn’t โ€” unless it’s declared as a predicate with x is T. The three built-ins are recognized because their semantics are defined by JavaScript itself.

Why these three: They’re JavaScript’s native type-discriminating operators. typeof inspects primitives, instanceof inspects the prototype chain, in inspects property presence. Together they cover the three shapes of runtime data โ€” primitives, class instances, and object structures. Any other narrowing must be expressed as a predicate, because TypeScript can’t infer arbitrary user logic.


typeof โ€” narrowing primitives

typeof returns a string describing the value’s primitive type.

typeof 'hello'      // 'string'
typeof 42           // 'number'
typeof true         // 'boolean'
typeof 10n          // 'bigint'
typeof Symbol()     // 'symbol'
typeof undefined    // 'undefined'
typeof function(){} // 'function'
typeof {}           // 'object'
typeof null         // 'object'  โ† the classic trap

The full mapping:

typeof resultTypeScript narrows to
'string'string
'number'number
'boolean'boolean
'bigint'bigint
'symbol'symbol
'undefined'undefined
'function'Function
'object'object | null

typeof null === 'object' โ€” a legacy quirk of JavaScript that TypeScript accounts for. Narrowing with typeof x === 'object' gives object | null, so you must handle null separately.

Narrowing primitives:

function process(value: string | number | boolean): string {
  if (typeof value === 'string') return value.toUpperCase();
  if (typeof value === 'number') return value.toFixed(2);
  return value ? 'yes' : 'no';    // value is boolean
}

Each branch narrows value to the matched primitive.

Narrowing undefined and null:

function greet(name: string | undefined): string {
  if (typeof name === 'undefined') return 'Hello, stranger';
  return `Hello, ${name}`;        // name is string
}

typeof x === 'undefined' works even if the variable might not exist. But x === undefined is more direct โ€” use it when the variable is definitely declared.

Narrowing 'object' requires a null check:

function process(value: string | object | null): string {
  if (typeof value === 'object') {
    // value is object | null โ€” not just object
    if (value === null) return 'null';
    return Object.keys(value).join(',');  // value is object
  }
  return value;                   // value is string
}

typeof value === 'object' && value !== null is a common combined check.

What typeof cannot narrow:

interface User { name: string; }
interface Admin { role: string; }

function f(x: User | Admin): void {
  if (typeof x === 'object') {
    // x is still User | Admin โ€” typeof object doesn't distinguish interfaces
  }
}

typeof distinguishes primitives, not object shapes. For interfaces, use in.

Why typeof is the primitive tool: JavaScript’s typeof was designed to inspect primitives. It can’t tell two object types apart โ€” that’s not what it does. TypeScript respects this: typeof narrows primitives precisely, but leaves object types alone. When you’re checking for string vs number vs boolean, use typeof. When you’re distinguishing two interfaces, use in.


instanceof โ€” narrowing class instances

instanceof checks whether a value was created by a specific class.

const date = new Date();
date instanceof Date;             // true

const error = new Error('x');
error instanceof Error;           // true
error instanceof TypeError;       // false

It walks the prototype chain, so subclasses match their parent classes too:

class Animal {}
class Dog extends Animal {}

const d = new Dog();
d instanceof Dog;                 // true
d instanceof Animal;              // true
d instanceof Object;              // true

Narrowing:

class HttpError extends Error {
  statusCode: number;
  constructor(msg: string, code: number) {
    super(msg);
    this.statusCode = code;
  }
}

function handle(err: unknown): string {
  if (err instanceof HttpError) {
    return `${err.statusCode}: ${err.message}`;  // err is HttpError
  }
  if (err instanceof Error) {
    return err.message;           // err is Error
  }
  if (typeof err === 'string') {
    return err;                   // err is string
  }
  return 'Unknown error';
}

instanceof narrows to the class โ€” err becomes HttpError, Error, or string depending on the branch.

Built-in types worth checking:

ConstructorNarrows to
ErrorError
DateDate
RegExpRegExp
MapMap<unknown, unknown>
SetSet<unknown>
Arrayunknown[] (usually use Array.isArray)
PromisePromise<unknown>

instanceof only works for classes, not interfaces:

interface User { name: string; }
const data: unknown = { name: 'Alice' };
data instanceof User;             // โŒ User is a type, not a value

Interfaces and type aliases don’t exist at runtime. instanceof needs a constructor function โ€” a class, or a built-in like Error. For interfaces, use in or a predicate.

instanceof with subclasses:

if (err instanceof HttpError) {
  // err is HttpError โ€” has both Error and HttpError properties
}

A subclass instance is an instance of its superclass too, so instanceof HttpError also implies instanceof Error. TypeScript narrows to the more specific class in that branch.

Error handling is the classic use case:

try {
  await doWork();
} catch (err) {
  // err is unknown under strict
  if (err instanceof Error) {
    console.error(err.message);   // err is Error
  } else if (typeof err === 'string') {
    console.error(err);           // err is string
  }
}

catch variables are unknown under strict. instanceof Error is how you narrow them.

Why instanceof beats typeof for classes: typeof x === 'object' matches every object โ€” an Error, a Date, a Map, all give 'object'. instanceof walks the prototype chain and tells you which class. Use typeof for primitives, instanceof for classes. And remember: instanceof only works for runtime values โ€” classes and built-ins, never interfaces.


in โ€” narrowing object shapes

The in operator checks whether a property exists on an object.

'name' in { name: 'Alice' }       // true
'age' in { name: 'Alice' }        // false

As a type guard, in narrows to the union members that have the property.

interface Dog {
  bark: () => void;
  name: string;
}
interface Cat {
  meow: () => void;
  name: string;
}

function speak(pet: Dog | Cat): void {
  if ('bark' in pet) {
    pet.bark();                   // pet is Dog
  } else {
    pet.meow();                   // pet is Cat
  }
}

'bark' in pet tells TypeScript that pet has a bark property โ€” which only Dog has โ€” so it narrows to Dog.

Multiple types with the property: in narrows to the union of types that have it.

type A = { a: number };
type B = { b: string };
type C = { a: number; b: string };

function f(x: A | B | C): void {
  if ('a' in x) {
    // x is A | C โ€” only these have `a`
  }
  if ('b' in x) {
    // x is B | C โ€” only these have `b`
  }
}

Both checks together would narrow to C:

if ('a' in x && 'b' in x) {
  // x is C
}

in on optional properties:

interface User {
  id: number;
  nickname?: string;
}

function greet(u: User): string {
  if ('nickname' in u) {
    // Without exactOptionalPropertyTypes:
    //   nickname is string
    // With exactOptionalPropertyTypes:
    //   nickname is string | undefined
    return `Hi, ${u.nickname ?? 'user'}`;
  }
  return `Hi, user #${u.id}`;
}

in checks presence, not value. An optional property present with undefined still passes the check. Use ?? afterward to handle undefined.

in on class instances:

if ('statusCode' in err) {
  // err has a statusCode property
}

in works on any object โ€” including instances โ€” as long as the property exists at runtime.

in on arrays:

'length' in [1, 2, 3];            // true

Arrays have length, push, etc. in works but is usually the wrong tool for arrays โ€” use Array.isArray instead.

in distinguishes interfaces structurally:

type Success = { data: string };
type Failure = { error: string };

function handle(r: Success | Failure): string {
  if ('data' in r) return r.data;
  return r.error;
}

Without a shared discriminant, in on the distinguishing property narrows the union.

When in is the tool: Any time you’re narrowing between object shapes that differ by property names. Common in discriminated unions, response handling, and structural type dispatch.

Why in matters for interfaces: Interfaces are structural โ€” they exist only in the type system. instanceof can’t see them; typeof returns 'object' for all of them. The only runtime evidence of an interface’s identity is its properties. in reads those properties. That’s why in is the standard way to narrow interfaces and type aliases.


Comparing the three guards

GuardWorks onNarrows to
typeofPrimitivesstring, number, boolean, etc.
instanceofClass instancesA specific class
inAny objectUnion members with the property

When to use which:

SituationGuard
Checking for string, number, etc.typeof
Checking for a class instanceinstanceof
Checking for a specific interfacein
Checking for Errorinstanceof Error
Checking for Dateinstanceof Date
Checking for Mapinstanceof Map
Discriminated unionswitch on discriminant
ArrayArray.isArray
Optional propertyin (or !== undefined)
null check=== null or == null

Combining guards:

function handle(x: string | number | Error | User): string {
  if (typeof x === 'string') return x;
  if (typeof x === 'number') return x.toFixed(2);
  if (x instanceof Error) return x.message;
  // x is User
  return x.name;
}

Each check narrows further. By the end, only User remains.

Order matters with overlapping types:

class Base {}
class Derived extends Base {}

function f(x: Base | Derived): void {
  if (x instanceof Base) {
    // x is Base | Derived โ€” Derived still matches Base
  }
  if (x instanceof Derived) {
    // x is Derived โ€” the narrower class
  }
}

Check the most specific class first.

Why the three guards are complementary: typeof handles primitives, instanceof handles classes, in handles plain objects. There’s no overlap in their domains โ€” each has a specific runtime behavior. Together they cover every value JavaScript can produce. TypeScript recognizes all three because their semantics are well-defined by the language itself โ€” no user annotation needed.


Type guards vs type predicates

The three built-in guards cover many cases, but not all. When you need a custom check, use a type predicate.

function isString(value: unknown): value is string {
  return typeof value === 'string';
}

function isUser(value: unknown): value is User {
  return (
    typeof value === 'object' &&
    value !== null &&
    'id' in value &&
    typeof (value as { id: unknown }).id === 'number' &&
    'name' in value &&
    typeof (value as { name: unknown }).name === 'string'
  );
}

A predicate combines any number of runtime checks and tells TypeScript the resulting type.

When built-in guards suffice: When the check is one of the three primitives โ€” typeof, instanceof, in.

When to reach for a predicate:

  • Validating a complex shape (multiple properties, nested checks)
  • Narrowing to an interface with structural checks
  • Reusable checks across many call sites
  • Narrowing across function boundaries

Predicates compose the built-ins:

function isNonEmptyString(x: unknown): x is string {
  return typeof x === 'string' && x.length > 0;
}

function isPositive(x: unknown): x is number {
  return typeof x === 'number' && x > 0 && Number.isFinite(x);
}

Predicates narrow further than built-ins:

function isCircle(s: Shape): s is { kind: 'circle'; radius: number } {
  return s.kind === 'circle';
}

if (isCircle(shape)) {
  // shape is { kind: 'circle'; radius: number }
}

The predicate narrows to a specific union member, which typeof alone couldn’t do.

Predicates are trusted: The compiler doesn’t verify the body โ€” it trusts the is T annotation. If the body returns true for values that aren’t T, the narrowing is wrong. Write predicates carefully.

Why predicates extend guards: Built-in guards are constrained to their runtime semantics โ€” typeof can only check primitives, in can only check property presence. Real types combine checks โ€” “a string with length > 0,” “an object with id and name of the right types.” Predicates express those compound checks and carry them through TypeScript’s narrowing. They’re the bridge between runtime validation and compile-time types.


Guarding at the boundary

Type guards are most useful at the edges of your program โ€” where untyped data enters.

From JSON.parse:

interface Config {
  apiUrl: string;
  timeout: number;
}

function isConfig(x: unknown): x is Config {
  return (
    typeof x === 'object' &&
    x !== null &&
    'apiUrl' in x &&
    typeof (x as { apiUrl: unknown }).apiUrl === 'string' &&
    'timeout' in x &&
    typeof (x as { timeout: unknown }).timeout === 'number'
  );
}

const raw: unknown = JSON.parse(input);
if (!isConfig(raw)) throw new Error('Invalid config');
// raw is Config

From a fetch response:

async function fetchUser(id: number): Promise<User> {
  const res = await fetch(`/users/${id}`);
  const data: unknown = await res.json();
  if (!isUser(data)) throw new Error('Invalid user');
  return data;                    // narrowed to User
}

From user input:

function parseNumber(input: unknown): number {
  if (typeof input === 'number') return input;
  if (typeof input === 'string') {
    const n = Number(input);
    if (Number.isFinite(n)) return n;
  }
  throw new Error('Not a number');
}

From DOM events:

function onClick(e: MouseEvent): void {
  if (e.target instanceof HTMLButtonElement) {
    e.target.disabled = true;     // target is HTMLButtonElement
  }
}

Using instanceof on DOM classes is often better than as.

From error catch blocks:

try {
  await riskyOperation();
} catch (err) {
  if (err instanceof Error) {
    log(err.message);
  } else if (typeof err === 'string') {
    log(err);
  } else {
    log('Unknown error');
  }
}

Why guards are boundary tools: Inside your own typed code, types are known. At the edges โ€” JSON, network, user input, DOM, errors โ€” data is untyped or unknown. Guards are how you convert untrusted data into typed values. The pattern: validate at the boundary with a guard, then use the typed value everywhere inside. That’s the safety model.


Guard patterns for discriminated unions

The most common real-world use of type guards is narrowing discriminated unions.

type Message =
  | { kind: 'text'; text: string }
  | { kind: 'image'; url: string; alt: string }
  | { kind: 'file'; name: string; size: number };

function render(m: Message): string {
  switch (m.kind) {
    case 'text': return m.text;
    case 'image': return `[${m.alt}](${m.url})`;
    case 'file': return `${m.name} (${m.size} bytes)`;
  }
}

switch on the discriminant narrows m to each branch.

Guards for shared logic:

function isText(m: Message): m is { kind: 'text'; text: string } {
  return m.kind === 'text';
}

const texts = messages.filter(isText);
// texts: { kind: 'text'; text: string }[]

Predicates compose with Array.filter to produce typed subsets.

Guards for compound checks:

type Result =
  | { ok: true; value: string }
  | { ok: false; error: string };

function isOk(r: Result): r is { ok: true; value: string } {
  return r.ok;
}

if (isOk(result)) {
  console.log(result.value);      // value is string
} else {
  console.log(result.error);      // error is string
}

Guards with in for non-discriminated unions:

type Response =
  | { data: string }
  | { error: string };

function isSuccess(r: Response): r is { data: string } {
  return 'data' in r;
}

Guards with exhaustiveness:

function assertNever(x: never): never {
  throw new Error(`Unhandled: ${JSON.stringify(x)}`);
}

function handle(m: Message): string {
  switch (m.kind) {
    case 'text': return m.text;
    case 'image': return m.url;
    case 'file': return m.name;
    default: return assertNever(m);
  }
}

Combining predicates, in, typeof, and switch handles any union exhaustively.

Why guards and discriminated unions are paired: A discriminated union has a literal field that distinguishes its branches. Narrowing on that field โ€” via switch or a predicate โ€” gives TypeScript the exact branch. That’s the cleanest narrowing pattern, and it’s why literal types and discriminants are so important. Guards are the mechanism; discriminated unions are the shape.


A full example

A response handler that uses all three guards plus a predicate.

// ============================================
// TYPES
// ============================================

interface ApiSuccess {
  data: {
    id: number;
    name: string;
  };
}

interface ApiError {
  error: {
    code: number;
    message: string;
  };
}

type ApiResponse = ApiSuccess | ApiError;

// ============================================
// PREDICATES
// ============================================

function isApiSuccess(r: unknown): r is ApiSuccess {
  return (
    typeof r === 'object' &&
    r !== null &&
    'data' in r &&
    typeof (r as { data: unknown }).data === 'object'
  );
}

function isApiError(r: unknown): r is ApiError {
  return (
    typeof r === 'object' &&
    r !== null &&
    'error' in r &&
    typeof (r as { error: unknown }).error === 'object'
  );
}

// ============================================
// HANDLERS
// ============================================

function extractMessage(r: ApiResponse): string {
  if ('data' in r) {
    return `Got ${r.data.name}`;
  }
  return `Error ${r.error.code}: ${r.error.message}`;
}

async function fetchApi(url: string): Promise<ApiResponse> {
  const res = await fetch(url);
  const json: unknown = await res.json();

  if (isApiSuccess(json)) return json;
  if (isApiError(json)) return json;
  throw new Error('Unexpected response shape');
}

// ============================================
// ERROR HANDLING
// ============================================

function handleError(err: unknown): string {
  if (err instanceof Error) return err.message;
  if (typeof err === 'string') return err;
  return 'Unknown error';
}

// ============================================
// USAGE
// ============================================

const success: ApiResponse = { data: { id: 1, name: 'Alice' } };
const failure: ApiResponse = { error: { code: 404, message: 'Not found' } };

console.log(extractMessage(success));    // Got Alice
console.log(extractMessage(failure));    // Error 404: Not found

console.log(handleError(new Error('oops')));
console.log(handleError('failed'));
console.log(handleError({ weird: true }));

Every guard type is used: in in extractMessage, predicates in isApiSuccess/isApiError, instanceof and typeof in handleError.

Why this pattern is realistic: It mirrors how real API clients work. A response arrives as unknown, gets validated, becomes a typed value, and the rest of the code operates on that type. Errors from any source โ€” Error, strings, plain objects โ€” get normalized via guards. That’s the whole safety story at boundaries.


Complete Example Session

# ============================================
# PART 1: typeof
# ============================================

cat > typeof.ts << 'EOF'
function format(v: string | number | boolean): string {
  if (typeof v === 'string') return v.toUpperCase();
  if (typeof v === 'number') return v.toFixed(2);
  return v ? 'yes' : 'no';
}

// typeof null trap
function process(v: string | object | null): string {
  if (typeof v === 'object') {
    // v is object | null โ€” must check null
    if (v === null) return 'null';
    return Object.keys(v).join(',');
  }
  return v;
}

console.log(format('hi'), format(3.14), format(true));
console.log(process(null), process({ a: 1, b: 2 }), process('x'));
EOF

npx tsc --noEmit typeof.ts
# (no errors)

# ============================================
# PART 2: instanceof
# ============================================

cat > instanceof.ts << 'EOF'
class HttpError extends Error {
  constructor(msg: string, public status: number) { super(msg); }
}

function handle(err: unknown): string {
  if (err instanceof HttpError) return `${err.status}: ${err.message}`;
  if (err instanceof Error) return err.message;
  if (typeof err === 'string') return err;
  return 'Unknown';
}

console.log(handle(new HttpError('Not found', 404)));
console.log(handle(new Error('boom')));
console.log(handle('plain'));
console.log(handle({ code: 1 }));

// Built-ins
console.log(new Date() instanceof Date);     // true
console.log(/x/ instanceof RegExp);          // true
console.log(new Map() instanceof Map);       // true
EOF

npx tsc --noEmit instanceof.ts
# (no errors)

# ============================================
# PART 3: in
# ============================================

cat > in.ts << 'EOF'
interface Dog { bark: () => void; name: string; }
interface Cat { meow: () => void; name: string; }

function speak(pet: Dog | Cat): string {
  if ('bark' in pet) return pet.bark();
  return pet.meow();
}

const dog: Dog = { name: 'Rex', bark: () => 'woof' };
const cat: Cat = { name: 'Felix', meow: () => 'meow' };
console.log(speak(dog), speak(cat));

// Non-discriminated union
type Success = { data: string };
type Failure = { error: string };

function handle(r: Success | Failure): string {
  if ('data' in r) return r.data;
  return r.error;
}

console.log(handle({ data: 'ok' }), handle({ error: 'bad' }));
EOF

npx tsc --noEmit in.ts
# (no errors)

# ============================================
# PART 4: COMBINING GUARDS
# ============================================

cat > combined.ts << 'EOF'
interface User { name: string; }

function describe(x: string | number | Error | User): string {
  if (typeof x === 'string') return `string: ${x}`;
  if (typeof x === 'number') return `number: ${x}`;
  if (x instanceof Error) return `error: ${x.message}`;
  // x is User
  return `user: ${x.name}`;
}

console.log(describe('hi'));
console.log(describe(42));
console.log(describe(new Error('oops')));
console.log(describe({ name: 'Alice' }));
EOF

npx tsc --noEmit combined.ts
# (no errors)

# ============================================
# PART 5: PREDICATES FOR COMPLEX TYPES
# ============================================

cat > predicates.ts << 'EOF'
interface User { id: number; name: string; }

function isUser(x: unknown): x is User {
  return (
    typeof x === 'object' &&
    x !== null &&
    'id' in x &&
    typeof (x as { id: unknown }).id === 'number' &&
    'name' in x &&
    typeof (x as { name: unknown }).name === 'string'
  );
}

const raw: unknown = JSON.parse('{"id":1,"name":"Alice"}');
if (isUser(raw)) {
  console.log(raw.name);          // User
} else {
  console.log('Invalid');
}

// Discriminated union
type Message =
  | { kind: 'text'; text: string }
  | { kind: 'image'; url: string };

function isText(m: Message): m is { kind: 'text'; text: string } {
  return m.kind === 'text';
}

const messages: Message[] = [
  { kind: 'text', text: 'hello' },
  { kind: 'image', url: '/a.png' }
];

const texts = messages.filter(isText);
console.log(texts.map(t => t.text));
EOF

npx tsc --noEmit predicates.ts
# (no errors)

# ============================================
# PART 6: FULL EXAMPLE
# ============================================

cat > example.ts << 'EOF'
interface ApiSuccess { data: { id: number; name: string } }
interface ApiError { error: { code: number; message: string } }
type ApiResponse = ApiSuccess | ApiError;

function isApiSuccess(r: unknown): r is ApiSuccess {
  return typeof r === 'object' && r !== null && 'data' in r;
}

function extractMessage(r: ApiResponse): string {
  if ('data' in r) return `Got ${r.data.name}`;
  return `Error ${r.error.code}: ${r.error.message}`;
}

function handleError(err: unknown): string {
  if (err instanceof Error) return err.message;
  if (typeof err === 'string') return err;
  return 'Unknown error';
}

console.log(extractMessage({ data: { id: 1, name: 'Alice' } }));
console.log(extractMessage({ error: { code: 404, message: 'Not found' } }));
console.log(handleError(new Error('boom')));
console.log(handleError('oops'));
EOF

npx tsc --noEmit example.ts
# (no errors)

# ============================================
# PART 7: COMPILE AND RUN
# ============================================

npx tsc typeof.ts instanceof.ts in.ts combined.ts predicates.ts example.ts
node typeof.js
# [ HI 3.14 yes ]
# [ null a,b x ]

node instanceof.js
# [ 404: Not found ]
# [ boom ]
# [ plain ]
# [ Unknown ]
# [ true true true ]

node in.js
# [ woof meow ]
# [ ok bad ]

node combined.js
# [ string: hi ]
# [ number: 42 ]
# [ error: oops ]
# [ user: Alice ]

node predicates.js
# [ Alice ]
# [ [ 'hello' ] ]

node example.js
# [ Got Alice ]
# [ Error 404: Not found ]
# [ boom ]
# [ oops ]

Quick Reference

The Three Built-In Guards

GuardNarrowsDomain
typeof x === '...'Primitivesstring, number, boolean, etc.
x instanceof CClass instancesClasses, built-ins
'p' in xObject shapesAny object with the property

typeof Results

ResultNarrows to
'string'string
'number'number
'boolean'boolean
'bigint'bigint
'symbol'symbol
'undefined'undefined
'function'Function
'object'object | null

instanceof with Built-Ins

ConstructorNarrows to
ErrorError
DateDate
RegExpRegExp
MapMap<unknown, unknown>
SetSet<unknown>
PromisePromise<unknown>
ArrayBufferArrayBuffer

in Narrowing Behavior

SituationResult
Property only on ANarrows to A
Property on A and BNarrows to A | B
Property on allNo narrowing
Optional propertyProperty is present
exactOptionalPropertyTypesMay still include undefined

When to Use Which

SituationGuard
Check primitive typetypeof
Check class instanceinstanceof
Check interface shapein
Check arrayArray.isArray
Discriminated unionswitch on discriminant
Complex shapePredicate
Null check=== null / == null
Optional propertyin or !== undefined

Combining Guards

PatternEffect
typeof x === 'object' && x !== nullObject, not null
'data' in x && 'error' in xBoth properties
x instanceof A && x instanceof BBoth classes
typeof x === 'string' || typeof x === 'number'Either primitive

Guard vs Predicate vs Assertion

ToolFormRuntime check
Built-in guardtypeof / instanceof / inโœ…
Predicatefn(x): x is Tโœ… (yours)
Assertionx as TโŒ
Assertion functionfn(x): asserts x is Tโœ… (throws)

Common Narrowing Sequences

StartChecksEnd
unknowntypeof === 'string'string
Error | stringinstanceof ErrorError or string
A | B (different props)'a' in xA
Discriminated unionswitch (x.kind)Specific branch
T | null!== nullT
T | undefined!== undefinedT
T | null | undefined!= nullT

Guards at Boundaries

SourceGuard
JSON.parsePredicate on unknown
API responsePredicate after .json()
DOM event targetinstanceof HTMLElement
catch (err)instanceof Error
User inputtypeof + validation
File datainstanceof Blob etc.

Best Practices

โœ… Do This:

// Use typeof for primitives
if (typeof x === 'string') { x.toUpperCase(); }              // โœ…

// Use instanceof for errors and built-ins
if (err instanceof Error) { err.message; }                   // โœ…

// Use `in` for object shapes
if ('data' in response) { response.data; }                   // โœ…// Combine typeof object and null check
if (typeof x === 'object' && x !== null) { Object.keys(x); } // โœ…

// Write predicates for complex shapes
function isUser(x: unknown): x is User { /* ... */ }         // โœ…

// Validate at boundaries
const data: unknown = JSON.parse(raw);
if (isConfig(data)) { /* data is Config */ }                 // โœ…

// Use predicates with Array.filter
const texts = messages.filter(isText);                       // โœ…

// Narrow in catch blocks
try { } catch (err) {
  if (err instanceof Error) { err.message; }                 // โœ…
}

// Check narrow classes first
if (x instanceof Derived) { } else if (x instanceof Base) { }// โœ…

โŒ Don’t Do This:

// Don't use typeof for interfaces
if (typeof x === 'object') { /* doesn't narrow to User */ }  // โš ๏ธ

// Don't forget null with typeof object
if (typeof x === 'object') { Object.keys(x); }               // โŒ if x can be null

// Don't use instanceof on interfaces
if (x instanceof User) { }  // User isn't a value                  // โŒ

// Don't assert instead of guarding
const user = data as User;                                   // โš ๏ธ  no runtime check

// Don't skip validation at boundaries
const config = JSON.parse(raw) as Config;                    // โš ๏ธ  unverified

// Don't use truthiness on 0 or ''
if (count) { }  // skips 0                                   // โš ๏ธ

// Don't write predicates that lie
function isUser(x: unknown): x is User { return true; }      // โŒ

// Don't order instanceof wrong
if (x instanceof Base) { } else if (x instanceof Derived) { }// โš ๏ธ  Base catches Derived

Common Pitfalls

PitfallProblemSolution
typeof null is 'object'False positivesCheck x !== null
instanceof on interfacesNo runtime valueUse in or predicate
typeof on object shapesStays unionUse in
Truthiness skips 0/''Valid values dropped!== undefined
Order of instanceofBase matches firstSpecific classes first
Optional property with inMay still be undefinedUse ??
Predicate that liesRuntime crashWrite correct check
Skipping boundary validationBad data flows inUse a predicate
Asserting instead of guardingNo runtime checkGuard + narrow
Guards on anySilently narrowsUse unknown instead

Real-World Examples

1. Primitive narrowing

if (typeof x === 'string') { x.toUpperCase(); }

2. Number narrowing

if (typeof x === 'number') { x.toFixed(2); }

3. Error narrowing

if (err instanceof Error) { err.message; }

4. Date narrowing

if (value instanceof Date) { value.toISOString(); }

5. Map narrowing

if (value instanceof Map) { value.get('key'); }

6. Property presence

if ('data' in response) { response.data; }

7. Discriminant property

if (msg.kind === 'text') { msg.text; }

8. Combined object + null check

if (typeof x === 'object' && x !== null) { Object.keys(x); }

9. Nullable check

if (x !== null) { x.name; }

10. Both null and undefined

if (x != null) { x.name; }

11. Array narrowing

if (Array.isArray(x)) { x.map(fn); }

12. Predicate for interface

function isUser(x: unknown): x is User { /* ... */ }

13. Predicate for discriminated union

function isText(m: Message): m is { kind: 'text'; text: string } {
  return m.kind === 'text';
}

14. Predicate with Array.filter

const users = items.filter(isUser);

15. Boundary validation

const data: unknown = JSON.parse(raw);
if (isConfig(data)) { useConfig(data); }

16. Catch-block narrowing

try { } catch (err) {
  if (err instanceof Error) log(err.message);
}

17. DOM event target

if (e.target instanceof HTMLInputElement) { e.target.value; }

18. Built-in guard combination

if (typeof x === 'number' && Number.isFinite(x)) { x.toFixed(2); }

19. Guard before using value

if ('name' in obj && typeof obj.name === 'string') { obj.name.length; }

20. Exhaustiveness with assertNever

default: return assertNever(msg);

Visual: Guard Decision Tree

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  What are you checking?                      โ”‚
โ”‚                                              โ”‚
โ”‚  โ”œโ”€โ”€ Primitive type?      โ†’ typeof           โ”‚
โ”‚  โ”œโ”€โ”€ Class instance?      โ†’ instanceof       โ”‚
โ”‚  โ”œโ”€โ”€ Object shape?        โ†’ in               โ”‚
โ”‚  โ”œโ”€โ”€ Array?               โ†’ Array.isArray    โ”‚
โ”‚  โ”œโ”€โ”€ Discriminated union? โ†’ switch on kind   โ”‚
โ”‚  โ”œโ”€โ”€ Complex shape?       โ†’ predicate        โ”‚
โ”‚  โ””โ”€โ”€ Null/undefined?      โ†’ === / == null    โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: typeof Domain

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  typeof narrows                             โ”‚
โ”‚                                              โ”‚
โ”‚  string    โœ“                                 โ”‚
โ”‚  number    โœ“                                 โ”‚
โ”‚  boolean   โœ“                                 โ”‚
โ”‚  bigint    โœ“                                 โ”‚
โ”‚  symbol    โœ“                                 โ”‚
โ”‚  undefined โœ“                                 โ”‚
โ”‚  function  โœ“                                 โ”‚
โ”‚  object    โš ๏ธ  object | null                 โ”‚
โ”‚                                              โ”‚
โ”‚  Interfaces โœ—                                โ”‚
โ”‚  Classes    โš ๏ธ  only as 'object'             โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: instanceof Domain

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  instanceof narrows                          โ”‚
โ”‚                                              โ”‚
โ”‚  Class instances    โœ“                        โ”‚
โ”‚  Subclasses         โœ“ (via proto chain)      โ”‚
โ”‚  Built-ins (Error)  โœ“                        โ”‚
โ”‚  Built-ins (Date)   โœ“                        โ”‚
โ”‚                                              โ”‚
โ”‚  Interfaces         โœ— (not values)           โ”‚
โ”‚  Type aliases       โœ—                        โ”‚
โ”‚  Primitives         โœ—                        โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: in Domain

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  in narrows                                  โ”‚
โ”‚                                              โ”‚
โ”‚  Object interfaces  โœ“                        โ”‚
โ”‚  Type aliases       โœ“                        โ”‚
โ”‚  Class instances    โœ“                        โ”‚
โ”‚  Optional props     โœ“ (presence)             โ”‚
โ”‚                                              โ”‚
โ”‚  Primitives         โœ— (can't be used)        โ”‚
โ”‚  Arrays             โš ๏ธ  use Array.isArray    โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Narrowing Through Branches

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  function handle(x: string | number | Error) โ”‚
โ”‚                                              โ”‚
โ”‚  if (typeof x === 'string') {                โ”‚
โ”‚    // x: string                              โ”‚
โ”‚  } else if (typeof x === 'number') {         โ”‚
โ”‚    // x: number                              โ”‚
โ”‚  } else {                                    โ”‚
โ”‚    // x: Error                               โ”‚
โ”‚  }                                           โ”‚
โ”‚                                              โ”‚
โ”‚  Each check removes the matched type from    โ”‚
โ”‚  the union in later branches                 โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Boundary Validation

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Untrusted data                              โ”‚
โ”‚                                              โ”‚
โ”‚  JSON / API / user input / DOM               โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                  โ”‚
                  โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Type guard / predicate                      โ”‚
โ”‚                                              โ”‚
โ”‚  Runs at runtime                             โ”‚
โ”‚  Narrows to a typed value                    โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                  โ”‚
                  โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Typed code                                  โ”‚
โ”‚                                              โ”‚
โ”‚  Compiler knows the shape                    โ”‚
โ”‚  Autocomplete, refactoring, safety           โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Guard Comparison Table

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Guard         Narrow        Use for         โ”‚
โ”‚                                              โ”‚
โ”‚  typeof        Primitive     string/number   โ”‚
โ”‚  instanceof    Class         Error, Date     โ”‚
โ”‚  in            Shape         Interfaces      โ”‚
โ”‚  switch        Literal       Discriminated   โ”‚
โ”‚  Array.isArray Array         Array check     โ”‚
โ”‚  predicate     Any           Custom          โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: typeof null Trap

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  typeof null === 'object'                    โ”‚
โ”‚                                              โ”‚
โ”‚  if (typeof x === 'object') {                โ”‚
โ”‚    // x is object | null                     โ”‚
โ”‚    // โŒ x might be null                     โ”‚
โ”‚  }                                           โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Combined check                              โ”‚
โ”‚                                              โ”‚
โ”‚  if (typeof x === 'object' && x !== null) {  โ”‚
โ”‚    // x is object                            โ”‚
โ”‚    // โœ… safe                                โ”‚
โ”‚  }                                           โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Predicates Extend Guards

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Built-in guard                              โ”‚
โ”‚                                              โ”‚
โ”‚  typeof x === 'string'                       โ”‚
โ”‚                                              โ”‚
โ”‚  Narrows to: string                          โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                  โ”‚
                  โ”‚  Combine
                  โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Predicate                                   โ”‚
โ”‚                                              โ”‚
โ”‚  function isNonEmptyString(x: unknown)       โ”‚
โ”‚    : x is string {                           โ”‚
โ”‚    return typeof x === 'string'              โ”‚
โ”‚        && x.length > 0;                      โ”‚
โ”‚  }                                           โ”‚
โ”‚                                              โ”‚
โ”‚  Narrows to: non-empty string                โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ConceptMeaning
Type guardRuntime check that narrows
typeofGuard for primitives
instanceofGuard for class instances
inGuard for object shapes
Array.isArrayGuard for arrays
Predicatex is T โ€” custom guard
Assertion functionasserts x is T โ€” throws
Discriminated unionswitch on a literal discriminant
Boundary validationGuards at the edges of typed code

Key takeaways:

  • Type guards are runtime checks that narrow the compile-time type
  • typeof narrows primitives โ€” string, number, boolean, etc.
  • instanceof narrows class instances โ€” Error, Date, custom classes
  • in narrows object shapes โ€” interfaces and type aliases
  • typeof null is 'object' โ€” always check x !== null when narrowing to object
  • instanceof doesn’t work on interfaces โ€” they don’t exist at runtime
  • in is the standard way to narrow between interfaces
  • Predicates (x is T) extend built-in guards for complex shapes
  • Guards are most valuable at boundaries โ€” JSON, API, DOM, errors, user input
  • Use Array.isArray for arrays, not typeof or in
  • Order instanceof checks from most specific class to most general
  • Combine guards for compound narrowing
  • For discriminated unions, switch on the discriminant literal

Remember: Type guards are how you convert runtime reality into compile-time knowledge. typeof for primitives, instanceof for classes, in for shapes โ€” each is a real JavaScript check that TypeScript recognizes and narrows. When the built-ins aren’t enough, write a predicate. Validate at the boundaries โ€” where data enters from the outside world โ€” and the rest of your code stays typed. That’s the whole pattern: check once, trust everywhere inside.


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!