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 StrategiesCore JSType SystemReact FundamentalsFunctionsMicrofrontendsGenericsAsync JSHooksObjectsMonorepoArrays'this' KeywordError HandlingModern JSBundle OptimizationPerformanceDOM & EventsState ManagementClasses & OOPCaching StrategiesComponent PatternsAdvanced TypesAuthenticationReact RouterFormsAdvanced PatternsFrontend SecurityConcurrent ReactServer ComponentsTestingEcosystemNetwork OptimizationCore Web VitalsBrowser APIs

Ready to practice Utility Types?

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

Start Free Practice →