QA Interview Hub · Knowledge base · Quizzes
TypeScript
Utility types
Partial, Required, Readonly, Pick, Omit
Utility types are built-in generic types that transform other types. They let you avoid duplicating models.
| Utility | What it does |
|---|
Partial<T> | all fields become optional |
Required<T> | all fields become required |
Readonly<T> | all fields readonly (shallowly) |
Pick<T, K> | keeps only the listed keys |
Omit<T, K> | removes the listed keys |
A typical use in automation is a test data factory. The base object is valid, and the test overrides only what matters for the scenario:
interface TransferRequest {
fromAccount: string;
toAccount: string;
amount: number;
currency: 'UAH' | 'USD';
description: string;
}
function buildTransfer(overrides: Partial<TransferRequest> = {}): TransferRequest {
return {
fromAccount: 'UA213223130000026007233566001',
toAccount: 'UA903052992990004149123456789',
amount: 10_000,
currency: 'UAH',
description: 'autotest',
...overrides,
};
}
const negative = buildTransfer({ amount: -1 });
Omit is handy for a creation body: the server generates id and createdAt, so the request is the model without them:
interface Account { id: string; iban: string; currency: string; createdAt: string }
type CreateAccountBody = Omit<Account, 'id' | 'createdAt'>;
type AccountSummary = Pick<Account, 'id' | 'iban'>;
A nuance: Omit doesn't check that the key exists. Omit<Account, 'idd'> compiles without errors. Pick does check keys.
In an interview: I use Partial in test data factories, so a test overrides only the needed fields, and Omit and Pick to derive a request type from a model without duplication. That way, when the model changes, all derived types update automatically.
Record, Exclude, Extract, NonNullable
Record<K, V> is an object with keys K and values V. If K is a union of literals, TS requires all the keys. This is convenient for lookup tables that must be complete:
type Currency = 'UAH' | 'USD' | 'EUR';
const minorUnits: Record<Currency, number> = {
UAH: 2,
USD: 2,
EUR: 2,
};
// add 'JPY' to Currency -> an error until you add the key
Exclude<T, U> removes from a union the variants that match U. Extract<T, U> does the opposite, keeping only them. NonNullable<T> removes null and undefined.
type TxStatus = 'PENDING' | 'COMPLETED' | 'FAILED' | 'REVERSED';
type FinalStatus = Exclude<TxStatus, 'PENDING'>;
// 'COMPLETED' | 'FAILED' | 'REVERSED'
type Event =
| { type: 'deposit'; amount: number }
| { type: 'withdrawal'; amount: number }
| { type: 'blocked'; reason: string };
type MoneyEvent = Extract<Event, { amount: number }>;
type MaybeId = string | null | undefined;
type Id = NonNullable<MaybeId>; // string
A practical example: a helper that waits for a transaction's final status accepts only FinalStatus. Passing 'PENDING' as the expected final state won't work, and a meaningless test won't compile.
A trap with Record<string, V>: access map['anyKey'] has the type V, though the key may be absent. Here noUncheckedIndexedAccess or the type Partial<Record<string, V>> helps.
In an interview: Record over a union of keys guarantees that a map covers all the variants, for example all currencies. Exclude and Extract filter a union, and that is how I pick out, for example, only the final transaction statuses for a wait helper.
ReturnType, Parameters, Awaited
These utilities derive types from functions. They are useful when a model isn't described explicitly or when you don't want to duplicate it.
ReturnType<typeof fn> is the type a function returns.Parameters<typeof fn> is a tuple of parameter types.Awaited<T> unwraps a Promise: Awaited<Promise<Account>> gives Account. It also works with nested promises.
async function createTestUser(role: 'admin' | 'viewer', balance = 0) {
return { id: crypto.randomUUID(), role, balance, token: 'jwt...' };
}
type TestUser = Awaited<ReturnType<typeof createTestUser>>;
// { id: string; role: 'admin' | 'viewer'; balance: number; token: string }
type CreateArgs = Parameters<typeof createTestUser>;
// [role: 'admin' | 'viewer', balance?: number]
Where I use this:
- The result type of a factory or a fixture without a separate interface.
- A wrapper over a function that keeps its signature, for example step logging:
function withLog<F extends (...args: any[]) => Promise<unknown>>(name: string, fn: F) {
return async (...args: Parameters<F>): Promise<Awaited<ReturnType<F>>> => {
console.log(`[step] ${name}`, args);
return (await fn(...args)) as Awaited<ReturnType<F>>;
};
}
The any[] in the constraint here is a deliberate and accepted technique: it is needed so that any function fits the constraint.
A limitation: if the source of truth is an external contract (OpenAPI), it is better to generate types from it than to derive them from the implementation. Otherwise the type adjusts to a bug in the function.
In an interview: ReturnType and Parameters derive types from a function, and Awaited unwraps a promise, so Awaited<ReturnType<typeof fn>> gives the result type of an async function. I use this for factories and wrappers, but I take API contracts from OpenAPI, not from the implementation.