Skip to content
The Switch · JavaScript to TypeScript · Lesson 6

null, undefined and strictness

The empty tiffin box, and why you check before you open it.

Contents
Part 1 of 7 · The hook

The empty tiffin

You opened it expecting lunch.

It is lunch break. You pull your tiffin box out of the bag, open the lid, and reach in for a paratha. The box is empty. Somebody forgot to pack it.

Nothing in the box told you. It looked exactly like a full one from outside. You only found out when you reached in.

That is the most common crash in JavaScript. A value you expected to be there is not there. You reach into it anyway, and the program stops with "Cannot read properties of undefined".

JavaScript has two words for "nothing here". undefined means "nothing was ever put here": a missing property, a function that returned nothing, a find that found nothing. null means "someone chose to leave this empty". The difference is small, and in practice you treat them the same way: check before you reach in.

TypeScript's big idea here is simple. It can mark a tiffin box as "might be empty", and then refuse to let you reach in until you have looked. That setting is called strictNullChecks, and it is part of strict, which you turned on in lesson 1.

Remember: undefined is "never filled", null is "left empty on purpose". TypeScript can make you look before you reach in.

Part 2 of 7 · In JavaScript

Reaching into nothing

JavaScript finds out when it crashes.

Here is the empty tiffin as code. The box has an owner and a lunch, and the lunch is null.

JavaScriptOpen in TypeScript Playground (opens in a new tab)
const tiffin = { owner: "Asha", lunch: null };console.log(tiffin.lunch.toUpperCase());
Output
TypeError: Cannot read properties of null (reading 'toUpperCase')

A TypeError is JavaScript's name for "you used a value in a way its kind doesn't allow". Here, you asked null for a method.

The sneakier version comes from find. A find looks through a list and gives back the first match, or undefined if there is none.

JavaScriptOpen in TypeScript Playground (opens in a new tab)
const students = [  { roll: 1, name: "Asha" },  { roll: 2, name: "Ravi" },];function nameOf(roll) {  return students.find((s) => s.roll === roll).name;}console.log(nameOf(1));console.log(nameOf(9));
Output
Asha
TypeError: Cannot read properties of undefined (reading 'name')

Student 9 doesn't exist. find gave back undefined, and .name on it crashed. The code read fine. It worked in every test with a real student. It broke the first time somebody typed a wrong roll number.

Remember: in JavaScript, any value can be null or undefined, and nothing warns you before you reach in.

Part 3 of 7 · In TypeScript

Mark the box maybe empty

With strictNullChecks, 'maybe' is part of the type.

In strict TypeScript, string means "always text". It can't be null. If a value might be empty, you say so with a union from lesson 5: string | null.

const tiffin = { owner: "Asha", lunch: null };console.log(tiffin.lunch.toUpperCase());

Tinted lines are the ones that differ

The error says 'tiffin.lunch' is possibly 'null'. The checker saw the union and refused the method call. Now the fix is the narrowing you learned last lesson. Look first, then reach in.

TypeScriptOpen in TypeScript Playground (opens in a new tab)
type Tiffin = { owner: string; lunch: string | null };const tiffin: Tiffin = { owner: "Asha", lunch: null };if (tiffin.lunch === null) {  console.log("The tiffin is empty");} else {  console.log(tiffin.lunch.toUpperCase());}
Output
The tiffin is empty

In the else branch, lunch is a plain string, so toUpperCase is allowed.

The find bug goes the same way. TypeScript knows that find may find nothing, so its answer has the type Student | undefined.

const students = [  { roll: 1, name: "Asha" },  { roll: 2, name: "Ravi" },];function nameOf(roll) {  return students.find((s) => s.roll === roll).name;}console.log(nameOf(1));console.log(nameOf(9));

Tinted lines are the ones that differ

JavaScript was happy. TypeScript stops you at .name. This is the bug that crashed in production, caught in the editor. The fix is a check.

TypeScriptOpen in TypeScript Playground (opens in a new tab)
type Student = { roll: number; name: string };const students: Student[] = [  { roll: 1, name: "Asha" },  { roll: 2, name: "Ravi" },];function nameOf(roll: number): string {  const found = students.find((s) => s.roll === roll);  if (found === undefined) {    return "No such student";  }  return found.name;}console.log(nameOf(1));console.log(nameOf(9));
Output
Asha
No such student

Once found is checked against undefined, the early return means the lines after it only run when found is a real Student.

A function can't sneak a null or undefined out either. If it promises string, the checker refuses to let it return undefined.

TypeScriptOpen in TypeScript Playground (opens in a new tab)
function firstWord(text: string): string {  const space = text.indexOf(" ");  if (space === -1) {    return undefined;  }  return text.slice(0, space);}

TypeScript says

error TS2322: Type 'undefined' is not assignable to type 'string'.

Remember: a plain type never includes null or undefined. If a value might be missing, the type says so, and you check before you use it.

Part 4 of 7 · How it works

Three short tools

Optional chaining, ??, and the ! promise.

A check after every value would get tiring. JavaScript has two short forms, and the third tool is TypeScript's own.

1. Optional chaining: ?. Put a question mark before the dot. It means "if the thing on the left is null or undefined, stop and give undefined. Otherwise carry on".

TypeScriptOpen in TypeScript Playground (opens in a new tab)
type Address = { city: string };type Customer = { name: string; address?: Address };function cityOf(customer: Customer): string | undefined {  return customer.address?.city;}console.log(cityOf({ name: "Asha", address: { city: "Pune" } }));console.log(cityOf({ name: "Ravi" }));
Output
Pune
undefined

Ravi has no address, so customer.address?.city stops early and gives undefined. It never reaches into the empty box. The result type is string | undefined, so the checker still reminds you that the answer may be missing.

2. Nullish coalescing: ??. This picks a fallback, but only when the left side is null or undefined. Compare it with the || you may already use. The || replaces every falsy value, meaning any value that counts as "no" in an if: 0, an empty text, false, and also null and undefined.

function runsLabel(runs) {  return "Runs: " + (runs || "did not bat");}console.log(runsLabel(82));console.log(runsLabel(0));console.log(runsLabel(undefined));

Tinted lines are the ones that differ

Output
Runs: 82
Runs: 0
Runs: did not bat

That is the output of the TypeScript side. With ||, a batter who scored a duck (zero runs) was shown as "did not bat", because 0 counts as "no". With ??, only a truly missing score gets the fallback. A zero is a real answer.

3. The non-null assertion: !. An exclamation mark after a value tells the checker "I promise this isn't null or undefined, so stop asking". The checker believes you, and doesn't check. If you're wrong, it crashes at runtime exactly as JavaScript would.

Only use it when you can prove it from the lines just above. Here, we make sure the row exists, and then ask for it.

TypeScriptOpen in TypeScript Playground (opens in a new tab)
const seats = new Map<string, number>();function book(row: string): number {  if (!seats.has(row)) {    seats.set(row, 0);  }  // We just made sure the row exists, so get() cannot give undefined here.  const next = seats.get(row)! + 1;  seats.set(row, next);  return next;}console.log(book("A"));console.log(book("A"));
Output
1
2

We checked seats.has(row) and added the row when missing. So get(row) can't give undefined there. The ! is honest. But often there is a version with no promise at all.

TypeScriptOpen in TypeScript Playground (opens in a new tab)
const seats = new Map<string, number>();function book(row: string): number {  const next = (seats.get(row) ?? 0) + 1;  seats.set(row, next);  return next;}console.log(book("A"));console.log(book("A"));
Output
1
2

?? 0 says "if the row isn't there yet, start from zero". No promise, no crash, same answer. Prefer this one when it reads as well.

Remember: ?. stops at the first empty box, ?? replaces only null and undefined, and ! is a promise you must be able to prove.

Part 5 of 7 · Watch out

Where 'maybe' hides

Four places the checker can't see the empty box.

strict has to be on. With strictNullChecks off, string quietly includes null and undefined, and every example in this lesson compiles without a word. The checker isn't broken. You switched off its best feature. Check that your tsconfig.json has "strict": true.

! is a promise, not a check. It adds no check at runtime. If you use it to silence an error you don't understand, you have only moved the crash from the editor to your user. Lesson 11 comes back to this.

?. can hide a real bug. If address should always be there, a long chain of ?. turns a loud crash into a quiet undefined that shows up three screens later. Use ?. where "missing" is a normal case, not to avoid thinking.

List items are trusted. names[10] on a list of three is undefined at runtime, but TypeScript still says string. A setting called noUncheckedIndexedAccess makes it string | undefined. It is stricter and takes some getting used to. For now, check the length, or use find and a loop.

Remember: keep strict on, treat ! as a promise, and use ?. where "missing" is expected.

Part 6 of 7 · Practise

Three empty boxes, handled

A missing city, a zero score, a missing contact.

Each problem starts with a JavaScript function that crashes or gives a wrong answer. Open it in the Playground and fix it with types.

Problem 1

A city that may be missing

Type cityLabel, so a customer with no address is allowed and gets the text "Unknown city". cityLabel({ name: "Ravi" }) should not crash.

Start here

JavaScriptOpen in TypeScript Playground (opens in a new tab)
function cityLabel(customer) {  return customer.address.city;}console.log(cityLabel({ name: "Asha", address: { city: "Pune" } })); // "Pune"// cityLabel({ name: "Ravi" }) would crash: address is undefined

Stuck?

Show a solution
TypeScriptOpen in TypeScript Playground (opens in a new tab)
type Address = { city?: string };type Customer = { name: string; address?: Address };function cityLabel(customer: Customer): string {  return customer.address?.city ?? "Unknown city";}cityLabel({ name: "Asha", address: { city: "Pune" } }); // "Pune"cityLabel({ name: "Ravi" }); // "Unknown city"

Both address and city are optional, so the type says they may be missing. The ?. stops at a missing address instead of crashing. The ?? supplies a fallback for any missing answer. The JavaScript version crashed for a customer with no address.

Problem 2

A duck is a score

Type scoreLine, and fix it so a player who scored 0 shows "Kohli: 0", and one who didn't bat shows "did not bat".

Start here

JavaScriptOpen in TypeScript Playground (opens in a new tab)
function scoreLine(player) {  return player.name + ": " + (player.runs || "did not bat");}console.log(scoreLine({ name: "Kohli", runs: 0 })); // "Kohli: did not bat", which is wrongconsole.log(scoreLine({ name: "Bumrah" })); // "Bumrah: did not bat"

Stuck?

Show a solution
TypeScriptOpen in TypeScript Playground (opens in a new tab)
type Player = { name: string; runs?: number };function scoreLine(player: Player): string {  return player.name + ": " + (player.runs ?? "did not bat");}scoreLine({ name: "Kohli", runs: 0 }); // "Kohli: 0"scoreLine({ name: "Bumrah" }); // "Bumrah: did not bat"

runs?: number allows a missing score. The ?? only replaces null and undefined, so a zero stays a zero. With ||, Kohli's duck was shown as 'did not bat', which is a quiet and unfair bug.

Problem 3

Pay a contact, if there is one

Type payTo, and handle a name that isn't in the list by returning a message, not by crashing.

Start here

JavaScriptOpen in TypeScript Playground (opens in a new tab)
const contacts = [  { name: "Asha", upi: "asha@upi" },  { name: "Ravi", upi: "ravi@upi" },];function payTo(name) {  const contact = contacts.find((c) => c.name === name);  return "Paying " + contact.upi;}console.log(payTo("Asha")); // "Paying asha@upi"// payTo("Meena") would crash: find gave back undefined

Stuck?

Show a solution
TypeScriptOpen in TypeScript Playground (opens in a new tab)
type Contact = { name: string; upi: string };const contacts: Contact[] = [  { name: "Asha", upi: "asha@upi" },  { name: "Ravi", upi: "ravi@upi" },];function findContact(name: string): Contact | undefined {  return contacts.find((c) => c.name === name);}function payTo(name: string): string {  const contact = findContact(name);  if (contact === undefined) {    return "No contact named " + name;  }  return "Paying " + contact.upi;}payTo("Asha"); // "Paying asha@upi"payTo("Meena"); // "No contact named Meena"

find gives back Contact | undefined. The check for undefined returns early, so the lines after it only run for a real contact, and contact.upi is allowed. The JavaScript version would crash for a name not in the list.

Part 7 of 7 · Interview notes

null in the interview

strictNullChecks, ?., ??, and the ! promise.

These come up in almost every TypeScript interview, often as a single question about "strict mode".

  • "What does strictNullChecks do?" It stops null and undefined from being allowed in every type. A value that might be missing has to say so, as in string | null, and you must check it before use.
  • "What is the difference between null and undefined?" undefined means a value was never set. null means it was set to empty on purpose. TypeScript tracks them as two separate types.
  • "What is the difference between ?. and ???" ?. stops a chain and gives undefined if something on the way is missing. ?? gives a fallback when the left side is null or undefined.
  • "Why ?? and not ||?" || replaces every falsy value, including 0, an empty text and false. ?? replaces only null and undefined.
  • "What is the ! operator?" The non-null assertion. It tells the checker a value isn't null or undefined. It does no checking, so use it only when you can prove the value is there.
  • "How do you check for both null and undefined?" value == null (with two equals signs) matches both. Many teams otherwise write the two checks out.
  • "What does find return in TypeScript?" The item type or undefined, so you must check before using it.
  • Say it simply. "With strict null checks, 'might be empty' is part of the type. I check, or use ?. and ??, before I reach in" is a good answer.

Updated 2026-10-17

Made by VeraStack Labs