645 - Diff
About 2 minTC-Medium
Diff
Problem
Get an Object that is the difference between O & O1. The result should contain properties that exist in exactly one of the two objects (symmetric difference).
type Foo = { name: string; age: string }
type Bar = { name: string; age: string; gender: number }
type Result = Diff<Foo, Bar> // { gender: number }Solution
type Diff<O, O1> = {
[K in Exclude<keyof O, keyof O1> | Exclude<keyof O1, keyof O>]:
K extends keyof O ? O[K] : K extends keyof O1 ? O1[K] : never
}Alternatively, using Omit:
type Diff<O, O1> = Omit<O & O1, keyof O & keyof O1>Deep Dive
Understanding the Problem
We need to find the symmetric difference of two object types—properties that appear in one object but not the other. This is different from a simple Exclude because we need to consider keys from both directions.
Approach 1: Explicit Key Exclusion
type Diff<O, O1> = {
[K in Exclude<keyof O, keyof O1> | Exclude<keyof O1, keyof O>]:
K extends keyof O ? O[K] : K extends keyof O1 ? O1[K] : never
}Breaking it down:
Exclude<keyof O, keyof O1>— Keys inObut not inO1Exclude<keyof O1, keyof O>— Keys inO1but not inO- Union them — All unique keys from either object
- Map values — Use conditional type to pick the correct value type
Approach 2: Intersection with Omit (Elegant)
type Diff<O, O1> = Omit<O & O1, keyof O & keyof O1>This is more elegant:
O & O1— Intersection contains all properties from both objectskeyof O & keyof O1— Keys that exist in both objects (common keys)Omit<..., ...>— Remove the common keys, leaving only the differences
Why It Works
Consider Diff<Foo, Bar> where:
Foo = { name: string; age: string }Bar = { name: string; age: string; gender: number }
Using Approach 2:
O & O1={ name: string; age: string; gender: number }keyof O & keyof O1="name" | "age"Omit<...>={ gender: number }✓
Edge Cases
// Same objects → empty object
Diff<{ a: 1 }, { a: 1 }> // {}
// Completely different objects → union of all
Diff<{ a: 1 }, { b: 2 }> // { a: 1; b: 2 }
// Overlapping with different types (uses intersection semantics)
Diff<{ a: string }, { a: number; b: boolean }> // { b: boolean }Key Takeaways
- Symmetric difference requires considering keys from both objects
Omit<O & O1, keyof O & keyof O1>is the cleanest solution- Intersection types (
&) merge all properties; combined withOmitwe can filter what we need - This pattern is useful for computing "what changed" between two type shapes
