← Back to blog

5 common TypeScript interview questions

4 min read
#typescript#interview#generics#utility-types#type-guards

These five questions show up in almost every TypeScript interview. They're not tricky syntax puzzles. They test whether you understand the type system well enough to use it on purpose instead of just making the compiler happy.

any vs unknown

any turns type checking off. Once a value is any, TypeScript stops protecting you and mistakes slip through until runtime. unknown is the safe counterpart: it can hold anything, but you must narrow it before using it.

const a: any = "hello";
a.toUpperCase(); // ✅ no error
a.thisDoesNotExist(); // ✅ no error either 💀

const u: unknown = "hello";
u.toUpperCase(); // ❌ 'u' is of type 'unknown'

if (typeof u === "string") {
  u.toUpperCase(); // ✅ narrowed to string
}

Reach for unknown when the type is genuinely unknown, like API responses, JSON.parse, or catch variables, and never reach for any.

interface vs type

Both describe object shapes and are mostly interchangeable. The real differences are what each one can express.

  • interface supports declaration merging; type doesn't.
  • type can express unions, intersections, tuples, and primitives; interface only describes object-like shapes.
  • interface extends with extends; type composes with &.
interface User {
  name: string;
}
interface User {
  age: number;
} // merged: { name: string; age: number }

type Id = string | number; // only possible with type

Declaration merging cuts both ways: it lets libraries augment existing types (like extending Window), but it can also merge a typo into a valid type. A common convention is interface for object APIs and type for unions and computed types.

Generics

Generics let you write code that works with many types while preserving the relationship between input and output. Without them you'd duplicate the function per type or fall back to any and lose the return type.

function first<T>(items: T[]): T | undefined {
  return items[0];
}

const n = first([1, 2, 3]); // number | undefined
const s = first(["a", "b"]); // string | undefined

You can constrain them when you need to rely on some structure:

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { id: 1, name: "Alice" };

getProperty(user, "name"); // ✅ string
getProperty(user, "age"); // ❌ "age" is not assignable to keyof typeof user

The definition is easy to recite. The interview signal is being able to show a case where a generic removed duplication or a lost type.

Utility types

Partial, Pick, Omit, and Record are built-in generics. They're not magic, but mapped types that transform an existing type into a new one.

type User = { id: string; name: string; email: string };

type PartialUser = Partial<User>; // every property optional
type UserPreview = Pick<User, "id" | "name">; // only id and name
type UserWithoutEmail = Omit<User, "email">; // everything except email
type UsersById = Record<string, User>; // { [key: string]: User }

Under the hood, Partial is roughly this:

type Partial<T> = { [K in keyof T]?: T[K] };

Knowing the mapping is what separates "I know they exist" from "I know how to build my own".

Type guards

A type guard is an expression that gives TypeScript information it can use to narrow a broader type into a more specific one within a block. Common narrowing checks include typeof, instanceof, in, and equality checks.

function format(value: string | number) {
  if (typeof value === "string") {
    return value.toUpperCase(); // string
  }
  return value.toFixed(2); // number
}

When you want to reuse a check across your code, wrap it in a named type guard using a type predicate (x is T):

type Cat = { meow: () => void };
type Dog = { bark: () => void };

function isCat(animal: Cat | Dog): animal is Cat {
  return "meow" in animal;
}

Now TypeScript can narrow the type when you call the guard:

function speak(animal: Cat | Dog) {
  if (isCat(animal)) {
    animal.meow(); // Cat
  } else {
    animal.bark(); // Dog
  }
}

The animal is Cat return type is a contract: TypeScript trusts it and narrows animal to Cat when the function returns true.

That means the logic inside the guard is your responsibility. If the predicate is wrong, TypeScript won't catch the resulting bug.