TypeScript interview question
Which TypeScript utility types do you use most, and how do they help in test code?
Short answer
Partial, Required, Pick, Omit, Record, Readonly, ReturnType and Awaited derive new types from existing ones, so test data builders, API payloads and config stay in sync with the source types. For example Omit<User, 'id'> for a create payload, or Partial<User> for builder overrides.
Answer
Utility types are built-in generic types that transform one type into another. Their value in test code is that test data, payloads and helpers derive from the real domain types, so when a developer adds or renames a field, the compiler points at every test that needs updating instead of the tests silently drifting.
The ones I use most
interface User {
id: string;
email: string;
name: string;
role: 'admin' | 'member';
createdAt: string;
}
Partial / Required: make all properties optional or all required.
function buildUser(overrides: Partial<User> = {}): User {
return {
id: crypto.randomUUID(),
email: `qa+${Date.now()}@example.com`,
name: 'Test User',
role: 'member',
createdAt: new Date().toISOString(),
...overrides,
};
}
const admin = buildUser({ role: 'admin' });
Omit / Pick: remove or select properties. A create payload has no server-generated fields:
type CreateUserPayload = Omit<User, 'id' | 'createdAt'>;
type UserSummary = Pick<User, 'id' | 'email'>;
Record: an object type with known keys and a value type. Combined with a union it guarantees every key is present:
type Env = 'dev' | 'staging' | 'prod';
const baseUrls: Record<Env, string> = {
dev: 'http://localhost:3000',
staging: 'https://staging.example.com',
prod: 'https://example.com',
}; // forgetting 'prod' is a compile error
Readonly: prevent tests from mutating shared fixtures (shallow).
const DEFAULT_USER: Readonly<User> = buildUser();
// DEFAULT_USER.role = 'admin'; // error
ReturnType, Parameters, Awaited: derive types from functions you do not control or do not want to duplicate.
async function login(email: string, password: string) {
return { token: 'abc', expiresIn: 3600 };
}
type LoginArgs = Parameters<typeof login>; // [email: string, password: string]
type Session = Awaited<ReturnType<typeof login>>; // { token: string; expiresIn: number }
Also worth knowing
NonNullable<T>removesnullandundefined.Extract<T, U>andExclude<T, U>filter union members:Exclude<User['role'], 'admin'>is'member'.satisfies(TypeScript 4.9+) is not a utility type but pairs well with them: it checks a value against a type while keeping the narrower inferred type.
const timeouts = { short: 2_000, long: 30_000 } satisfies Record<string, number>;
timeouts.short; // type number, and the key 'short' is known
What interviewers look for
- Several utility types with correct semantics, not just names.
- Realistic test-code uses: builders with
Partial, payloads withOmit, config withRecord. - The reason they matter: types derived from a single source of truth.
- Awareness that
ReadonlyandPartialare shallow.
A strong answer covers
- Partial and Required change optionality
- Pick and Omit select or remove properties
- Record for maps keyed by a union
- ReturnType, Parameters, Awaited for deriving from functions
- Explains the benefit: derived types stay in sync with the source