FREE PREVIEW

You're viewing a free preview

This is a sample of 15 questions from our full collection of 38 interview questions.

Unlock all 38 questions with detailed explanations and code examples

Get Full Access

Data Types and Variables

Explain the difference between let, const, and var in TypeScript.

In TypeScript, let, const, and var are keywords used to declare variables, but they have important differences in terms of scope, hoisting, and mutability:

var

  • Function-scoped or globally-scoped (not block-scoped)
  • Hoisted to the top of their containing function or global scope
  • Can be redeclared and reassigned
  • Initialized with undefined when hoisted
function example() {
    console.log(x); // undefined (not an error due to hoisting)
    var x = 5;
    
    if (true) {
        var y = 10;
    }
    console.log(y); // 10 (accessible outside the block)
}

let

  • Block-scoped (limited to the nearest enclosing block)
  • Hoisted but not initialized (Temporal Dead Zone)
  • Can be reassigned but not redeclared in the same scope
  • Must be declared before use
function example() {
    // console.log(x); // ReferenceError: Cannot access 'x' before initialization
    let x = 5;
    
    if (true) {
        let y = 10;
    }
    // console.log(y); // ReferenceError: y is not defined
}

const

  • Block-scoped (same as let)
  • Hoisted but not initialized (Temporal Dead Zone)
  • Cannot be reassigned after declaration
  • Must be initialized at declaration
  • For objects and arrays, the contents can still be modified
const x = 5;
// x = 10; // TypeError: Assignment to constant variable

const obj = { name: "John" };
obj.name = "Jane"; // This is allowed - we're modifying the content, not reassigning
// obj = {}; // TypeError: Assignment to constant variable

Best Practice: Use const by default, let when you need to reassign, and avoid var in modern TypeScript/JavaScript.

References:

↑ Back to top

What are the basic data types in TypeScript?

TypeScript provides several built-in data types that extend JavaScript's type system:

Primitive Types

boolean

let isDone: boolean = false;
let isActive: boolean = true;

number

All numbers in TypeScript are floating-point values or BigIntegers.

let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
let big: bigint = 100n;

string

let color: string = "blue";
let fullName: string = `Bob Bobbington`;
let age: number = 37;
let sentence: string = `Hello, my name is ${fullName}. I'll be ${age + 1} years old next month.`;

null and undefined

let u: undefined = undefined;
let n: null = null;

Special Types

any

Disables type checking - should be avoided when possible.

let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean

unknown

Type-safe counterpart to any. You must perform type checking before using.

let userInput: unknown;
let userName: string;

if (typeof userInput === "string") {
    userName = userInput; // OK
}

void

Typically used as the return type of functions that don't return a value.

function warnUser(): void {
    console.log("This is my warning message");
}

never

Represents values that never occur (functions that throw errors or infinite loops).

function error(message: string): never {
    throw new Error(message);
}

Collection Types

Arrays

let list: number[] = [1, 2, 3];
let list2: Array<number> = [1, 2, 3]; // Generic array type

Tuples

Arrays with fixed number of elements of known types.

let x: [string, number];
x = ["hello", 10]; // OK
// x = [10, "hello"]; // Error

Objects

let user: { name: string; age: number } = {
    name: "John",
    age: 30
};

Advanced Types

Enums

enum Color {
    Red,
    Green,
    Blue,
}
let c: Color = Color.Green;

Union Types

let id: string | number;
id = "abc123"; // OK
id = 123; // OK

References:

↑ Back to top

What is type inference in TypeScript?

Type inference is TypeScript's ability to automatically determine and assign types to variables, function return values, and expressions without explicit type annotations. This feature makes TypeScript code more concise while maintaining type safety.

How Type Inference Works

TypeScript uses the best common type algorithm to infer the most appropriate type based on the available information:

// TypeScript infers 'number' type
let x = 3; // x: number

// TypeScript infers 'string' type  
let message = "Hello World"; // message: string

// TypeScript infers 'boolean' type
let isCompleted = false; // isCompleted: boolean

Types of Type Inference

1. Variable Initialization

let name = "Alice"; // string
let count = 42; // number
let items = [1, 2, 3]; // number[]
let mixed = [1, "hello", true]; // (string | number | boolean)[]

2. Function Return Types

function add(a: number, b: number) {
    return a + b; // Return type inferred as 'number'
}

function greet(name: string) {
    return `Hello, ${name}!`; // Return type inferred as 'string'
}

// Arrow functions
const multiply = (x: number, y: number) => x * y; // Returns number

3. Contextual Typing

TypeScript infers types based on the context where an expression appears:

// Event handler example - TypeScript knows 'event' is MouseEvent
button.addEventListener('click', (event) => {
    console.log(event.clientX); // TypeScript knows event has clientX
});

// Array method callbacks
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(num => num * 2); // num is inferred as number

4. Object Literal Inference

const person = {
    name: "John",
    age: 30,
    isEmployed: true
}; 
// Inferred type: { name: string; age: number; isEmployed: boolean; }

const config = {
    host: "localhost",
    port: 3000,
    ssl: false
};
// Inferred type: { host: string; port: number; ssl: boolean; }

Best Common Type Algorithm

When TypeScript encounters an array or expression with multiple types, it tries to find the best common type:

let mixed = [0, 1, null]; // Inferred as (number | null)[]
let zoo = [new Rhino(), new Elephant(), new Snake()]; // Animal[] if they share a common base class

// Without common base type
let stuff = [1, "hello", true]; // (string | number | boolean)[]

Benefits of Type Inference

1. Reduced Verbosity

// Without inference (verbose)
let name: string = "Alice";
let count: number = 42;
let isActive: boolean = true;

// With inference (concise)
let name = "Alice";
let count = 42;
let isActive = true;

2. Maintained Type Safety

let count = 42; // TypeScript knows this is number
// count = "hello"; // Error: Type 'string' is not assignable to type 'number'

3. Better Refactoring Support

If you change a function's return type, TypeScript automatically updates inferred types throughout your codebase.

When Type Inference Doesn't Work

1. Uninitialized Variables

let data; // Type is 'any' - should provide explicit type
data = "hello"; // Still 'any'

// Better approach
let data: string; // Explicit type annotation
data = "hello";

2. Function Parameters

// Parameters need explicit types
function processUser(name, age) { // Error: Parameters need type annotations
    return `${name} is ${age} years old`;
}

// Correct
function processUser(name: string, age: number) {
    return `${name} is ${age} years old`; // Return type inferred as string
}

3. Complex Object Structures

// May need explicit typing for complex structures
interface User {
    id: number;
    name: string;
    preferences?: {
        theme: 'dark' | 'light';
        notifications: boolean;
    };
}

let user: User = {
    id: 1,
    name: "Alice"
}; // Explicit type ensures structure compliance

Best Practices

  1. Let TypeScript infer when obvious:
const message = "Hello"; // Clear inference
   const count = items.length; // Clear inference
  1. Use explicit types for public APIs:
export function calculateTax(amount: number, rate: number): number {
       return amount * rate;
   }
  1. Provide types for complex data structures:
interface ApiResponse {
       data: User[];
       status: number;
       message: string;
   }
   
   const response: ApiResponse = await fetchUsers();
  1. Use type assertions sparingly:
const userInput = document.getElementById('user-input') as HTMLInputElement;

Advanced Inference Examples

Generic Function Inference

function identity<T>(arg: T): T {
    return arg;
}

let output = identity("hello"); // TypeScript infers T as string
let number = identity(42); // TypeScript infers T as number

Conditional Type Inference

type ApiResponse<T> = T extends string ? { message: T } : { data: T };

const stringResponse = getApiResponse("error"); // { message: string }
const dataResponse = getApiResponse({ users: [] }); // { data: { users: any[] } }

References:

I'll help you understand TypeScript functions by answering each of your questions with detailed explanations and examples.

↑ Back to top

What are generics in TypeScript and how do you use them in functions?

Generics allow you to create reusable functions that work with multiple types while maintaining type safety. They use type parameters (usually denoted with <T>).

1. Basic Generic Function

function identity<T>(arg: T): T {
    return arg;
}

// Usage - TypeScript infers the type
let stringResult = identity("hello");    // string
let numberResult = identity(42);         // number

// Or explicitly specify the type
let boolResult = identity<boolean>(true); // boolean

2. Generic Function with Multiple Type Parameters

function pair<T, U>(first: T, second: U): [T, U] {
    return [first, second];
}

const result = pair("hello", 42); // [string, number]

3. Generic Constraints

You can constrain generics to specific types using extends:

interface Lengthwise {
    length: number;
}

function logLength<T extends Lengthwise>(arg: T): T {
    console.log(arg.length); // Now we know arg has a length property
    return arg;
}

logLength("hello");        // Works - string has length
logLength([1, 2, 3]);      // Works - array has length
// logLength(42);          // Error - number doesn't have length

4. Generic Array Functions

function getFirstElement<T>(array: T[]): T | undefined {
    return array.length > 0 ? array[0] : undefined;
}

const firstNumber = getFirstElement([1, 2, 3]);     // number | undefined
const firstString = getFirstElement(["a", "b"]);    // string | undefined

5. Generic with Default Type Parameters

function createArray<T = string>(length: number, value: T): T[] {
    return Array(length).fill(value);
}

const stringArray = createArray(3, "hello");        // string[]
const numberArray = createArray<number>(3, 42);     // number[]

6. Generic Function Types

type GenericFunction<T> = (arg: T) => T;

const stringProcessor: GenericFunction<string> = (str) => str.toUpperCase();
const numberProcessor: GenericFunction<number> = (num) => num * 2;

Practical Example: Array Utility Functions

function filterArray<T>(
    array: T[], 
    predicate: (item: T) => boolean
): T[] {
    return array.filter(predicate);
}

function mapArray<T, U>(
    array: T[], 
    transform: (item: T) => U
): U[] {
    return array.map(transform);
}

// Usage
const numbers = [1, 2, 3, 4, 5];
const evenNumbers = filterArray(numbers, n => n % 2 === 0); // number[]
const strings = mapArray(numbers, n => n.toString());        // string[]

Generics make your functions more flexible and reusable while preserving type safety, which is one of the key benefits of using TypeScript over plain JavaScript.

I'll provide comprehensive answers to these TypeScript interview questions about interfaces and classes.

↑ Back to top

Interfaces and Classes in TypeScript

What are access modifiers (public, private, protected) in TypeScript?

Access modifiers control the visibility and accessibility of class members:

public (default)

  • Accessible from anywhere
  • Default modifier if none is specified

private

  • Only accessible within the same class
  • Cannot be accessed from outside the class or subclasses

protected

  • Accessible within the class and its subclasses
  • Cannot be accessed from outside the class hierarchy
class Animal {
    public name: string;        // Accessible everywhere
    private age: number;        // Only within Animal class
    protected species: string;  // Within Animal and subclasses

    constructor(name: string, age: number, species: string) {
        this.name = name;
        this.age = age;
        this.species = species;
    }

    public getInfo(): string {
        return `${this.name} is ${this.age} years old`;
    }

    private calculateLifeExpectancy(): number {
        return 100 - this.age; // Only accessible within this class
    }

    protected makeSound(): void {
        console.log("Some generic animal sound");
    }
}

class Dog extends Animal {
    constructor(name: string, age: number) {
        super(name, age, "Canine");
    }

    public bark(): void {
        // Can access protected members from parent class
        console.log(`${this.name} of species ${this.species} is barking!`);
        this.makeSound(); // ✅ Allowed - protected method
        
        // this.age = 5; // ❌ Error - private member not accessible
    }
}

const dog = new Dog("Buddy", 3);
console.log(dog.name);        // ✅ Allowed - public
// console.log(dog.age);      // ❌ Error - private
// console.log(dog.species);  // ❌ Error - protected
dog.bark();                   // ✅ Allowed - public method

Readonly Modifier

TypeScript also supports the readonly modifier, which makes properties immutable after initialization:

class Configuration {
    readonly apiUrl: string;
    readonly version: number;

    constructor(apiUrl: string, version: number) {
        this.apiUrl = apiUrl;
        this.version = version;
    }

    // updateApiUrl(newUrl: string) {
    //     this.apiUrl = newUrl; // ❌ Error - readonly property
    // }
}

Parameter Properties

TypeScript allows you to declare and initialize properties directly in the constructor parameters:

class User {
    // Shorthand property declaration
    constructor(
        public id: number,
        private email: string,
        protected createdAt: Date = new Date()
    ) {
        // Properties are automatically created and initialized
    }

    public getEmail(): string {
        return this.email;
    }
}

// Equivalent to the longer form:
// class User {
//     public id: number;
//     private email: string;
//     protected createdAt: Date;
//     
//     constructor(id: number, email: string, createdAt: Date = new Date()) {
//         this.id = id;
//         this.email = email;
//         this.createdAt = createdAt;
//     }
// }

These concepts form the foundation of object-oriented programming in TypeScript, providing type safety, encapsulation, and clear contracts for your code.

I'll help you understand these advanced TypeScript types with detailed explanations and examples.

↑ Back to top

Advanced Types in TypeScript

What are union types and how do you use them?

Union types allow a variable to be one of several types, using the | operator. They represent a value that can be one of several types.

// Basic union type
type StringOrNumber = string | number;

let value: StringOrNumber = "hello"; // Valid
value = 42; // Also valid

// Function with union parameter
function formatId(id: string | number): string {
  if (typeof id === "string") {
    return id.toUpperCase();
  }
  return id.toString();
}

// Union with literal types
type Status = "loading" | "success" | "error";
let currentStatus: Status = "loading";

// Array with union types
type MixedArray = (string | number | boolean)[];
const items: MixedArray = ["hello", 42, true, "world"];
↑ Back to top

What are intersection types and how do you use them?

Intersection types combine multiple types into one using the & operator. The resulting type has all properties of the combined types.

// Basic intersection
interface Person {
  name: string;
  age: number;
}

interface Employee {
  employeeId: string;
  department: string;
}

type PersonEmployee = Person & Employee;

const worker: PersonEmployee = {
  name: "John",
  age: 30,
  employeeId: "EMP001",
  department: "Engineering"
};

// Intersection with type aliases
type Timestamped = {
  timestamp: Date;
};

type Tagged = {
  tag: string;
};

type TimestampedAndTagged = Timestamped & Tagged;

const item: TimestampedAndTagged = {
  timestamp: new Date(),
  tag: "important"
};

// Function intersection
type Logger = {
  log: (message: string) => void;
};

type Counter = {
  count: number;
  increment: () => void;
};

type LoggerCounter = Logger & Counter;
↑ Back to top

What are type aliases and how do you use them?

Type aliases create new names for existing types using the type keyword. They help make complex types more readable and reusable.

// Basic type alias
type UserID = string;
type Score = number;

// Object type alias
type User = {
  id: UserID;
  name: string;
  email: string;
  isActive: boolean;
};

// Function type alias
type EventHandler = (event: string, data: any) => void;
type Predicate<T> = (item: T) => boolean;

// Generic type alias
type ApiResponse<T> = {
  data: T;
  status: number;
  message: string;
};

type UserResponse = ApiResponse<User>;

// Complex type alias
type DatabaseConfig = {
  host: string;
  port: number;
  database: string;
  credentials: {
    username: string;
    password: string;
  };
};

// Union type alias
type Theme = "light" | "dark" | "auto";
type SizeOptions = "small" | "medium" | "large";
↑ Back to top

What are conditional types and how do you use them?

Conditional types select types based on conditions, similar to ternary operators but for types. They use the syntax T extends U ? X : Y.

// Basic conditional type
type IsString<T> = T extends string ? true : false;

type Test1 = IsString<string>; // true
type Test2 = IsString<number>; // false

// More practical example
type NonNullable<T> = T extends null | undefined ? never : T;

type SafeString = NonNullable<string | null>; // string
type SafeNumber = NonNullable<number | undefined>; // number

// Conditional type with infer
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

function getString(): string { return "hello"; }
function getNumber(): number { return 42; }

type StringReturn = ReturnType<typeof getString>; // string
type NumberReturn = ReturnType<typeof getNumber>; // number

// Distributive conditional types
type ToArray<T> = T extends any ? T[] : never;
type StringOrNumberArray = ToArray<string | number>; // string[] | number[]

// Complex conditional type
type Flatten<T> = T extends (infer U)[] ? U : T;
type FlatString = Flatten<string[]>; // string
type FlatNumber = Flatten<number>; // number

// Mapped conditional type
type Nullable<T> = {
  [K in keyof T]: T[K] | null;
};

type NullableUser = Nullable<{ name: string; age: number }>;
// Result: { name: string | null; age: number | null; }
↑ Back to top

Explain the concept of the "never" type in TypeScript

The never type represents values that never occur. It's the type of values that never return or always throw an exception.

// Functions that never return
function throwError(message: string): never {
  throw new Error(message);
}

function infiniteLoop(): never {
  while (true) {
    // This function never returns
  }
}

// Exhaustive checking with never
type Shape = "circle" | "square" | "triangle";

function getArea(shape: Shape): number {
  switch (shape) {
    case "circle":
      return Math.PI * 2 * 2;
    case "square":
      return 4 * 4;
    case "triangle":
      return 0.5 * 3 * 4;
    default:
      // This ensures all cases are handled
      const exhaustiveCheck: never = shape;
      throw new Error(`Unhandled shape: ${exhaustiveCheck}`);
  }
}

// Never in conditional types
type NonNullable<T> = T extends null | undefined ? never : T;

// Never as unreachable code
function processValue(value: string | number) {
  if (typeof value === "string") {
    return value.toUpperCase();
  } else if (typeof value === "number") {
    return value * 2;
  }
  
  // TypeScript knows this is never reached
  const unreachable: never = value;
  return unreachable;
}

// Never with union types
type Result = string | never; // Equivalent to just string

// Filter out types using never
type NonFunction<T> = T extends Function ? never : T;
type Filtered = NonFunction<string | number | (() => void)>; // string | number

// Never in mapped types
type RequiredKeys<T> = {
  [K in keyof T]-?: {} extends Pick<T, K> ? never : K;
}[keyof T];

interface Example {
  required: string;
  optional?: number;
}

type Required = RequiredKeys<Example>; // "required"
↑ Back to top

Modules and Namespaces

What are modules in TypeScript?

Modules in TypeScript are a way to organize and encapsulate code into separate files, where each file represents a module. They provide a mechanism for creating reusable, maintainable, and well-structured code by allowing you to export functionality from one module and import it into another.

Key characteristics of TypeScript modules:

  • File-based: Each file with top-level import or export statements is considered a module
  • Scoped: Variables, functions, and classes declared in a module are not visible outside unless explicitly exported
  • ES6 Module System: TypeScript follows the ECMAScript 2015 (ES6) module specification
  • Compile-time checking: TypeScript provides type checking across module boundaries

Here's a simple example of a TypeScript module:

// math.ts - This module exports mathematical utility functions
export function add(a: number, b: number): number {
    return a + b;
}

export function multiply(a: number, b: number): number {
    return a * b;
}

export const PI = 3.14159;

This code demonstrates a basic module that exports two functions and a constant. The export keyword makes these items available for import in other modules.

↑ Back to top

Decorators

What are decorators in TypeScript?

Decorators in TypeScript are a special kind of declaration that can be attached to classes, methods, properties, accessors, or parameters. They are essentially functions that provide a way to add metadata, modify behavior, or extend functionality of the decorated element at design time. Decorators use the @expression syntax and are executed at runtime when the class is defined.

Decorators are based on the decorator pattern and provide a clean, declarative way to modify or extend the behavior of classes and their members without directly modifying the original code. They are widely used in frameworks like Angular, NestJS, and TypeORM for dependency injection, routing, validation, and ORM mapping.

Feature Description
Syntax Uses @decoratorName prefix
Execution Time Runtime during class definition
Target Elements Classes, methods, properties, accessors, parameters
Return Value Can return modified constructor/descriptor or void
Composition Multiple decorators can be applied (executed bottom-up)
↑ Back to top

Asynchronous Programming

How do you handle asynchronous operations in TypeScript?

TypeScript provides several mechanisms for handling asynchronous operations, building upon JavaScript's async capabilities with additional type safety. The primary approaches include:

1. Promises with Type Annotations

TypeScript allows you to specify the type that a Promise resolves to, providing compile-time type checking and better IDE support.

// Promise with type annotation
function fetchUserData(id: number): Promise<User> {
  return fetch(`/api/users/${id}`)
    .then(response => response.json())
    .then(data => data as User);
}

interface User {
  id: number;
  name: string;
  email: string;
}

This code demonstrates how TypeScript enhances Promise handling by specifying that fetchUserData returns a Promise<User>, ensuring type safety throughout the promise chain.

2. Async/Await with Type Safety

The async/await syntax provides a more readable approach to handling asynchronous operations while maintaining full type checking.

async function processUserData(userId: number): Promise<ProcessedUser> {
  try {
    const user: User = await fetchUserData(userId);
    const preferences: UserPreferences = await fetchUserPreferences(userId);
    
    return {
      ...user,
      preferences,
      processedAt: new Date()
    };
  } catch (error) {
    throw new Error(`Failed to process user data: ${error.message}`);
  }
}

This example shows how async/await simplifies error handling and maintains type safety across multiple asynchronous operations.

3. Generic Promises and Utility Types

TypeScript's generic system allows for flexible, reusable asynchronous code patterns.

// Generic API wrapper
class ApiClient {
  async get<T>(endpoint: string): Promise<T> {
    const response = await fetch(endpoint);
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }
    return response.json() as T;
  }
}

// Usage with type inference
const apiClient = new ApiClient();
const users: User[] = await apiClient.get<User[]>('/api/users');

Comparison with JavaScript

Aspect JavaScript TypeScript
Type Safety Runtime errors only Compile-time type checking
IDE Support Basic autocomplete Rich IntelliSense and refactoring
Error Prevention Runtime discovery Compile-time error detection
Documentation Comments only Self-documenting through types

References:

↑ Back to top

Type Guards and Type Assertions

What are type guards in TypeScript and when would you use them?

Type guards are a TypeScript feature that allows you to narrow down the type of a variable within a conditional block. They are functions or expressions that perform runtime checks to determine the type of a variable, enabling TypeScript's compiler to understand the specific type within that code block.

When to use type guards:

  • Working with union types where you need to handle different types differently
  • Validating data from external sources (APIs, user input)
  • Ensuring type safety when dealing with polymorphic code
  • Avoiding runtime errors by checking types before performing type-specific operations

Types of type guards:

Type Guard Description Example
typeof Checks primitive types typeof x === 'string'
instanceof Checks if object is instance of class obj instanceof Date
in operator Checks if property exists in object 'length' in obj
Custom type guards User-defined functions with type predicates function isString(x): x is string

Here's an example demonstrating different type guard techniques:

// Union type example
type Animal = Dog | Cat;

interface Dog {
  breed: string;
  bark(): void;
}

interface Cat {
  color: string;
  meow(): void;
}

// Custom type guard function
function isDog(animal: Animal): animal is Dog {
  return 'breed' in animal && 'bark' in animal;
}

// Using type guards
function handleAnimal(animal: Animal) {
  if (isDog(animal)) {
    // TypeScript knows animal is Dog here
    console.log(`Dog breed: ${animal.breed}`);
    animal.bark();
  } else {
    // TypeScript knows animal is Cat here
    console.log(`Cat color: ${animal.color}`);
    animal.meow();
  }
}

// Primitive type guard
function processValue(value: string | number) {
  if (typeof value === 'string') {
    // TypeScript knows value is string
    return value.toUpperCase();
  } else {
    // TypeScript knows value is number
    return value.toFixed(2);
  }
}

This code demonstrates how type guards help TypeScript understand the specific type within conditional blocks, providing better IntelliSense and preventing type-related errors.

↑ Back to top

What are type assertions in TypeScript and when should you use them?

Type assertions are a way to tell the TypeScript compiler that you know more about the type of a value than it does. They don't perform any runtime checking or data conversion - they're purely a compile-time construct that instructs the compiler to treat a value as a specific type.

When to use type assertions:

  • When you're certain about a type but TypeScript cannot infer it
  • Working with DOM elements where you know the specific element type
  • Migrating JavaScript code to TypeScript gradually
  • Dealing with third-party libraries with incomplete type definitions
  • When accessing properties that TypeScript doesn't recognize

Syntax options:

// Angle bracket syntax (not recommended in JSX)
let value1 = <string>someValue;

// 'as' syntax (preferred, works in all contexts)
let value2 = someValue as string;

Here's a practical example showing appropriate use cases:

// DOM manipulation example
const inputElement = document.getElementById('username') as HTMLInputElement;
// Without assertion, TypeScript would infer HTMLElement | null
// With assertion, we can access input-specific properties
inputElement.value = 'John Doe';
inputElement.focus();

// API response handling
interface User {
  id: number;
  name: string;
  email: string;
}

async function fetchUser(): Promise<User> {
  const response = await fetch('/api/user');
  const data = await response.json();
  
  // We know the API returns a User object
  return data as User;
}

// Working with unknown types
function processUnknownData(data: unknown) {
  // First, check if it's an object
  if (typeof data === 'object' && data !== null) {
    // Assert it has the structure we expect
    const userLike = data as { name?: string; age?: number };
    
    if (userLike.name) {
      console.log(`Name: ${userLike.name}`);
    }
  }
}

// Non-null assertion (special case)
function getUsername(): string {
  const user = getCurrentUser(); // returns User | undefined
  // We're certain user exists at this point
  return user!.name; // ! is equivalent to 'as User'
}

This code shows how type assertions help when you have knowledge about types that TypeScript cannot automatically infer, particularly when working with external data or DOM elements.

Important considerations:

  • Use type assertions sparingly and only when necessary
  • Prefer type guards when possible for runtime safety
  • Be careful with type assertions as they can lead to runtime errors if incorrect
↑ Back to top

Want more questions?

You've seen 15 sample questions. Unlock all 38 En interview questions with detailed explanations, code examples, and expert insights.

38+ questions
Code examples
Expert explanations
Instant access
Unlock Full Access