-
Section 1: Getting Started
-
Section 2: Basic Types
-
Section 3: Functions and Objects
-
Section 4: Advanced Types
-
Section 5: Object-Oriented TypeScript
-
Section 6: Working with Modules
-
Section 7: Tooling and Practice
-
Section 8: Type-Level Programming
-
Section 9: TypeScript with Backends
-
Section 10: Testing Typed Code
-
Section 11: Data Validation with Types
-
Section 12: Practical Projects
-
Section 13: Compiler Internals
-
Section 14: Configuration Deep Dive
-
Section 15: Enums, Symbols, and Special Types
-
Section 16: Working with Async Code
-
Section 17: TypeScript and the DOM
-
Section 18: Advanced Generics Practice
-
Section 19: Working with Third-Party Types
-
Section 20: Monorepo and Large-Scale Practices
-
Section 21: Common Pitfalls and Best Practices
-
Section 22: Interview Practice
-
Section 23: Handbook: Narrowing In Depth
-
Section 24: Handbook: Object Types In Depth
-
Section 25: Handbook: Classes In Depth
-
Section 26: Handbook: Modules In Depth
-
Section 27: Handbook: Declaration Files In Depth
-
Section 28: JSX and Namespaces
-
Section 29: Compiler Configuration Reference
-
Section 30: More Practice Exercises
-
Section 31: Handbook: Everyday Types Deep Dive
-
Section 32: Utility Types Full Reference
-
Section 33: Decorators Reference
-
Section 34: Mixins and Advanced OOP Patterns
-
Section 35: Iterators and Generators Typing
-
Section 36: More Type-Level Programming Practice
-
Section 37: TypeScript Ecosystem Tools
-
Section 38: TypeScript with Testing Frameworks
-
Section 39: TypeScript for Library Authors
-
Section 40: More Interview Practice
-
Section 41: Handbook: Functions In Depth
-
Section 42: Handbook: Type Manipulation Deep Dive
-
Section 43: More Real-World Patterns
-
Section 44: TypeScript Release Notes Highlights
-
Section 45: Final Practice and Review
183: Partial<Type> and Required<Type> Revisited
We've touched on Partial and Required before, but in a real-world codebase, they aren't just "neat tricks" for making fields optional. They are tools for managing the lifecycle of your data. I often see developers struggle with "Update" patterns where they end up duplicating their interfaces, which is a recipe for a maintenance nightmare.
The Maintenance Trap: Manual Mirroring
Imagine you're building a user profile system. You have a User interface that defines exactly what a user looks like in your database. When it comes time to write an update function, the naive approach is to create a second interface specifically for the update payload.
interface User {
id: string;
username: string;
email: string;
avatarUrl: string;
bio: string;
}
interface UpdateUserDto {
username?: string;
email?: string;
avatarUrl?: string;
bio?: string;
}
function updateUser(id: string, updates: UpdateUserDto) {
// logic to update user in DB
}
At first glance, this looks clean. But here is where it breaks: the moment you add a phoneNumber field to the User interface, you have to remember to manually add it to UpdateUserDto as well. If you forget, the TypeScript compiler won't complain, but your API will silently ignore the phone number update. I've spent way too many hours debugging "missing field" bugs that were caused by this kind of manual mirroring. You're essentially lying to the compiler by claiming these are two different shapes when they are actually the same shape with different requirements.
Sourcing Truth with Partial
The better way is to treat your base interface as the single source of truth. Instead of maintaining a separate DTO, we use Partial<T> to tell TypeScript: "I want a type that has all the same keys as User, but every single one of them is now optional."
interface User {
id: string;
username: string;
email: string;
avatarUrl: string;
bio: string;
}
// No separate interface needed.
// We just omit the 'id' because we don't want users updating their primary key.
type UpdateUserDto = Partial<Omit<User, 'id'>>;
function updateUser(id: string, updates: UpdateUserDto) {
// TypeScript now knows exactly which fields are allowed based on the User interface
}
Now, if I add phoneNumber to User, UpdateUserDto updates automatically. I don't have to touch it. This is the core strength of TypeScript's utility types—they allow you to describe the transformation of data rather than the data itself.
Closing the Loop with Required
The flip side of this is Required<T>. You'll rarely use this on a base interface, but it's incredibly powerful when you're dealing with "Draft" states. Let's say you have a complex configuration object that is built piece-by-piece across several screens of a wizard. While the user is filling it out, the object is a Partial<Config>.
However, your internal deployServer function cannot handle missing values; it needs a complete configuration to avoid crashing the production environment. Instead of using a bunch of if (!config.port) checks or non-null assertions (which I strongly advise against), you can use a validation function that returns a Required<Config>.
interface ServerConfig {
port: number;
region: string;
instanceSize: string;
}
type DraftConfig = Partial<ServerConfig>;
function finalizeConfig(draft: DraftConfig): Required<ServerConfig> {
if (!draft.port || !draft.region || !draft.instanceSize) {
throw new Error("Missing required configuration fields!");
}
// We cast it here because we've manually verified the presence of all fields
return draft as Required<ServerConfig>;
}
const myDraft: DraftConfig = { port: 8080 };
// const final = finalizeConfig(myDraft); // This would throw the error
By utilizing Required, you create a "type gate." Once the data passes through finalizeConfig, the rest of your application can stop worrying about undefined and start treating the data as a guaranteed, complete object. It turns a runtime uncertainty into a type-level certainty.
📋 Practical Task
Refactor the ProjectSettings Guard
You are working on a project management tool. Currently, the project settings are handled using a manually mirrored interface, which has led to bugs when new settings are added. Your task is to refactor the code to use Partial and Required.
Requirements:
- Remove the
ProjectUpdatePayloadinterface entirely. - Use
Partialcombined withOmitto create a type for updates that allows any setting to be changed except for theprojectId. - Implement a function called
validateSettingsthat takes aPartial<ProjectSettings>and returns aRequired<ProjectSettings>, throwing an error if any field is missing.
interface ProjectSettings {
projectId: string;
projectName: string;
visibility: 'public' | 'private';
maxContributors: number;
}
// TODO: Remove this and replace with a utility type based on ProjectSettings
interface ProjectUpdatePayload {
projectName?: string;
visibility?: 'public' | 'private';
maxContributors?: number;
}
function updateProject(id: string, changes: ProjectUpdatePayload) {
console.log(`Updating ${id} with`, changes);
}
// TODO: Implement this using Required<ProjectSettings>
function validateSettings(settings: Partial<ProjectSettings>) {
// Your logic here
}There are no comments for now.