Utility Types
- #typescript
- #utility-types
- #partial
- #readonly
- #pick
- #record
In questa lezione
1. Introduction
TypeScript ships with a set of built-in utility types that let you transform an existing type into a new one, without repeating its structure by hand. They’re globally available — no import needed — and they’re heavily used across real-world TypeScript codebases, so understanding what each one does (and doesn’t do) is essential.
We’ll start from a shared Todo interface:
interface Todo {
title: string;
description: string;
completed: boolean;
}
Suggerimento
Compared to some textbook examples, completed: boolean has been added to Todo here on purpose — it’s what a realistic “to-do” concept needs, and it’s the property several utility types below (Pick, Omit) will operate on.
2. Partial<T>
Partial<T> produces a new type where every property of T becomes optional. It’s the classic shape for “update” functions, where the caller only supplies the fields they want to change:
const updateTodo = (id: number, todo: Partial<Todo>) => {
// todo might only contain { completed: true }, and that's valid
};
updateTodo(1, { completed: true });
updateTodo(2, { title: "Buy groceries", description: "Milk, eggs, bread" });
Without Partial, both calls above would fail to compile, since Todo normally requires all three properties.
3. Readonly<T>
Readonly<T> produces a type where all properties become read-only — they can be assigned once when the object is created, but never reassigned afterward:
const myTodo: Readonly<Todo> = {
title: "Learn TypeScript",
description: "",
completed: false
};
myTodo.completed = true; // Error: Cannot assign to 'completed' because it is a read-only property.
Attenzione
Readonly<T> is a compile-time guarantee only. It does not freeze the object at runtime the way Object.freeze() does — if the value crosses into plain JavaScript code, or is cast with as, nothing stops a mutation there. Use Object.freeze() alongside it if you need actual runtime immutability.
4. Record<K, T>
Record<K, T> builds an object type whose keys are of type K and whose values are all of type T. It’s the type-safe way to describe a “dictionary” or “map”-like object:
interface PageInfo {
title: string;
}
const pages: Record<string, PageInfo> = {
home: { title: "Home" },
about: { title: "About" },
contact: { title: "Contact" }
};
const pagesByIndex: Record<number, PageInfo> = {
0: { title: "Home" },
1: { title: "About" },
2: { title: "Contact" }
};
Both the key and value types are checked: attempting pages.home = { title: 42 } (a number instead of a string) would fail, and so would using a key type that doesn’t match K.
5. Pick<T, K>
Pick<T, K> builds a new type containing only the properties named in K (a union of string literal keys) from T:
type TodoPreview = Pick<Todo, "title" | "completed">;
const todo: TodoPreview = {
title: "Clean room",
completed: false
};
TodoPreview only has title and completed — description isn’t part of it, and trying to access todo.description would be a compile error.
Pericolo
A very common mistake is picking a key that doesn’t actually exist on the source type — for example Pick<Todo, "title" | "priority"> when Todo has no priority field. TypeScript will reject this at the type level with an error like “Type ‘“priority”’ does not satisfy the constraint…”. Always double check the source interface’s actual property names before writing a Pick.
6. More utility types worth knowing
Beyond the four above, a handful of other built-in utility types come up constantly.
6.1 Required<T>
The opposite of Partial<T>: makes every property required, even ones that were originally declared optional.
interface Draft {
title?: string;
description?: string;
}
type PublishedPost = Required<Draft>;
// { title: string; description: string }
const post: PublishedPost = { title: "Hello", description: "World" };
6.2 Omit<T, K>
The inverse of Pick: builds a type with all properties except the ones listed in K.
type TodoWithoutDescription = Omit<Todo, "description">;
// { title: string; completed: boolean }
const shortTodo: TodoWithoutDescription = { title: "Ping Marco", completed: true };
6.3 Exclude<T, U>
Works on union types rather than object shapes: it removes from T any member that is assignable to U.
type Status = "draft" | "published" | "archived";
type ActiveStatus = Exclude<Status, "archived">;
// "draft" | "published"
6.4 ReturnType<T>
Extracts the return type of a function type — useful when you don’t want to (or can’t) name a type explicitly and want to derive it from an existing function instead.
function createTodo(title: string): Todo {
return { title, description: "", completed: false };
}
type CreatedTodo = ReturnType<typeof createTodo>;
// Todo
Suggerimento
Notice the typeof createTodo inside ReturnType<...> — typeof here is a TypeScript type-level operator that reads the type of a value (in this case, a function), which is different from the runtime typeof operator used in typeof value === "string" checks from the type-guards lesson.
7. Choosing the right utility type
| Situation | Utility type |
|---|---|
| Update function accepting partial input | Partial<T> |
| Value that shouldn’t be reassigned | Readonly<T> |
| Dictionary/map keyed by a known type | Record<K, T> |
| Subset of properties for a “preview” | Pick<T, K> |
| All properties, but all required | Required<T> |
| Everything except a few properties | Omit<T, K> |
| Remove specific members from a union | Exclude<T, U> |
| Derive a type from a function’s return value | ReturnType<T> |
8. Further reading
9. Quiz
Mettiti alla prova
0/7 risposte
What does
Partial<Todo>produce?What guarantee does
Readonly<T>actually provide?Given
interface PageInfo { title: string }, what doesRecord<string, PageInfo>describe?What happens if you write
Pick<Todo, "title" | "priority">when Todo has nopriorityproperty?Which utility type builds a type with all properties EXCEPT the ones you list?
What does
Exclude<Status, "archived">do, giventype Status = "draft" | "published" | "archived"?What does
ReturnType<typeof createTodo>extract?
10. Exercises
Scenario: You are building a small task-management API and want to use utility types instead of hand-writing near-duplicate interfaces.
Task:
- Start from
interface Task { id: number; title: string; assignee: string; done: boolean; }. - Create a
CreateTaskInputtype usingOmit<Task, "id" | "done">to represent the payload for creating a new task (the server assignsid, anddonealways startsfalse). - Create an
UpdateTaskInputtype usingPartial<Omit<Task, "id">>to represent a partial update payload. - Create a
TaskSummarytype usingPick<Task, "id" | "title" | "done">for a lightweight list view. - Write three functions —
createTask,updateTask,listTaskSummaries— using these types in their signatures (implementations can be simple stubs).
Learning objective: compose multiple utility types (Omit, Partial, Pick) to model realistic request/response shapes derived from a single source interface.
Scenario: A reporting module needs a lookup table of report generators keyed by report name, plus a way to derive types from existing functions rather than duplicating them.
Task:
- Write a function
generateSalesReport(year: number): { total: number; year: number }. - Use
ReturnType<typeof generateSalesReport>to define a typeSalesReportwithout repeating the shape manually. - Define
type ReportName = "sales" | "inventory" | "payroll";and buildtype ReportRegistry = Record<ReportName, () => void>representing a lookup of report-generating functions. - Populate a
reportRegistry: ReportRegistryobject with a function for each key, and callreportRegistry.sales()to confirm it type-checks.
Learning objective: practice deriving types from functions with ReturnType and building strongly-typed lookup tables with Record.