interviewDeck

Your one-stop platform to prepare, practice and ace your interviews.

Loading your questions…

All Questions

Filters & tools

TypeScript Interview Questions and Answers

27 hand-picked TypeScript interview questions with detailed answers. Open the interactive version above to search, filter by difficulty, run code, bookmark questions and track your progress.

interface vs type — what's the difference?

Both describe shapes. interface can be re-opened/merged (declaration merging) and is the idiom for object/class contracts. type is more flexible — it can be a union, intersection, tuple, or alias a primitive.

Rule of thumb: interface for public object shapes, type for unions and utility compositions.

type Status = 'active' | 'inactive';
interface User { id: number; name: string; }

any vs unknown vs never.

  • any — turns off type checking (avoid).
  • unknown — a safe any; you must narrow it before use.
  • never — a value that can't exist (a function that always throws, or an exhausted union).
// any — checking OFF, everything allowed, crashes at runtime
const a: any = JSON.parse(s);
a.foo.bar.baz();          // compiles. explodes.

// unknown — must narrow before use
const u: unknown = JSON.parse(s);
if (typeof u === 'object' && u !== null && 'name' in u) {
  console.log((u as { name: string }).name);   // now safe
}

// never — no value can exist
function fail(msg: string): never { throw new Error(msg); }

What are generics?

Generics allow functions, classes, interfaces, and types to work with different data types while preserving type safety. Instead of using any, a generic acts as a placeholder type that is determined when the function or class is used.

// Generic Function
function first<T>(arr: T[]): T {
  return arr[0];
}

const name = first(['Alice', 'Bob']);
const number = first([10, 20, 30]);

console.log(name);   // Alice
console.log(number); // 10

// Generic Interface
interface ApiResponse<T> {
  data: T;
  success: boolean;
}

const response: ApiResponse<string[]> = {
  data: ['Angular', 'React'],
  success: true
};

Name some built-in utility types.

  • Partial<T> — all props optional (patch/update DTOs).
  • Required<T> — all props required.
  • Pick<T,K> / Omit<T,K> — select or exclude keys.
  • Record<K,V> — a map/dictionary type.
  • Readonly<T> — immutable props.
type UserPatch = Partial<User>;
type Roles = Record<string, boolean>;

What are enums, and what's a union alternative?

enum defines a set of named constants. Numeric enums generate reverse mappings; string enums are more readable in logs.

Many teams prefer a string literal union (type Size = 'sm' | 'md' | 'lg') — it's lighter and tree-shakable, with no runtime object.

enum Role { Admin = 'ADMIN', User = 'USER' }

Explain public, private, protected, and readonly in TypeScript.

TypeScript provides access modifiers to control the visibility of class members. public members are accessible from anywhere, private members can only be accessed inside the same class, protected members are accessible inside the class and its subclasses, while readonly members can only be assigned once during declaration or inside the constructor.

class Person {
  public name = 'John';
  private age = 25;
  protected city = 'Bangalore';
  readonly country = 'India';
}

const person = new Person();
console.log(person.name); // ✅
// console.log(person.age); // ❌
// console.log(person.city); // ❌
console.log(person.country); // ✅

What is type narrowing / type guards?

Refining a broad type to a specific one within a block using checks like typeof, instanceof, in, or a custom type predicate (x is Cat). TypeScript then knows the precise type inside that branch.

function isCat(a: Animal): a is Cat { return 'meow' in a; }

Union vs intersection types.

Union (A | B) — the value is one of the types. Intersection (A & B) — the value has all properties of both.

type Id = string | number;      // union
type Employee = Person & Staff;  // intersection

What does strictNullChecks do in TypeScript?

strictNullChecks is a TypeScript compiler option that treats null and undefined as separate types. When enabled, they cannot be assigned to other types unless explicitly included using a union type. This helps catch null-related bugs at compile time instead of runtime.

// strictNullChecks = true

let username: string;

// username = null;      // ❌ Error
// username = undefined; // ❌ Error

let name: string | null = null; // ✅
let email: string | undefined;  // ✅

What are mapped types?

Types that transform each property of another type. Utility types like Partial and Readonly are mapped types under the hood.

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

What do keyof and typeof do at the type level?

keyof T produces a union of a type's property names. Type-level typeof value captures the type of an existing value/const so you can derive types from data.

const config = { host: '', port: 0 };
type ConfigKey = keyof typeof config; // 'host' | 'port'

What are decorators (as used in Angular)?

Decorators are functions that add metadata/behaviour to classes, methods, properties, or parameters. Angular relies on them heavily: @Component, @Injectable, @Input, @Output.

@Input() label = '';
@Output() change = new EventEmitter();

What does 'as const' do?

A const assertion makes a value deeply readonly and narrows it to its literal types instead of the widened primitive — useful for building precise union types from arrays/objects.

const sizes = ['sm', 'md', 'lg'] as const;
type Size = typeof sizes[number]; // 'sm' | 'md' | 'lg'

What is the non-null assertion operator (!)?

The postfix ! tells the compiler 'this value is definitely not null/undefined here'. It removes the check but does not add a runtime guard — use it only when you're certain.

@ViewChild('el') el!: ElementRef; // asserted, assigned by Angular later

What is a discriminated (tagged) union?

A union of object types that share a common literal tag field. Switching on that tag lets TypeScript narrow to the exact member, giving type-safe, exhaustive handling — great for state machines and Redux/NgRx actions.

type Shape =
  | { kind: 'circle'; r: number }
  | { kind: 'square'; side: number };
function area(s: Shape) {
  switch (s.kind) {
    case 'circle': return Math.PI * s.r ** 2;
    case 'square': return s.side ** 2;
  }
}

What are conditional types?

Types that branch based on a condition: T extends U ? X : Y. They power much of the standard library (e.g. Exclude, ReturnType) and let generic types adapt to their inputs.

type NonNull<T> = T extends null | undefined ? never : T;
type A = NonNull<string | null>; // string

What does the infer keyword do?

Inside a conditional type, infer captures a type from a position so you can reuse it — e.g. extract a function's return type or an array's element type.

type Return<T> = T extends (...args: any[]) => infer R ? R : never;
type R = Return<() => number>; // number

What is an index signature?

Declares that an object can have arbitrary keys of a given type: { [key: string]: number }. Useful for dictionaries when keys aren't known ahead of time. Record<K, V> is the shorthand.

interface Scores { [player: string]: number; }
const s: Scores = { alice: 10, bob: 7 };

What are tuple types in TypeScript?

A tuple is a special type of array where the number of elements and the type of each element are fixed. Unlike normal arrays, each position in a tuple has its own predefined type.

const employee: [string, number] = ['John', 25];

console.log(employee[0]); // John
console.log(employee[1]); // 25

What are function overloads in TypeScript?

Function overloads allow a function to have multiple call signatures while sharing a single implementation. Each overload describes a different way the function can be called, enabling TypeScript to infer the correct parameter and return types based on the arguments passed.

interface User {
  id: number;
  name: string;
}

function getUser(id: number): User;
function getUser(ids: number[]): User[];

function getUser(idOrIds: number | number[]) {
  if (Array.isArray(idOrIds)) {
    return idOrIds.map(id => ({ id, name: `User ${id}` }));
  }

  return {
    id: idOrIds,
    name: `User ${idOrIds}`
  };
}

const user = getUser(1);
const users = getUser([1, 2, 3]);

What is the difference between an Abstract Class and an Interface in TypeScript?

An abstract class is a base class that cannot be instantiated directly. It can contain both implemented methods and abstract methods that subclasses must implement. An interface defines only the structure (contract) of an object or class and does not contain implementation. Use an abstract class when you want to share common behavior, and use an interface when you only want to enforce a contract.

abstract class Animal {
  abstract makeSound(): void;

  eat() {
    console.log('Eating...');
  }
}

class Dog extends Animal {
  makeSound() {
    console.log('Bark');
  }
}

What does the satisfies operator do?

(TS 4.9+) satisfies checks that a value matches a type without widening it — you keep the precise inferred literal types while still validating the shape. Best of both worlds vs a plain annotation.

const routes = { home: '/', about: '/about' } satisfies Record<string, string>;
// routes.home is '/', not just string

What are Template Literal Types in TypeScript?

Template Literal Types allow you to create new string literal types by combining existing literal types using JavaScript template string syntax. They help generate strongly typed strings, such as event names, CSS classes, API routes, and object keys.

type Size = 'sm' | 'md' | 'lg';

type ButtonClass = `btn-${Size}`;

// 'btn-sm' | 'btn-md' | 'btn-lg'

How do you constrain generics using extends and keyof in TypeScript?

Generic constraints limit the types that can be passed to a generic. The extends keyword ensures a type has a required shape, while keyof restricts a value to the valid property names of an object. Together, they enable type-safe and reusable generic functions.

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

const user = {
  id: 1,
  name: 'John'
};

console.log(getProp(user, 'name')); // John
// getProp(user, 'age'); // ❌ Error

Why TypeScript over JavaScript? And what does tsconfig strict mode buy you?

TypeScript is JavaScript + a static type system that runs at compile time and is erased at runtime. The wins:

  • Errors at compile time, not in production — typos, wrong argument shapes, null access surface in the editor.
  • Refactoring at scale — rename a field and every stale usage lights up; the compiler is your regression net.
  • Self-documenting APIs + tooling — autocomplete, jump-to-def, IntelliSense all flow from types.

"strict": true turns on the family that makes types honest: strictNullChecks (null/undefined must be handled), noImplicitAny (no silent any), strictFunctionTypes, strictPropertyInitialization, and more. Without strict, TypeScript is mostly decoration.

// tsconfig.json — the non-negotiable core
{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,   // arr[i] is T | undefined — honest!
    "noImplicitOverride": true
  }
}

Type assertion (as) vs casting — what are the risks?

value as T is not a cast — nothing is converted or checked at runtime. It's you overruling the compiler: "trust me, this is a T". If you're wrong, the error surfaces later as an undefined property somewhere far from the lie.

Risks: asserting API responses (res as User validates nothing), and double assertion (x as unknown as T) which can connect any two types.

Prefer, in order: proper typing/generics → type guards / narrowing (runtime-checked) → satisfies (checks without widening) → assertion as the last resort at genuine trust boundaries (DOM lookups, JSON you control).

// legit: you know more than the compiler
const input = document.getElementById('email') as HTMLInputElement;

// dangerous lie: no runtime check whatsoever
const user = (await res.json()) as User;   // prefer: parse with zod

// checked alternative — narrowing:
if (isUser(data)) { data.name }            // custom guard actually verified it

TypeScript private vs JavaScript # private fields?

TS private — a compile-time promise only; erased in the emitted JS, so at runtime the field is a normal property (obj['secret'] works, JSON.stringify includes it).

JS #field — real language-level privacy (ES2022): inaccessible outside the class at runtime, invisible to bracket access, Object.keys and JSON.

Other differences: # is "hard private" even from subclasses; TS also offers protected (no JS equivalent) and parameter properties (constructor(private http: HttpClient) — the Angular idiom).

class A {
  private tsSecret = 1;   // erased -> reachable at runtime
  #jsSecret = 2;          // truly private
}
const a = new A();
(a as any).tsSecret;      // 1 — the 'privacy' was a type error only
// (a as any).#jsSecret   // SyntaxError — # isn't even valid syntax outside