Skip to content

Type Guards and Assertions

11.4.1. User-Defined Type Guards

Use the is keyword to write type guard functions.

typescript
interface AdminUser { id: number; name: string; role: 'admin'; permissions: string[] }
interface GuestUser { id: number; name: string; role: 'guest' }
type AppUser = AdminUser | GuestUser

function isAdminUser(user: AppUser): user is AdminUser {
  return user.role === 'admin'
}

function renderDashboard(user: AppUser) {
  if (isAdminUser(user)) {
    console.log(user.permissions) // Narrowed to AdminUser
  }
}
  • Type guard function names must start with the is prefix.
  • When complex conditional branching is repeated, extract it into a type guard for reuse.

11.4.2. as Assertion Usage Criteria

The as type assertion bypasses the compiler's type checking and must be used sparingly.

Permitted cases:

typescript
const canvas = document.getElementById('chart') as HTMLCanvasElement
const target = (event as MouseEvent).target as HTMLInputElement

Prohibited cases:

typescript
const data = fetchData() as UserProfile           // Unfounded assertion
const value = rawData as any as SpecificType       // Double assertion

The non-null assertion operator (!) must not be used. Replace it with optional chaining (?.) or explicit null checks.

typescript
// Incorrect
const name = user!.name
// Correct
const name = user?.name

11.4.3. unknown vs any

Data originating from external sources must be received as the unknown type and narrowed using type guards before use.

typescript
interface ErrorResponse { success: false; message: string }

function isErrorResponse(value: unknown): value is ErrorResponse {
  return (
    typeof value === 'object' &&
    value !== null &&
    'success' in value &&
    (value as Record<string, unknown>).success === false
  )
}

async function handleApiResponse(response: Response): Promise<void> {
  const body: unknown = await response.json()
  if (isErrorResponse(body)) {
    throw new Error(body.message)
  }
}
  • any disables type checking, while unknown enforces type verification before use.
  • Error objects in catch blocks are of type unknown, so instanceof checks must be performed.

11.4.4. Type Narrowing Patterns

Use TypeScript's type narrowing mechanisms as appropriate for the situation.

PatternPurposeExample
typeofPrimitive type discriminationtypeof x === 'string'
instanceofClass instance discriminationerror instanceof TypeError
inProperty existence check'email' in user
Discriminated unionBranching by common discriminant propertyevent.type === 'click'
typescript
// typeof
function formatValue(value: string | number): string {
  if (typeof value === 'string') return value.trim()
  return value.toFixed(2)
}

// Discriminated Union
interface TextNotification { type: 'text'; content: string }
interface ImageNotification { type: 'image'; imageUrl: string }
type Notification = TextNotification | ImageNotification

function renderNotification(n: Notification) {
  switch (n.type) {
    case 'text': return n.content
    case 'image': return n.imageUrl
  }
}
  • Discriminated unions should be used with switch statements to handle all cases.
  • To detect unhandled cases at compile time, use the never type in the default branch.

TIENIPIA QUALIFIED STANDARD