Yuzhe's Blog

yuzhes

645 - Diff

Diff

Challenge Link

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:

  1. Exclude<keyof O, keyof O1> — Keys in O but not in O1
  2. Exclude<keyof O1, keyof O> — Keys in O1 but not in O
  3. Union them — All unique keys from either object
  4. 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:

  1. O & O1 — Intersection contains all properties from both objects
  2. keyof O & keyof O1 — Keys that exist in both objects (common keys)
  3. Omit<..., ...> — Remove the common keys, leaving only the differences

Why It Works

Consider Diff<Foo, Bar> where:

Using Approach 2:

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

  1. Symmetric difference requires considering keys from both objects
  2. Omit<O & O1, keyof O & keyof O1> is the cleanest solution
  3. Intersection types (&) merge all properties; combined with Omit we can filter what we need
  4. This pattern is useful for computing “what changed” between two type shapes