TypeScript Interview Questions (Free Preview)
Free sample of 15 from 38 questions available
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
undefinedwhen 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 topWhat 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 topWhat 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
- Let TypeScript infer when obvious:
const message = "Hello"; // Clear inference
const count = items.length; // Clear inference
- Use explicit types for public APIs:
export function calculateTax(amount: number, rate: number): number {
return amount * rate;
}
- Provide types for complex data structures:
interface ApiResponse {
data: User[];
status: number;
message: string;
}
const response: ApiResponse = await fetchUsers();
- 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:
- TypeScript Handbook - Type Inference
- TypeScript Deep Dive - Type Inference
- Microsoft TypeScript Blog - Better Type Inference
I'll help you understand TypeScript functions by answering each of your questions with detailed explanations and examples.
↑ Back to topWhat 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 topInterfaces 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 topAdvanced 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
importorexportstatements 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.
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) |
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 topType 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 topWhat 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