Typescript · Utility Types

Utility Types Interview Questions
With Answers & Code Examples

7 carefully curated Utility Types interview questions with working code examples and real interview gotchas.

Practice Interactively →← All Categories
7 questions2 beginner3 core2 advanced
Q1Beginner

What are Partial, Required, and Readonly utility types?

💡 Hint: Partial makes all fields optional; Required makes all mandatory; Readonly prevents mutation

These three utility types transform the optionality and mutability of all properties in a type.

Partial<T> — makes every property optional (?):

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

type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string }

// Common use: update/patch functions
function updateUser(id: number, changes: Partial<User>): User {
  return { ...findUser(id), ...changes };
}

Required<T> — makes every property required (removes ?):

interface Config {
  host?: string;
  port?: number;
  timeout?: number;
}

type FinalizedConfig = Required<Config>;
// { host: string; port: number; timeout: number }

// After merging defaults — all fields are now guaranteed
function buildConfig(opts: Config): Required<Config> {
  return { host: 'localhost', port: 3000, timeout: 5000, ...opts };
}

Readonly<T> — makes every property readonly:

const config: Readonly<Config> = { host: 'localhost', port: 3000 };
config.host = 'other'; // ❌ Error — readonly

// Deep readonly (not built-in, must define)
type DeepReadonly<T> = { readonly [K in keyof T]: DeepReadonly<T[K]> };
💡 Readonly is only a compile-time protection — Object.freeze() is needed for runtime. Also note: ReadonlyArray<T> is the equivalent for arrays.
Practice this question →
Q2Beginner

What are Pick and Omit utility types and when should you use them?

💡 Hint: Pick selects a subset of keys; Omit excludes specific keys — both create derived types from existing ones

Pick<T, K> — creates a type with only the specified keys from T:

interface User {
  id: number;
  name: string;
  email: string;
  password: string;
  createdAt: Date;
}

// Only expose safe fields to the client
type PublicUser = Pick<User, 'id' | 'name' | 'email'>;
// { id: number; name: string; email: string }

// Safe to serialize and send over the wire
function getUserPublic(id: number): PublicUser {
  const user = findUser(id);
  return { id: user.id, name: user.name, email: user.email };
}

Omit<T, K> — creates a type with the specified keys removed:

// Remove sensitive fields
type SafeUser = Omit<User, 'password'>;

// Remove auto-managed fields for create inputs
type CreateUserInput = Omit<User, 'id' | 'createdAt'>;
// { name: string; email: string; password: string }

function createUser(input: CreateUserInput): User {
  return { ...input, id: generateId(), createdAt: new Date() };
}

Pick vs Omit — when to choose:

  • Use Pick when you want a small subset from a large type — clearly states which fields you want
  • Use Omit when you want most fields minus a few — less fragile when new fields are added (they're included automatically)
💡 Prefer Omit when the excluded set is small and stable. If you add a field to User, Omit<User, 'password'> automatically includes it, while Pick<User, 'id' | 'name'> would not.
Practice this question →
Q3Core

What is the Record utility type and when is it useful?

💡 Hint: Record<K, V> creates an object type with keys K and values V — great for lookup maps and indexed objects

Record<K, V> constructs an object type with keys of type K and values of type V.

// Simple lookup map
type CountryCode = 'US' | 'UK' | 'DE';
const currencies: Record<CountryCode, string> = {
  US: 'USD',
  UK: 'GBP',
  DE: 'EUR',
}; // TypeScript ensures all keys are present

// Dynamic key type
type IdMap<T> = Record<string, T>;
const userMap: IdMap<User> = {};
userMap['abc123'] = { id: 1, name: 'Alice' };

Record vs index signature:

// Index signature — key type can be broad
interface Dict {
  [key: string]: string;
}

// Record — key type can be a union literal (exhaustive)
type StatusMap = Record<'active' | 'inactive' | 'pending', number>;
// TypeScript enforces all three keys must be present

const counts: StatusMap = {
  active: 12,
  inactive: 3,
  pending: 1,
  // missing 'pending' would be an error
};

Common patterns:

// Route config map
type Route = '/home' | '/about' | '/users';
const routeTitles: Record<Route, string> = {
  '/home': 'Home',
  '/about': 'About Us',
  '/users': 'Users',
};

// Grouping array items
function groupBy<T, K extends string>(
  items: T[],
  getKey: (item: T) => K
): Partial<Record<K, T[]>> {
  return items.reduce((acc, item) => {
    const key = getKey(item);
    (acc[key] ??= []).push(item);
    return acc;
  }, {} as Partial<Record<K, T[]>>);
}
💡 Use Record<'a' | 'b', T> instead of a plain object type when you want TypeScript to enforce that all union members are handled — like an exhaustive switch but for object keys.
Practice this question →
Q4Core

What are ReturnType, Parameters, and InstanceType utility types?

💡 Hint: Infer the output type of a function, the input tuple, or the instance type of a class constructor

These utility types extract type information from function and class signatures.

ReturnType<T> — extracts the return type of a function type:

function fetchUser(id: number): Promise<User> { /* ... */ }

type FetchResult = ReturnType<typeof fetchUser>; // Promise<User>
type User2 = Awaited<ReturnType<typeof fetchUser>>; // User (unwrapped)

// Useful when the return type is complex and you don't want to duplicate it
const config = { theme: 'dark', lang: 'en', debug: false };
type Config = ReturnType<typeof createConfig>; // mirrors the function output

Parameters<T> — extracts the parameter types as a tuple:

function createUser(name: string, age: number, admin?: boolean): User { /* ... */ }

type CreateUserParams = Parameters<typeof createUser>;
// [name: string, age: number, admin?: boolean]

// Useful for wrapping or forwarding function calls
function withLogging<T extends (...args: any[]) => any>(
  fn: T,
  ...args: Parameters<T>
): ReturnType<T> {
  console.log('Calling with', args);
  return fn(...args);
}

InstanceType<T> — extracts the instance type from a constructor:

class Connection {
  host: string;
  connect(): void { /* ... */ }
}

type ConnectionInstance = InstanceType<typeof Connection>; // Connection

// Useful in factory patterns where you work with constructors
function create<T extends new (...args: any[]) => any>(
  Ctor: T, ...args: ConstructorParameters<T>
): InstanceType<T> {
  return new Ctor(...args);
}
💡 These three types work with typeof to extract shapes from existing values — no need to manually define return types or parameter interfaces for functions you already have.
Practice this question →
Q5Core

What are Exclude and Extract utility types?

💡 Hint: Exclude removes members from a union; Extract keeps only the matching members

Both work on union types to filter members based on a condition.

Exclude<T, U> — removes from T all types assignable to U:

type T = string | number | boolean | null | undefined;

type NonNullable<T> = Exclude<T, null | undefined>;
type Primitives = Exclude<T, null | undefined>;
// string | number | boolean

type StringOrNumber = Exclude<string | number | boolean, boolean>;
// string | number

Extract<T, U> — keeps from T only types assignable to U (opposite of Exclude):

type Strings = Extract<string | number | boolean, string | boolean>;
// string | boolean

// Practical use: filter union to only the callable types
type FnOnly = Extract<string | number | (() => void), Function>;
// () => void

Combined with discriminated unions:

type Action =
  | { type: 'ADD'; payload: string }
  | { type: 'REMOVE'; id: number }
  | { type: 'CLEAR' };

// Extract only actions with a payload
type ActionWithPayload = Extract<Action, { payload: any }>;
// { type: 'ADD'; payload: string }

// Exclude the CLEAR action
type MutatingActions = Exclude<Action, { type: 'CLEAR' }>;
// { type: 'ADD'; payload: string } | { type: 'REMOVE'; id: number }
💡 NonNullable<T> is just Exclude<T, null | undefined> built into TypeScript. Understanding Exclude lets you build your own specialized filters.
Practice this question →
Q6Advanced

What are Awaited and NonNullable utility types?

💡 Hint: Awaited recursively unwraps Promise<T>; NonNullable removes null and undefined from a type

Awaited<T> — recursively unwraps the resolved type of a Promise (or thenable). Added in TypeScript 4.5.

type A = Awaited<Promise<string>>;
// string

type B = Awaited<Promise<Promise<number>>>;
// number (recursively unwrapped)

// Common pattern: get the type a fetch function resolves to
async function getUser(): Promise<User> { /* ... */ }

type ResolvedUser = Awaited<ReturnType<typeof getUser>>;
// User (not Promise<User>)

// Useful for typing the result of Promise.all
type MultiResult = Awaited<Promise<[User, Post[]]>>;
// [User, Post[]]

NonNullable<T> — removes null and undefined from a type:

type MaybeUser = User | null | undefined;

type DefiniteUser = NonNullable<MaybeUser>; // User

// Useful after null checks in generics
function assertDefined<T>(val: T): NonNullable<T> {
  if (val === null || val === undefined) {
    throw new Error('Value is null or undefined');
  }
  return val as NonNullable<T>;
}

// Filter nulls from an array
function compact<T>(arr: (T | null | undefined)[]): NonNullable<T>[] {
  return arr.filter((x): x is NonNullable<T> => x != null);
}
💡 Before TypeScript 4.5, getting the resolved type of a Promise required manual conditional types. Awaited makes async code types much cleaner, especially with Promise.all and async utility functions.
Practice this question →
Q7Advanced

What is the Mapped Type pattern in utility types — how are Partial, Readonly, and Record implemented?

💡 Hint: for...in at the type level — iterate over keyof T and transform each property

Mapped types iterate over the keys of a type and apply a transformation to each property. They power all the modifier utility types.

// Partial implementation — adds ? to every property
type MyPartial<T> = {
  [K in keyof T]?: T[K];
  // for each key K in T, make it optional
};

// Required implementation — removes ? from every property
type MyRequired<T> = {
  [K in keyof T]-?: T[K];
  // -? removes optionality
};

// Readonly implementation — adds readonly to every property
type MyReadonly<T> = {
  readonly [K in keyof T]: T[K];
};

// Mutable (remove readonly)
type Mutable<T> = {
  -readonly [K in keyof T]: T[K];
};

// Record implementation
type MyRecord<K extends string | number | symbol, V> = {
  [P in K]: V;
};

Remapping keys with as:

// Add getter prefix to all keys
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

type UserGetters = Getters<{ name: string; age: number }>;
// { getName: () => string; getAge: () => number }

// Filter keys by type
type OnlyStrings<T> = {
  [K in keyof T as T[K] extends string ? K : never]: T[K];
};
💡 The modifiers +? (add optional), -? (remove optional), +readonly, -readonly give you surgical control. -? is what Required uses to make every field mandatory.
Practice this question →

Other Typescript Interview Topics

Rendering Strategies →Core JS →Type System →React Fundamentals →Functions →Microfrontends →Generics →Async JS →Hooks →Objects →Monorepo →Arrays →'this' Keyword →Error Handling →Modern JS →Bundle Optimization →Performance →DOM & Events →State Management →Classes & OOP →Caching Strategies →Component Patterns →Advanced Types →Authentication →React Router →Forms →Advanced Patterns →Frontend Security →Concurrent React →Server Components →Testing →Ecosystem →Network Optimization →Core Web Vitals →Browser APIs →

Ready to practice Utility Types?

Get AI feedback on your answers, predict code output, and fix real bugs.

Start Free Practice →