Skip to content

TypeScript Interview Questions

Q: What is the difference between type and interface?

// interface — extendable, mergeable (declaration merging)
interface User {
name: string;
age: number;
}
interface User {
email: string; // merged with above
}
// type — more flexible, supports unions/intersections
type ID = string | number;
type Point = { x: number; y: number };
type Named = Point & { name: string };

Use interface for object shapes and public APIs. Use type for unions, intersections, and aliases.


Q: What is the difference between any, unknown, and never?

// any — disables type checking (avoid)
let x: any = 5;
x.foo.bar; // no error
// unknown — type-safe any, must narrow before use
let y: unknown = 5;
// y.foo; // Error
if (typeof y === 'string') y.toUpperCase(); // OK after narrowing
// never — represents impossible values
function fail(msg: string): never {
throw new Error(msg);
}
// Exhaustive check
type Shape = 'circle' | 'square';
function area(s: Shape) {
if (s === 'circle') return Math.PI;
if (s === 'square') return 1;
const _exhaustive: never = s; // Error if Shape has unhandled case
}

Q: What is type narrowing?

function process(value: string | number) {
if (typeof value === 'string') {
return value.toUpperCase(); // narrowed to string
}
return value.toFixed(2); // narrowed to number
}
// instanceof narrowing
function handle(err: Error | string) {
if (err instanceof Error) {
console.log(err.message);
}
}
// in operator narrowing
type Cat = { meow: () => void };
type Dog = { bark: () => void };
function speak(animal: Cat | Dog) {
if ('meow' in animal) animal.meow();
else animal.bark();
}

Q: What are type guards?

// typeof guard
function isString(val: unknown): val is string {
return typeof val === 'string';
}
// Custom type guard
interface Admin { role: 'admin'; permissions: string[] }
interface User { role: 'user'; name: string }
function isAdmin(user: Admin | User): user is Admin {
return user.role === 'admin';
}
const u: Admin | User = getUser();
if (isAdmin(u)) {
console.log(u.permissions); // TypeScript knows it's Admin
}

Q: What are generics and why use them?

// Without generics — loses type info
function first(arr: any[]): any { return arr[0]; }
// With generics — type-safe
function first<T>(arr: T[]): T { return arr[0]; }
const n = first([1, 2, 3]); // n: number
const s = first(['a', 'b']); // s: string

Q: What are generic constraints?

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "Alice", age: 30 };
getProperty(user, 'name'); // string
getProperty(user, 'age'); // number
// getProperty(user, 'foo'); // Error — 'foo' not in keyof User

Q: Explain the most useful utility types.

interface User {
id: number;
name: string;
email: string;
age?: number;
}
// Partial — all properties optional
type PartialUser = Partial<User>;
// Required — all properties required
type RequiredUser = Required<User>;
// Pick — select subset of properties
type UserPreview = Pick<User, 'id' | 'name'>;
// Omit — exclude properties
type UserWithoutId = Omit<User, 'id'>;
// Readonly — all properties readonly
type ReadonlyUser = Readonly<User>;
// Record — map type
type Roles = Record<string, User[]>;
// ReturnType — extract return type of function
function getUser() { return { id: 1, name: "Alice" }; }
type UserType = ReturnType<typeof getUser>;
// Parameters — extract parameter types
type Params = Parameters<typeof getUser>;
// NonNullable — remove null/undefined
type SafeString = NonNullable<string | null | undefined>; // string

Q: What are mapped types?

// Make all properties optional
type Optional<T> = { [K in keyof T]?: T[K] };
// Make all properties nullable
type Nullable<T> = { [K in keyof T]: T[K] | null };
// Make all properties readonly
type Immutable<T> = { readonly [K in keyof T]: T[K] };
// Conditional mapped type
type NonFunctions<T> = {
[K in keyof T]: T[K] extends Function ? never : K
}[keyof T];

Q: What are conditional types?

type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
// Infer keyword
type UnpackPromise<T> = T extends Promise<infer U> ? U : T;
type Resolved = UnpackPromise<Promise<string>>; // string
type Plain = UnpackPromise<number>; // number

Q: What are template literal types?

type EventName = 'click' | 'focus' | 'blur';
type Handler = `on${Capitalize<EventName>}`;
// 'onClick' | 'onFocus' | 'onBlur'
type CSSProperty = 'margin' | 'padding';
type CSSDirection = 'Top' | 'Right' | 'Bottom' | 'Left';
type CSSProp = `${CSSProperty}${CSSDirection}`;
// 'marginTop' | 'marginRight' | ... | 'paddingLeft'

Q: What is declaration merging?

// Interface merging
interface Window {
myCustomProp: string;
}
window.myCustomProp = "hello"; // now valid
// Namespace merging with function
function validate(x: string): boolean { return x.length > 0; }
namespace validate {
export const minLength = 1;
}
validate("test");
validate.minLength; // 1

Q: What is the satisfies operator (TypeScript 4.9)?

type Colors = 'red' | 'green' | 'blue';
type ColorMap = Record<Colors, string | [number, number, number]>;
const palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255]
} satisfies ColorMap;
// satisfies validates the type but preserves the literal type
palette.green.toUpperCase(); // OK — TypeScript knows it's string, not string | number[]

Q: What is infer and how is it used?

// Extract element type from array
type ElementType<T> = T extends (infer E)[] ? E : never;
type Num = ElementType<number[]>; // number
// Extract first argument type
type FirstArg<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never;
type F = FirstArg<(x: string, y: number) => void>; // string
// Unwrap nested Promise
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;

Q: What is the difference between interface extending and type intersection?

interface A { a: string }
interface B extends A { b: number } // B has a and b
type C = { c: boolean }
type D = A & C // D has a and c
// Key difference: interface extends reports errors at definition
// type intersection merges silently (may produce never for conflicting types)
interface X { prop: string }
interface Y extends X { prop: number } // Error at definition
type P = { prop: string } & { prop: number } // prop: never — no error at definition