Back to Read More
TypeScriptInterview

TypeScript Interview Questions

Feb 10, 2026

TypeScript has become the standard for building large-scale JavaScript applications. Whether you are preparing for a frontend, backend, or full-stack interview, these 20 commonly asked TypeScript questions will help you solidify your understanding of the language and its type system.

1. What is TypeScript? Why use it over JavaScript?

TypeScript is a statically typed superset of JavaScript developed by Microsoft. It compiles down to plain JavaScript and runs anywhere JavaScript runs. You use it over JavaScript because it catches errors at compile time, provides better IDE support with autocompletion, makes refactoring safer, and improves code readability with explicit types.

TypeScript vs JavaScript
// JavaScript β€” no type safety
function add(a, b) {
  return a + b;
}
add(5, "3"); // "53" β€” no error, silent bug!

// TypeScript β€” catches errors at compile time
function add(a: number, b: number): number {
  return a + b;
}
add(5, "3"); // Error: Argument of type 'string' is not assignable to parameter of type 'number'
add(5, 3);   // 8 β€” correct!

2. What are the basic types in TypeScript?

TypeScript provides several primitive and structural types that form the foundation of its type system: string, number, boolean, null, undefined, void, any, unknown, never, arrays, tuples, and objects.

Basic Types
// Primitives
let name: string = "Alice";
let age: number = 30;
let isActive: boolean = true;

// Arrays
let numbers: number[] = [1, 2, 3];
let names: Array<string> = ["Alice", "Bob"];

// Tuple β€” fixed-length array with specific types
let pair: [string, number] = ["Alice", 30];

// Object
let user: { name: string; age: number } = {
  name: "Alice",
  age: 30,
};

// Void β€” function that returns nothing
function logMessage(msg: string): void {
  console.log(msg);
}

// Null and Undefined
let nothing: null = null;
let notDefined: undefined = undefined;

3. What is the difference between interface and type?

Both interface and type can describe object shapes, but they differ in flexibility. Interfaces support declaration merging and are extendable with extends. Types are more versatile and can represent unions, intersections, primitives, tuples, and mapped types.

Interface vs Type
// Interface β€” describes object shapes, supports merging
interface User {
  name: string;
  age: number;
}

interface User {
  email: string; // Declaration merging: adds to existing User
}

// Extending interfaces
interface Admin extends User {
  role: string;
}

// Type β€” more flexible, supports unions & intersections
type ID = string | number;          // Union β€” not possible with interface
type Coordinates = [number, number]; // Tuple
type Status = "active" | "inactive"; // Literal union

// Type intersection (similar to extends)
type Employee = User & { company: string };

// Both work for object shapes:
interface Dog { breed: string; }
type Cat = { breed: string; };
Rule of thumb: Use interface for object shapes and class contracts. Use type when you need unions, intersections, or more complex type expressions.

4. What are Generics? How to use them?

Generics allow you to write reusable, type-safe code that works with multiple types. Instead of using any, generics preserve type information through parameters like <T>.

Generics
// Without generics β€” loses type information
function identity(value: any): any {
  return value;
}

// With generics β€” preserves type
function identity<T>(value: T): T {
  return value;
}

const num = identity<number>(42);    // type: number
const str = identity("hello");       // type: string (inferred)

// Generic interface
interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

const userResponse: ApiResponse<{ name: string }> = {
  data: { name: "Alice" },
  status: 200,
  message: "OK",
};

// Generic constraints
function getLength<T extends { length: number }>(item: T): number {
  return item.length;
}

getLength("hello");     // 5
getLength([1, 2, 3]);   // 3
getLength(123);          // Error: number doesn't have 'length'

5. What is the difference between any, unknown, and never?

any disables type checking entirely. unknown is the type-safe counterpart of any -- you must narrow the type before using it. never represents values that never occur, such as a function that always throws or an impossible type.

any vs unknown vs never
// any β€” disables ALL type checking (avoid it!)
let a: any = "hello";
a.toFixed();    // No error at compile time, but crashes at runtime!

// unknown β€” safe alternative to any
let b: unknown = "hello";
b.toFixed();    // Error: Object is of type 'unknown'

// Must narrow the type first
if (typeof b === "string") {
  b.toUpperCase(); // OK β€” TypeScript knows it's a string
}

// never β€” represents impossible values
function throwError(msg: string): never {
  throw new Error(msg); // Function never returns
}

// Exhaustive checking with never
type Shape = "circle" | "square";

function getArea(shape: Shape) {
  switch (shape) {
    case "circle": return Math.PI * 10;
    case "square": return 100;
    default:
      const _exhaustive: never = shape; // Error if a case is missing
      return _exhaustive;
  }
}

6. What are Enums?

Enums define a set of named constants. TypeScript supports numeric enums, string enums, and const enums. They help make code more readable by giving meaningful names to sets of values.

Enums
// Numeric enum (default starts at 0)
enum Direction {
  Up,      // 0
  Down,    // 1
  Left,    // 2
  Right,   // 3
}

let move: Direction = Direction.Up;

// String enum
enum Status {
  Active = "ACTIVE",
  Inactive = "INACTIVE",
  Pending = "PENDING",
}

function setStatus(status: Status) {
  console.log(status);
}
setStatus(Status.Active); // "ACTIVE"

// Const enum β€” inlined at compile time (no runtime object)
const enum Color {
  Red = "#FF0000",
  Green = "#00FF00",
  Blue = "#0000FF",
}

let bg = Color.Red; // Compiled to: let bg = "#FF0000"

7. What are Union and Intersection types?

A union type (A | B) means the value can be either type A or type B. An intersection type (A & B) combines multiple types into one -- the value must satisfy all types simultaneously.

Union & Intersection Types
// Union type β€” value can be one of several types
type ID = string | number;

function printId(id: ID) {
  if (typeof id === "string") {
    console.log(id.toUpperCase());
  } else {
    console.log(id.toFixed(2));
  }
}

printId("abc"); // "ABC"
printId(42);    // "42.00"

// Discriminated union β€” powerful pattern matching
type Success = { status: "success"; data: string };
type Error = { status: "error"; message: string };
type Response = Success | Error;

function handleResponse(res: Response) {
  if (res.status === "success") {
    console.log(res.data);     // TypeScript knows it's Success
  } else {
    console.log(res.message);  // TypeScript knows it's Error
  }
}

// Intersection type β€” combines multiple types
type HasName = { name: string };
type HasAge = { age: number };
type Person = HasName & HasAge;

const person: Person = { name: "Alice", age: 30 }; // Must have both

8. What is Type Narrowing / Type Guards?

Type narrowing is the process of refining a broad type to a more specific one within a conditional block. TypeScript uses control flow analysis to narrow types automatically. Common guards include typeof, instanceof, in, and custom type predicates.

Type Guards
// typeof guard
function double(value: string | number) {
  if (typeof value === "string") {
    return value.repeat(2);  // string methods available
  }
  return value * 2;          // number operations available
}

// instanceof guard
class Dog { bark() { return "Woof!"; } }
class Cat { meow() { return "Meow!"; } }

function speak(animal: Dog | Cat) {
  if (animal instanceof Dog) {
    return animal.bark();
  }
  return animal.meow();
}

// "in" operator guard
type Fish = { swim: () => void };
type Bird = { fly: () => void };

function move(animal: Fish | Bird) {
  if ("swim" in animal) {
    animal.swim(); // Fish
  } else {
    animal.fly();  // Bird
  }
}

// Custom type predicate
function isString(value: unknown): value is string {
  return typeof value === "string";
}

function process(val: unknown) {
  if (isString(val)) {
    console.log(val.toUpperCase()); // TypeScript knows val is string
  }
}

9. What are Utility Types (Partial, Required, Pick, Omit, Record)?

TypeScript ships with built-in utility types that transform existing types. These save you from writing repetitive type definitions and make your code more expressive.

Utility Types
interface User {
  name: string;
  email: string;
  age: number;
  role: string;
}

// Partial<T> β€” all properties become optional
type PartialUser = Partial<User>;
// { name?: string; email?: string; age?: number; role?: string }

// Required<T> β€” all properties become required
type RequiredUser = Required<PartialUser>;

// Pick<T, K> β€” select specific properties
type UserPreview = Pick<User, "name" | "email">;
// { name: string; email: string }

// Omit<T, K> β€” exclude specific properties
type UserWithoutRole = Omit<User, "role">;
// { name: string; email: string; age: number }

// Record<K, V> β€” create object type with specific key-value pairs
type UserRoles = Record<string, string[]>;
const roles: UserRoles = {
  admin: ["read", "write", "delete"],
  viewer: ["read"],
};

// Readonly<T> β€” all properties become readonly
type ReadonlyUser = Readonly<User>;
const user: ReadonlyUser = { name: "Alice", email: "a@b.com", age: 30, role: "admin" };
// user.name = "Bob"; // Error: Cannot assign to 'name' because it is a read-only property

10. What is the keyof operator?

The keyof operator takes an object type and produces a union of its keys as string literal types. It is commonly used with generics to create type-safe property access patterns.

keyof Operator
interface User {
  name: string;
  age: number;
  email: string;
}

// keyof creates a union of property names
type UserKeys = keyof User; // "name" | "age" | "email"

// Type-safe property access with generics
function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user: User = { name: "Alice", age: 30, email: "a@b.com" };

const name = getValue(user, "name");  // type: string
const age = getValue(user, "age");    // type: number
getValue(user, "phone");               // Error: "phone" is not in keyof User

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

11. What are Mapped Types?

Mapped types let you create new types by transforming each property of an existing type. They iterate over keys using the in keyword and can modify property attributes like readonly or optionality.

Mapped Types
// Basic mapped type
type Readonly<T> = {
  readonly [K in keyof T]: T[K];
};

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

// Practical example
interface User {
  name: string;
  age: number;
}

type ReadonlyUser = Readonly<User>;
// { readonly name: string; readonly age: number }

// Mapped type with transformation
type Nullable<T> = {
  [K in keyof T]: T[K] | null;
};

type NullableUser = Nullable<User>;
// { name: string | null; age: number | null }

// Mapped type with key remapping (TS 4.1+)
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

type UserGetters = Getters<User>;
// { getName: () => string; getAge: () => number }

12. What are Conditional Types?

Conditional types select one of two types based on a condition, using the syntax T extends U ? X : Y. They enable powerful type-level programming and are the foundation of many advanced type patterns.

Conditional Types
// Basic conditional type
type IsString<T> = T extends string ? "yes" : "no";

type A = IsString<string>;  // "yes"
type B = IsString<number>;  // "no"

// Practical: extract types from complex structures
type ArrayElement<T> = T extends (infer U)[] ? U : T;

type Elem = ArrayElement<string[]>;  // string
type NotArr = ArrayElement<number>;  // number

// Distributive conditional types β€” applied to each member of a union
type NonNullable<T> = T extends null | undefined ? never : T;

type Clean = NonNullable<string | null | undefined>; // string

// Conditional type with multiple conditions
type TypeName<T> =
  T extends string ? "string" :
  T extends number ? "number" :
  T extends boolean ? "boolean" :
  T extends Function ? "function" :
  "object";

type T1 = TypeName<string>;    // "string"
type T2 = TypeName<() => void>; // "function"

13. What is the difference between abstract class and interface?

An abstract class can contain both implemented methods and abstract method signatures. It exists at runtime as a JavaScript class. An interface is purely a compile-time construct with zero runtime footprint -- it only describes a shape.

Abstract Class vs Interface
// Abstract class β€” can have implementations
abstract class Animal {
  constructor(public name: string) {}

  // Implemented method β€” shared by all subclasses
  move(distance: number): void {
    console.log(`${this.name} moved ${distance}m`);
  }

  // Abstract method β€” must be implemented by subclasses
  abstract makeSound(): string;
}

class Dog extends Animal {
  makeSound(): string {
    return "Woof!";
  }
}

// const animal = new Animal("x"); // Error: cannot instantiate abstract class
const dog = new Dog("Rex");
dog.move(10);       // "Rex moved 10m" β€” inherited
dog.makeSound();    // "Woof!" β€” implemented

// Interface β€” no implementation, no runtime cost
interface Serializable {
  serialize(): string;
  deserialize(data: string): void;
}

// A class can implement multiple interfaces
class User extends Animal implements Serializable {
  makeSound() { return "Hello!"; }
  serialize() { return JSON.stringify({ name: this.name }); }
  deserialize(data: string) { this.name = JSON.parse(data).name; }
}

14. What are Decorators?

Decorators are special declarations that can be attached to classes, methods, properties, or parameters. They are functions that modify or annotate the target at design time. Decorators require enabling experimentalDecorators in tsconfig.

Decorators
// Class decorator
function Sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@Sealed
class BugReport {
  title: string;
  constructor(t: string) { this.title = t; }
}

// Method decorator
function Log(target: any, key: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`Calling ${key} with args: ${JSON.stringify(args)}`);
    return original.apply(this, args);
  };
}

class Calculator {
  @Log
  add(a: number, b: number): number {
    return a + b;
  }
}

const calc = new Calculator();
calc.add(2, 3);
// Console: Calling add with args: [2,3]
// Returns: 5

// Property decorator
function MinLength(min: number) {
  return function (target: any, key: string) {
    let value: string;
    Object.defineProperty(target, key, {
      get: () => value,
      set: (v: string) => {
        if (v.length < min) throw new Error(`${key} must be at least ${min} chars`);
        value = v;
      },
    });
  };
}

15. What is Declaration Merging?

Declaration merging is when the TypeScript compiler merges two or more separate declarations with the same name into a single definition. This works with interfaces, namespaces, and enums. It is commonly used to extend third-party library types.

Declaration Merging
// Interface merging β€” same name interfaces are combined
interface Box {
  height: number;
  width: number;
}

interface Box {
  depth: number;
  color: string;
}

// Result: Box has all four properties
const box: Box = { height: 10, width: 20, depth: 30, color: "red" };

// Extending third-party types (e.g., Express)
declare namespace Express {
  interface Request {
    user?: { id: string; role: string };
  }
}

// Now req.user is available in Express handlers

// Enum merging
enum Status {
  Active = 1,
  Inactive = 2,
}

enum Status {
  Pending = 3, // Adds to existing enum
}

const s: Status = Status.Pending; // 3

16. What are Template Literal Types?

Template literal types build on string literal types by using template literal syntax to create new string types through concatenation. They allow you to model string patterns at the type level.

Template Literal Types
// Basic template literal type
type Greeting = `hello ${string}`;
const g: Greeting = "hello world"; // OK
// const g2: Greeting = "hi world"; // Error

// Combining string literal unions
type Color = "red" | "blue";
type Size = "small" | "large";
type Shirt = `${Size}-${Color}`;
// "small-red" | "small-blue" | "large-red" | "large-blue"

// Event handler pattern
type EventName = "click" | "focus" | "blur";
type Handler = `on${Capitalize<EventName>}`;
// "onClick" | "onFocus" | "onBlur"

// CSS unit type
type CSSUnit = "px" | "em" | "rem" | "%";
type CSSValue = `${number}${CSSUnit}`;

const width: CSSValue = "100px";   // OK
const height: CSSValue = "2.5rem"; // OK
// const bad: CSSValue = "100";    // Error

// Intrinsic string manipulation types
type Upper = Uppercase<"hello">;     // "HELLO"
type Lower = Lowercase<"HELLO">;     // "hello"
type Cap = Capitalize<"hello">;      // "Hello"
type Uncap = Uncapitalize<"Hello">;  // "hello"

17. What is the infer keyword?

The infer keyword is used within conditional types to declare a type variable that TypeScript will infer from the context. It lets you extract and reuse parts of complex types.

infer Keyword
// Extract return type of a function
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type Fn = () => string;
type Result = ReturnType<Fn>; // string

// Extract parameter types
type Parameters<T> = T extends (...args: infer P) => any ? P : never;

type Fn2 = (name: string, age: number) => void;
type Params = Parameters<Fn2>; // [string, number]

// Extract element type from array
type ElementOf<T> = T extends (infer E)[] ? E : never;
type Item = ElementOf<string[]>; // string

// Extract Promise value type
type Awaited<T> = T extends Promise<infer V> ? V : T;
type Value = Awaited<Promise<number>>; // number

// Nested inference
type UnpackPromise<T> = T extends Promise<infer U>
  ? U extends Promise<infer V>
    ? V
    : U
  : T;

type Deep = UnpackPromise<Promise<Promise<string>>>; // string

18. What are Namespaces vs Modules?

Modules (ES modules) use import/export and are file-based -- each file is its own module. Namespaces are a TypeScript-specific way to organize code under a named scope. In modern TypeScript, modules are preferred over namespaces.

Namespaces vs Modules
// === Modules (recommended) ===
// math.ts β€” each file is a module
export function add(a: number, b: number): number {
  return a + b;
}

export function subtract(a: number, b: number): number {
  return a - b;
}

// app.ts β€” import what you need
import { add, subtract } from "./math";
console.log(add(2, 3)); // 5

// === Namespaces (legacy, avoid in new code) ===
namespace MathUtils {
  export function add(a: number, b: number): number {
    return a + b;
  }

  export function subtract(a: number, b: number): number {
    return a - b;
  }

  // Not exported β€” private to namespace
  function helper() { return 42; }
}

MathUtils.add(2, 3); // 5
// MathUtils.helper(); // Error: not exported

// Key differences:
// - Modules: file-based, standard ES imports, tree-shakeable
// - Namespaces: global scope, TS-specific, can span multiple files
// - Use modules for all new projects

19. What is strict mode in TypeScript?

Enabling "strict": true in tsconfig.json turns on a family of strict type-checking options including strictNullChecks, noImplicitAny, strictFunctionTypes, and more. It is strongly recommended for all new projects.

Strict Mode
// tsconfig.json
// "strict": true enables ALL of these:
{
  "compilerOptions": {
    "strict": true,
    // Equivalent to enabling all of the following:
    // "noImplicitAny": true,        β€” error on implicit 'any'
    // "strictNullChecks": true,      β€” null/undefined not assignable to other types
    // "strictFunctionTypes": true,   β€” stricter function type checking
    // "strictBindCallApply": true,   β€” strict bind/call/apply
    // "strictPropertyInitialization": true, β€” class properties must be initialized
    // "noImplicitThis": true,        β€” error on 'this' with implicit 'any' type
    // "alwaysStrict": true           β€” emit "use strict" in every file
  }
}

// Without strictNullChecks:
let name: string = null;  // OK (dangerous!)

// With strictNullChecks:
let name: string = null;  // Error!
let name: string | null = null; // OK β€” explicitly nullable

// Without noImplicitAny:
function greet(name) { }  // 'name' is implicitly 'any' β€” OK

// With noImplicitAny:
function greet(name) { }  // Error: Parameter 'name' implicitly has an 'any' type
function greet(name: string) { } // OK β€” explicit type

20. What is the difference between == and === in TypeScript?

This is inherited from JavaScript. == (loose equality) performs type coercion before comparison, while === (strict equality) compares both value and type without coercion. TypeScript encourages using === for predictable comparisons.

== vs ===
// == (loose equality) β€” performs type coercion
console.log(5 == "5");     // true  β€” string "5" coerced to number
console.log(0 == false);   // true  β€” false coerced to 0
console.log("" == false);  // true  β€” both coerced to 0
console.log(null == undefined); // true β€” special coercion rule

// === (strict equality) β€” no type coercion
console.log(5 === "5");     // false β€” different types
console.log(0 === false);   // false β€” number vs boolean
console.log("" === false);  // false β€” string vs boolean
console.log(null === undefined); // false β€” different types

// TypeScript helps catch == mistakes
let x: number = 5;
let y: string = "5";

if (x == y) { }  // TypeScript warning with strict checks
if (x === y) { } // Error: This comparison is always false
                  // (different types can never be ===)

// Best practice: always use === in TypeScript
function isValid(value: string | null): boolean {
  return value !== null && value.length > 0;
}

Summary

  • βœ“TypeScript adds static typing to JavaScript for safer, more maintainable code
  • βœ“Interfaces & Types define object shapes and complex type expressions
  • βœ“Generics enable reusable, type-safe functions and classes
  • βœ“Type Guards narrow broad types for safe property access
  • βœ“Utility Types transform existing types without rewriting them
  • βœ“Mapped & Conditional Types power advanced type-level programming
  • βœ“Strict mode catches more bugs at compile time and is recommended for all projects
  • βœ“Modules over Namespaces align with modern JavaScript standards

Β© 2026 Koeuk KOS. All rights reserved.

Built with Nuxt.js, Vue.js & Tailwind CSS