๐ŸŽ“ A1Learner
๐Ÿ”
0 of 37 lessons completed

TypeScript exactOptionalPropertyTypes in Practice

exactOptionalPropertyTypes is a compiler flag that treats an absent optional member and a member holding undefined as two different states. Under the flag, an optional member accepts a value of its stated type or no member at all.

exactOptionalPropertyTypes keeps an absent member absent

The profile is built without the nickname member. The in check reports the member as absent, which the flag treats as a state of its own.

interface Profile {
  userName: string;
  nickname?: string;
}

const profile: Profile = { userName: 'Asha' };
console.log('nickname' in profile);
console.log(profile.userName);

exactOptionalPropertyTypes refuses undefined for an optional member

The literal sets nickname to undefined while the member names only string. Under the flag the checker refuses the literal.

interface Profile {
  userName: string;
  nickname?: string;
}

const profile: Profile = { userName: 'Asha', nickname: undefined };
console.log(profile.userName);

exactOptionalPropertyTypes admits undefined with the | undefined spelling

The member is spelled with an added undefined in its type. The literal now compiles under the flag, and in reports the member as present.

interface Profile {
  userName: string;
  nickname?: string | undefined;
}

const profile: Profile = { userName: 'Asha', nickname: undefined };
console.log('nickname' in profile);
console.log(profile.nickname === undefined);

exactOptionalPropertyTypes accepts a member added only when present

The builder starts with an absent member and adds the member only when a value arrives. Both profiles compile under the flag, and the in checks show the two states.

interface Profile {
  userName: string;
  nickname?: string;
}

function buildProfile(userName: string, nickname?: string): Profile {
  const profile: Profile = { userName };
  if (nickname !== undefined) {
    profile.nickname = nickname;
  }
  return profile;
}

const plain = buildProfile('Asha');
console.log('nickname' in plain);
const named = buildProfile('Ben', 'Benny');
console.log('nickname' in named);
if ('nickname' in named) {
  console.log(named.nickname);
}

Example

interface Settings {
  theme: string;
  banner?: string;
  note?: string | undefined;
}

const plain: Settings = { theme: 'dark' };
console.log('banner' in plain);

const withNote: Settings = { theme: 'dark', note: undefined };
console.log('note' in withNote);
console.log(withNote.note === undefined);
console.log(plain.theme);

Key points

  • Without the flag, an optional member written as nickname?: string also accepts an explicit undefined.
  • Under the flag, nickname?: string accepts a string or an absent member, and nothing else.
  • The in operator tells the two states apart, because a member holding undefined is still present.
  • Spelling the member as nickname?: string | undefined admits both states under the flag.
  • Conditional construction builds an object with the member only when a value exists, which keeps absence honest.

In this example, one settings object leaves a member absent while another holds an explicit undefined under the wider spelling, and the in checks print the difference between the two states.

Note

The flag is separate from strict mode, and a project turns it on with its own line in tsconfig.

Try it Yourself

Edit the code, then press Run.

Running is simulated ยท TypeScript Playground

Exercise

Under exactOptionalPropertyTypes, which spelling lets an optional member hold an explicit undefined?