Q206: Utility Types and Type Transformations
What Interviewers Want To Evaluate
Interviewers want to know whether you can use TypeScript utility types to describe real data transformations without making types unreadable.
They check Partial, Required, Pick, Omit, Readonly, Record, Exclude, Extract, NonNullable, ReturnType, Parameters, and how these utilities affect API design.
Short Interview Answer
Utility types are built-in generic helpers that transform existing types. I use them when a new type is clearly derived from an existing model, such as drafts, previews, API payloads, readonly data, and lookup maps. I avoid stacking too many utilities together because the type should still communicate intent to the next developer.
Detailed Interview Answer
Utility types help avoid duplication.
Instead of rewriting similar shapes, you derive them.
type User = {
id: string;
name: string;
email: string;
role: "admin" | "member";
};
From this model, different parts of the UI may need different views.
Partial
Partial<T> makes every property optional.
type UserDraft = Partial<User>;
This is useful for patch forms.
const draft: UserDraft = {
name: "Ada",
};
But Partial should not be used when a state has specific required fields.
For example, a create form may require name and email.
type CreateUserInput = {
name: string;
email: string;
role?: User["role"];
};
Specific types can be clearer than broad partials.
Required
Required<T> makes all properties required.
type FormValues = {
name?: string;
email?: string;
};
type ValidFormValues = Required<FormValues>;
This can represent a post-validation shape.
Use it carefully because validation is runtime behavior.
The type should only change after the code actually proves the fields exist.
Pick
Pick<T, K> keeps selected properties.
type UserPreview = Pick<User, "id" | "name">;
This is common in list cards, search results, and navigation menus.
function UserCard({ user }: { user: UserPreview }) {
return <a href={`/users/${user.id}`}>{user.name}</a>;
}
The component asks for only what it needs.
Omit
Omit<T, K> removes properties.
type PublicUser = Omit<User, "email">;
This is useful when hiding sensitive fields.
But be careful with security.
Omit only affects TypeScript.
It does not remove the property at runtime.
function toPublicUser(user: User): PublicUser {
const { email, ...publicUser } = user;
return publicUser;
}
You still need real data transformation.
Readonly
Readonly<T> prevents mutation through that reference.
type ReadonlyUser = Readonly<User>;
This helps for configuration and state snapshots.
function renderUser(user: ReadonlyUser) {
user.name = "Changed";
}
The compiler blocks accidental mutation.
It does not deeply freeze runtime objects.
Record
Record<K, V> creates a map type.
type Status = "idle" | "loading" | "success" | "error";
const labels: Record<Status, string> = {
idle: "Start",
loading: "Loading",
success: "Done",
error: "Failed",
};
This is excellent for exhaustive lookup tables.
If a new status is added, TypeScript can force the map to update.
Exclude And Extract
Exclude<T, U> removes union members.
type RequestStatus = "idle" | "loading" | "success" | "error";
type TerminalStatus = Exclude<RequestStatus, "idle" | "loading">;
Extract<T, U> keeps matching union members.
type ErrorStatus = Extract<RequestStatus, "error">;
These utilities are useful when a domain union has meaningful subsets.
NonNullable
NonNullable<T> removes null and undefined.
type MaybeUser = User | null | undefined;
type LoadedUser = NonNullable<MaybeUser>;
It is useful after validation or narrowing.
Do not use it to pretend data is loaded before it is actually checked.
ReturnType And Parameters
ReturnType<T> extracts a function return type.
function createUserViewModel(user: User) {
return {
title: user.name,
subtitle: user.role,
};
}
type UserViewModel = ReturnType<typeof createUserViewModel>;
Parameters<T> extracts argument types.
type CreateUserArgs = Parameters<typeof createUserViewModel>;
These are useful when a helper is the source of truth.
Awaited
Awaited<T> unwraps promise-like values.
async function loadUser() {
return { id: "u1", name: "Ada" };
}
type LoadedUser = Awaited<ReturnType<typeof loadUser>>;
This is useful for server functions, data loaders, and test fixtures.
Utility Types In React Props
Utility types can reduce prop duplication.
type BaseButtonProps = {
label: string;
disabled?: boolean;
onClick: () => void;
};
type IconButtonProps = Omit<BaseButtonProps, "label"> & {
ariaLabel: string;
icon: React.ReactNode;
};
This works if the relationship is obvious.
If the transformed type becomes hard to read, write a named type directly.
Common Mistakes
Common mistakes include:
using Partial for every form
using Omit as a security mechanism without runtime removal
stacking many utility types until intent disappears
deriving types from unstable implementation details
using Record with string when a narrower union is available
using NonNullable before checking the value
Utility types are helpers, not a replacement for design.
Senior Trade-Offs
Use utility types when derivation is the main idea.
Use explicit named types when the new shape has independent meaning.
Good examples:
UserPreview from User
PatchPayload from domain model
StatusLabelMap from status union
LoadedData from async loader
ReadonlyConfig from config object
Poor examples are long type puzzles that nobody wants to review.
Interview Framing
In an interview, say:
I use utility types to keep related models consistent, but I do not let them hide business meaning. If a transformed type becomes a domain concept, I give it a name and keep the runtime transformation honest.
Revision Notes
- Utility types transform existing types.
PickandOmitare useful for view models and payloads.Recordis strong with literal unions.ReturnType,Parameters, andAwaitedconnect functions to derived types.- Type transformations do not transform runtime data.
- Readability beats clever utility stacking.
Follow-Up Questions
- When would you use
Pickinstead of writing a new type? - Why is
Omitnot enough for security? - How can
Recordenforce exhaustive maps? - When is
ReturnTypeuseful? - What is a danger of overusing
Partial?
How I Would Answer This In A Real Interview
I would say utility types are great when the new shape is clearly derived from an existing model. I use Pick for previews, Omit for payloads after real runtime removal, Record for exhaustive maps, and ReturnType or Awaited when a function is the source of truth. My rule is that the type should still read like a design, not a puzzle.