Narrowing and Type Guards Interview Questions

What is narrowing? Which constructs narrow types?

Narrowing is how TypeScript refines a broad type to a more specific one inside a code branch, using control flow analysis. Constructs: typeof, instanceof, in, equality (===, == null), truthiness, Array.isArray, discriminant property checks, user-defined type guards (x is T) and assertion functions (asserts x is T).

What is a discriminated union and how do you make a switch exhaustive?

A union of object types that share a literal property (the discriminant). Checking it narrows to one member. For exhaustiveness, assign the value to never in the default branch: adding a new member later produces a compile error there.

Exhaustiveness Check

type Payment = { kind: "upi"; vpa: string } | { kind: "card"; last4: string } | { kind: "wallet"; provider: string };

function label(p: Payment): string {
  switch (p.kind) {
    case "upi": return "UPI " + p.vpa;
    case "card": return "Card **" + p.last4;
    default: {
      const unhandled: never = p; // error: "wallet" is not handled
      return unhandled;
    }
  }
}

Compiler output (tsc)

Line 8: error TS2322: Type '{ kind: "wallet"; provider: string; }' is not assignable to type 'never'.

What is a user-defined type guard? How is it different from an assertion function?

A type guard returns boolean with the return type value is T; the caller narrows inside an if. An assertion function returns void with asserts value is T and throws on failure; code after the call is narrowed. Both are only as correct as their implementation: TypeScript trusts them.

Guard vs Assertion

type User = { id: number; email: string };
const isUser = (x: unknown): x is User =>
  typeof x === "object" && x !== null && "id" in x && typeof x.id === "number" && "email" in x && typeof x.email === "string";

function assertUser(x: unknown): asserts x is User {
  if (!isUser(x)) throw new TypeError("Not a user");
}

const data: unknown = JSON.parse('{"id": 1, "email": "[email protected]"}');
if (isUser(data)) console.log("guard:", data.email);
assertUser(data);
console.log("assert:", data.id);

Output

guard: [email protected]
assert: 1

Why can typeof x === "object" not remove null?

Because in JavaScript typeof null === "object". TypeScript models this, so after that check the type is still object | null; add x !== null. Also remember truthiness checks remove 0, "" and NaN, which can be a bug for numbers; prefer x != null or ??.