Q:

Why doesn't using keys from two sides of a union cause TS to complain?

Why doesn't this cause a TS error?  TS playground
type T = { a: string } | { b: string }

const obj: T = {
  a: 'a',
  b: 'd',
}

A:

In order to get mutual exclusivity and excess property check one workaround is to do this
export type XOR<T, U> = (T | U) extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U;
export type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };

type T = XOR<{ a: string }, { b: string }>

const obj: T = {
  a: 'a',
} // passes

const obj1: T = {
  b: 'b',
} // passes

const obj2: T = {
  a: 'a',
  b: 'b',
} // fails

