100 TypeScript Interview Questions And Answers

TypeScript has become a must-have skill for today’s web and mobile developers. It helps you write safer, clearer, and more maintainable code that actually scales.

This guide brings together 100 essential TypeScript interview questions and answers. It’s designed to help developers strengthen their understanding and get ready for real-world interviews.

You’ll find topics from type systems and interfaces to decorators and generics. This collection makes it easier to review both core and advanced features.

Each question highlights the concepts behind TypeScript’s design. You’ll see how they actually come up in technical interviews and everyday development.

Table of Contents

1. What is TypeScript, and why use it?

TypeScript is a programming language from Microsoft. It extends JavaScript by adding static types, so you can catch errors while coding instead of after you ship.

TypeScript Interview Questions And Answers

Since it compiles down to regular JavaScript, you can run TypeScript anywhere JavaScript works. That’s pretty handy.

Developers use TypeScript to write more reliable and maintainable code. The type system brings better code completion, easier refactoring, and clearer documentation—right in your editor.

Teams especially benefit when projects get big or complex. TypeScript supports modern JavaScript features and adds stuff like interfaces, generics, and enums.

These extras make code clearer and more structured. Here’s a quick peek:

let message: string = "Hello, TypeScript!";
console.log(message);

This simple example shows how TypeScript asks for type definitions. It makes your intent obvious and helps avoid type-related bugs.

2. Explain TypeScript’s type system.

TypeScript uses a static type system. This means you can catch mistakes before you even run your code.

Interview Questions And Answers TypeScript

By defining the types of variables, parameters, and functions, you reduce surprises at runtime. That makes your code easier to maintain and refactor.

The type system covers basics like string, number, and boolean. It also handles complex types: arrays, tuples, and enums.

You can create custom types with interfaces and type aliases. That keeps things clear and consistent across a project.

TypeScript uses type inference, so it figures out types for you when it can. You can also use union, intersection, and generic types for more flexibility.

function addNumbers(a: number, b: number): number {
  return a + b;
}

Here, the compiler checks that both parameters are numbers. It stops you from using the wrong types while you’re coding.

3. Difference between TypeScript and JavaScript

TypeScript is a statically typed superset of JavaScript built by Microsoft. It adds optional type annotations and compiles into plain JavaScript, so browsers can run it.

JavaScript is dynamically typed and interpreted by browsers directly. TypeScript checks types at compile time, so you catch errors early.

JavaScript checks types at runtime, which means some bugs only show up when you run the code. With TypeScript, you can define interfaces, classes, and generics for more predictable, maintainable code.

// TypeScript example
function add(a: number, b: number): number {
  return a + b;
}
// JavaScript example
function add(a, b) {
  return a + b;
}

4. How do interfaces work in TypeScript?

Interfaces in TypeScript define the structure an object should follow. They describe property names, types, and method signatures to keep things consistent.

If a class or object implements an interface, TypeScript checks that all required properties and methods are there. If something’s missing or typed wrong, you’ll get a compiler error.

Interfaces can also extend other interfaces. This lets you build complex types from simpler ones and makes code more modular.

interface Person {
  name: string;
  age: number;
}

const student: Person = {
  name: "Alice",
  age: 20
};

In this example, the student object has to match the Person interface. If it doesn’t, TypeScript will let you know.

5. Explain TypeScript enums with examples.

TypeScript enums give you a way to define a set of named constants. They make code more readable and help you avoid hard-coded values.

100 TypeScript Interview Questions And Answers

Developers often use enums for things like directions or statuses. A numeric enum assigns numbers to each member, either by default or manually.

enum Direction {
  North,
  East,
  South,
  West
}

Here, North is 0, and the rest go up by one. String enums use text values for clarity and safety:

enum Status {
  Pending = "PENDING",
  Success = "SUCCESS",
  Failed = "FAILED"
}

This makes output clearer and debugging easier. You can also use const enums for better performance—they don’t generate extra code at runtime.

6. What are generics in TypeScript?

Generics let you write reusable code that works with different data types. They help functions, classes, and interfaces accept a variety of inputs while keeping type safety.

A generic uses a type variable, usually T, as a placeholder for a specific type. This keeps your code flexible and prevents type errors at compile time.

Here’s a quick example:

function identity<T>(value: T): T {
  return value;
}

let numberResult = identity<number>(100);
let stringResult = identity<string>("Hello");

This function works with both numbers and strings, no need to rewrite the logic. Generics show up all over collections, data utilities, and libraries.

7. How to define and use tuples?

Tuples in TypeScript store a fixed number of elements, each with a specific type. They’re great for representing related but different types of data.

Unlike regular arrays, the length and order of types in a tuple are set at compile time. To define a tuple, use square brackets with types in order:

let user: [string, number] = ["Alice", 25];

The first value must be a string, the second a number. You access elements like arrays, but TypeScript enforces the types.

Tuples come in handy for things like coordinates or key-value pairs. They’re also useful for function return types when you need to return multiple values with specific meanings.

8. Describe TypeScript namespaces.

Namespaces in TypeScript help organize code and avoid naming conflicts. You can group related variables, functions, classes, and interfaces under a shared name.

This is useful in big projects where lots of identifiers might overlap. A namespace creates its own scope, and you access members with dot notation.

If you use the same namespace name in multiple files, TypeScript merges them at compile time. Here’s an example:

namespace Utilities {
  export function logMessage(msg: string) {
    console.log(msg);
  }
}

Utilities.logMessage("Hello, TypeScript!");

The export keyword makes elements accessible outside the namespace. Without it, they stay private. While modules are usually preferred these days, namespaces still work well in non-module projects.

9. How to create type aliases?

A type alias in TypeScript lets you give a name to any type. It makes complex or repeated type definitions easier to read and reuse.

Type aliases don’t create new types—they’re just shorthand for existing ones. You can use them for primitives, objects, unions, or functions.

type User = {
  name: string;
  age: number;
  isAdmin: boolean;
};

type ID = string | number;

Type aliases help keep code clear and maintainable, especially when you’re working with big data shapes or types used in lots of places.

10. What is type inference in TypeScript?

Type inference means TypeScript figures out a variable’s type based on the value you assign. You don’t always have to write type annotations—the compiler fills in the blanks.

For example, if you write let count = 10;, TypeScript knows count is a number. If you try to assign a string later, you’ll get a type error.

Type inference also works for function return types and complex expressions. When a function returns something simple, TypeScript deduces the type from the return value.

It keeps code concise but still type-safe, which is honestly pretty nice.

11. Explain union types with examples.

Union types let a variable hold more than one type of value. They give you flexibility without losing type safety.

You use the pipe symbol | to list possible types. For example:

let id: string | number;
id = "A123";
id = 456;

TypeScript checks that assigned values match one of the declared types. When using a union, you can use type guards like typeof or instanceof to narrow things down before you use them.

function printId(id: string | number) {
  if (typeof id === "string") {
    console.log("ID in text:", id.toUpperCase());
  } else {
    console.log("ID in number:", id.toFixed());
  }
}

This approach keeps things flexible but still under control.

12. What are intersection types?

Intersection types combine multiple types into one. They let a value have all the properties and methods from the combined types.

You use intersection types to create objects that share traits from different sources, like interfaces or classes. It’s handy for modeling complex data structures.

For instance, if you have Student and Teacher interfaces, their intersection ensures an object satisfies both:

interface Student { name: string; grade: number; }
interface Teacher { subject: string; }
type StudentTeacher = Student & Teacher;

const person: StudentTeacher = { name: "Alex", grade: 10, subject: "Math" };

Now, person has to include all the properties from both interfaces. That’s type safety you can count on.

13. How does TypeScript support OOP concepts?

TypeScript brings Object-Oriented Programming (OOP) to the table with classes, interfaces, and inheritance. You can set up class structures that include properties, constructors, and methods.

This makes it easier to organize code into logical, reusable chunks. Encapsulation comes into play through public, private, and protected access modifiers.

These modifiers let you control how class members get accessed or changed, which can really boost code safety. Inheritance lets one class extend another, so you can reuse code and keep maintenance from getting out of hand.

class Person {
  constructor(public name: string) {}
}

class Student extends Person {
  constructor(name: string, public grade: number) {
    super(name);
  }
}

Polymorphism and abstraction show up through interfaces and abstract classes. They help you define consistent structures and behaviors for related objects, while still giving you room to implement things your way.

14. Difference between ‘any’, ‘unknown’, and ‘never’ types.

The any type lets a variable hold literally any value, skipping type checking. It’s flexible, but you lose type safety—so, use it when you really don’t know the type, but beware of potential runtime errors.

let value: any = 5;
value = "text"; // valid

The unknown type is a bit safer. The compiler makes you check the type before using it, so it acts as a signal that you should verify what you’re working with.

let input: unknown = "hello";
if (typeof input === "string") {
  console.log(input.toUpperCase());
}

The never type represents values that never happen. You’ll see it in functions that always throw errors or in code that shouldn’t ever be reached.

function error(message: string): never {
  throw new Error(message);
}

15. Explain decorators and their usage.

Decorators in TypeScript let you attach metadata or tweak behavior for classes, methods, properties, or parameters. They’re just functions that take the thing being decorated as an argument.

People use them to keep code modular and easier to maintain. For example, a class decorator might add or change how a class works.

Method decorators can log calls or do some validation before running the method. Property and parameter decorators can set constraints or inject dependencies.

function Log(target: any, key: string) {
  console.log(`Property decorated: ${key}`);
}

class Example {
  @Log
  name: string = "Test";
}

To use decorators, you need to enable the experimentalDecorators option in your TypeScript config. They’re inspired by features in other languages and come in handy, especially in frameworks like Angular for things like components and services.

16. What are Type Assertions?

Type assertions let you tell the TypeScript compiler the exact type of a value when you know more than it does. They don’t actually change the data or how it runs—they just help the compiler treat the value the way you want.

You can use value as Type or the older <Type>value syntax, but the as style is usually better, especially if you’re working with JSX.

For example:

let input = document.getElementById("username") as HTMLInputElement;
console.log(input.value);

Here, you’re telling TypeScript that input is an HTMLInputElement, so you can safely grab its value property. Type assertions come in handy when you’re dealing with dynamic data or the DOM.

17. How to handle asynchronous code in TypeScript?

To handle asynchronous code in TypeScript, you can use callbacks, promises, or the async/await syntax. These tools help you deal with operations that take time, like API calls or file reads, without freezing the rest of your code.

Callbacks run a function when another finishes, but too many nested callbacks can turn your code into a mess. Promises help by letting you chain operations and handle errors more clearly.

The async/await syntax makes things look more like regular synchronous code, which is a relief. It helps you write code that’s easier to follow, even though it’s still asynchronous under the hood.

async function getData() {
  try {
    const response = await fetch("https://api.example.com/data");
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error("Error fetching data:", error);
  }
}

18. Explain the difference between ‘interface’ and ‘type’

Both interface and type in TypeScript describe the shape of objects and help keep your types straight. They can look similar, but they aren’t identical.

Interfaces support declaration merging, so you can reopen them and add properties in different places or files. Type aliases, on the other hand, can’t be reopened once you’ve defined them.

Interfaces use extends for inheritance, while types build new combos using intersections with &. Types can also handle unions, primitives, and tuples—things interfaces can’t describe.

interface Person {
  name: string;
  age: number;
}

type Employee = Person & { role: string };

Classes can implement both, but which one you pick depends on your needs. Interfaces are great for extending structures; types give you more flexibility for complex combinations.

19. How to use literal types?

Literal types let you set variables to only specific values. That makes your code more predictable and helps catch errors before they happen.

You can use literal types with strings, numbers, or booleans. For instance, you might want a variable that only accepts certain directions:

type Direction = "up" | "down" | "left" | "right";
let move: Direction;
move = "up"; // valid
move = "forward"; // error

Literal types work well with unions and type aliases to describe valid states in your app. They’re also handy with enums or discriminated unions for safer, more readable code.

20. What are mapped types?

Mapped types let you create new types by transforming properties from an existing type. You take each property from a base type and apply a rule or modifier to it.

This makes it easier to reuse code and keep your type definitions in sync. A common example is making all properties optional, required, or read-only.

type User = { name: string; age: number };
type ReadonlyUser = { readonly [K in keyof User]: User[K] };

In this case, ReadonlyUser has the same properties as User, but you can’t change them once set. Mapped types often get used with utility types like Partial<T>, Required<T>, and Readonly<T>.

They help you safely transform objects and keep type accuracy across different situations.

21. Describe conditional types.

Conditional types let you create types based on a condition. The basic idea is to compare one type to another and return one of two options depending on the result.

Here’s the general syntax:

T extends U ? X : Y

If T extends (or matches) U, you get X; otherwise, you get Y.

Conditional types are useful for managing complex type relationships. You’ll often see them with utility types like Exclude, Extract, and NonNullable.

type NonString<T> = T extends string ? never : T;

This example removes all string types from a union. With conditional types, you can build flexible and reusable type structures for advanced TypeScript work.

22. Explain utility types like Partial and Readonly.

TypeScript comes with built-in utility types to help you tweak existing types without starting from scratch. Partial and Readonly are two of the most common.

Partial makes all properties of a type optional. It’s handy when you want to build an object that might only include some fields at first.

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

const updateUser = (data: Partial<User>) => {
  // Only the fields to update are required
};

Readonly does the opposite—it makes all properties immutable. Once you create the object, you can’t change its properties.

const user: Readonly<User> = { name: "Alex", age: 25 };
// user.age = 26; // Error: cannot assign to 'age'

23. How do you configure tsconfig.json?

You set up tsconfig.json to tell the TypeScript compiler how to process your code. It’s a JSON file that lives at the root of your project.

This file controls which files to include, where to put the output, and which language features to support. The compilerOptions section is the heart of it all.

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "strict": true,
    "outDir": "dist"
  },
  "include": ["src"]
}

You can also extend another config file using extends to share common options. That’s super helpful for keeping things consistent in bigger projects or monorepos.

24. Difference between ‘declare’ and ‘export’

The declare and export keywords in TypeScript do pretty different things. declare tells the compiler that something exists elsewhere—maybe in JavaScript code or a third-party library.

declare const process: any;

That example lets TypeScript know about a global variable without actually defining it. export, on the other hand, makes code available for import in other modules.

export interface User {
  name: string;
  age: number;
}

With export, you’re sharing the actual implementation or definition. declare is just for type info, usually in declaration files. export is for splitting your code into reusable pieces.

25. What is the role of declaration files (.d.ts)?

Declaration files (.d.ts) help TypeScript understand the types and structure of JavaScript code that doesn’t have type annotations. They describe how modules, functions, and variables look, but don’t add any runtime code.

These files act as type definitions, so editors and the TypeScript compiler can give you autocompletion, error checking, and better support for JavaScript libraries. They’re especially useful when you want to use third-party libraries written in plain JavaScript.

For example:

declare module "myLibrary" {
  export function greet(name: string): void;
}

This tells TypeScript what the module looks like, even though the real code is just JavaScript. The DefinitelyTyped project maintains a ton of these files for popular libraries across the ecosystem.

26. Explain the concept of module augmentation.

Module augmentation in TypeScript lets you expand existing modules by adding new types, interfaces, or values. It’s handy when you need extra types for third-party libraries that don’t cover your use case.

You can reopen a module’s declaration and enhance it without changing the original code. For example:

declare module "express" {
  interface Request {
    user?: { id: string; role: string };
  }
}

This snippet adds a user property to the Request interface in Express. TypeScript merges your addition with the original during compilation, so you get type-safe access to the new property.

27. How to use enums with string values?

String enums in TypeScript let you assign human-readable string values to enum members instead of numbers. This makes your code clearer and helps avoid confusion when you check output values.

Just give each member a specific string value, like this:

enum Status {
  Idle = "idle",
  Loading = "loading",
  Success = "success",
  Error = "error"
}

You can access these values the same way as numeric enums. For example, Status.Loading gives you "loading". This is especially helpful for logging or debugging since you see the actual text.

String enums work well when you need values to match external data or want readable output in APIs and UIs.

28. Explain the ‘keyof’ operator.

The keyof operator in TypeScript creates a union of the property names of a given object type. It lets you refer to object keys in a type-safe way rather than just using strings, which helps avoid typos and mistakes.

When you use it with a type, like keyof Person, it returns a union of all keys in that type. For example, if Person has name and age, then keyof Person is 'name' | 'age'.

interface Person {
  name: string;
  age: number;
}

type PersonKeys = keyof Person; // 'name' | 'age'

You’ll often see keyof used with generics to build utility functions. It helps functions only accept valid property names, making code safer and more predictable.

29. What is structural typing in TypeScript?

TypeScript uses structural typing, so type compatibility depends on the shape of the data, not just its name. If two types have the same required properties and methods, TypeScript considers them compatible.

This makes code more flexible and reusable. You don’t have to tightly couple type definitions, which is a nice break from nominal typing, where matching relies on explicit declarations.

Here’s an example:

interface Point { x: number; y: number; }
let position = { x: 10, y: 20 };
let p: Point = position; // Valid, same structure

In this case, position fits Point because both have the same structure. Structural typing helps different parts of your code work together while keeping type checks in place.

30. How to extend interfaces?

In TypeScript, you can extend an interface to reuse and build on its structure. This keeps your code organized and consistent.

Use the extends keyword to inherit properties from another interface and add new ones if you want.

interface Person {
  name: string;
  age: number;
}

interface Employee extends Person {
  employeeId: number;
}

Here, Employee gets name and age from Person and adds employeeId. This avoids duplication and keeps shared fields in one place.

Extending interfaces lets you build scalable designs where new types grow from existing ones, and you don’t break old contracts.

31. Describe function overloads in TypeScript.

Function overloads in TypeScript let you define several ways to call the same function. Each overload gives a different combination of parameter types or counts, but you only write one implementation.

This helps TypeScript understand how your function should behave in different scenarios. It makes type checking and autocompletion better, without repeating logic.

function greet(name: string): string;
function greet(name: string, age: number): string;
function greet(name: string, age?: number): string {
  return age ? `Hello, ${name}. You are ${age} years old.` : `Hello, ${name}.`;
}

greet("Emma");
greet("Liam", 25);

Overloading adds clarity and flexibility when working with complex or type-sensitive functions.

32. Explain TypeScript’s strict null checks.

With strict null checks enabled, TypeScript treats null and undefined as separate types. So, a variable declared as a specific type can’t be null or undefined unless you say so in the type definition.

This catches potential null reference errors at compile time, not at runtime. It makes you handle cases where a variable could be null or undefined, which improves reliability.

Check this out:

let name: string = "Alex";
name = null; // Error with strictNullChecks enabled
let age: number | null = null; // Allowed

Strict null checks encourage better handling of optional data and make type intentions much clearer.

33. How do enums transpile to JavaScript?

When TypeScript compiles enums, it turns them into JavaScript objects. Each enum member becomes a property with a value.

Numeric enums also get a reverse mapping so names and numbers reference each other.

enum Direction {
  Up,
  Down,
  Left,
  Right
}

This compiles to something like:

var Direction;
(function (Direction) {
  Direction[Direction["Up"] = 0] = "Up";
  Direction[Direction["Down"] = 1] = "Down";
  Direction[Direction["Left"] = 2] = "Left";
  Direction[Direction["Right"] = 3] = "Right";
})(Direction || (Direction = {}));

String enums compile more simply—they don’t get reverse mappings. Knowing how enums compile helps you understand what runs at runtime and how they’re different from plain objects.

34. What’s the difference between type and interface when it comes to extending?

Both type and interface can build on existing definitions, but they do it differently. An interface uses extends to inherit from another interface.

interface Person {
  name: string;
}

interface Employee extends Person {
  id: number;
}

A type extends other types using intersections. Instead of extends, you combine types with & like this:

type Person = { name: string };
type Employee = Person & { id: number };

Interfaces can’t extend complex types like unions, but type can handle them. So, type is more flexible for mixing shapes, while interface is more easy for structured objects.

35. How does TypeScript handle JSX?

TypeScript supports JSX with a syntax extension, usually for React. You have to enable it by setting "jsx" in your tsconfig.json. Options like "react", "react-jsx", and "preserve" tell TypeScript how to transform JSX during compilation.

Use the .tsx extension for files with JSX so the compiler knows what to do. TypeScript checks both the types and the JSX syntax, ensuring that elements match the correct component types and props.

When compiling, TypeScript turns JSX tags into function calls for React. For example:

const element = <Button label="Click me" />;

TypeScript checks that Button is a valid component and label matches its prop type. This type checking helps catch mistakes before runtime in React apps.

36. What is the ‘never’ type used for?

The never type in TypeScript means values that never happen. You’ll see it with functions that never return, like ones that always throw errors or loop forever.

function fail(message: string): never {
  throw new Error(message);
}

It also helps with exhaustive type checks. If TypeScript sees all cases handled, any leftover gets the never type.

type Shape = { kind: "circle" } | { kind: "square" };

function area(shape: Shape) {
  if (shape.kind === "circle") {
    // handle circle
  } else if (shape.kind === "square") {
    // handle square
  } else {
    const _exhaustiveCheck: never = shape;
  }
}

Using never helps catch logic mistakes at compile time.

37. Explain async/await with TypeScript

TypeScript supports async and await to make writing asynchronous code easier. These keywords let you handle promises in a way that looks more like regular, synchronous code.

An async function always returns a Promise. Inside, await pauses the function until the promise settles. This avoids messy .then() chains and keeps things tidy.

async function fetchData(): Promise<string> {
  const data = await new Promise<string>((resolve) => {
    setTimeout(() => resolve("Data fetched"), 1000);
  });
  return data;
}

fetchData().then((result) => console.log(result));

TypeScript adds type safety to async code. You can specify return types, helping you catch errors before runtime.

38. How to create readonly properties?

Use the readonly modifier in TypeScript to mark properties that shouldn’t change after being set. Once you assign a value, you can’t reassign it. This guards against accidental changes.

You can use readonly properties in classes, interfaces, and type aliases. TypeScript will throw an error if you try to reassign a readonly field at compile time.

class User {
  readonly id: number;
  name: string;

  constructor(id: number, name: string) {
    this.id = id;
    this.name = name;
  }
}

Here, id can’t change after construction, but name can. Using readonly makes your code more reliable and data handling more predictable.

39. Describe TypeScript’s keyof typeof pattern.

The keyof typeof pattern in TypeScript combines two operators to grab property names from an object as a union of string literal types. This lets you reference object keys safely in your types, so you don’t have to repeat them by hand.

The typeof operator turns a value, like an object, into its type. Then, keyof takes that type and produces a union of its keys.

For example:

const colors = {
  primary: "blue",
  secondary: "green",
  accent: "yellow"
};

type ColorKeys = keyof typeof colors;
// "primary" | "secondary" | "accent"

This approach helps TypeScript catch invalid key usage at compile time. It’s handy when you want types that depend on the keys of a constant object.

40. What are declaration merging capabilities?

TypeScript allows you to merge multiple declarations with the same name into a single definition. The compiler combines their properties or members, so you get a unified shape. This is a neat way to extend types or add features without rewriting existing code.

Interface merging is probably the most common example. If you declare two interfaces with the same name, TypeScript merges their members into one.

interface User {
  name: string;
}

interface User {
  age: number;
}

// Resulting type includes both name and age
const person: User = { name: "Alex", age: 25 };

Namespaces can also merge with classes or functions if you declare them after. This flexibility helps organize code across files and keeps type safety strong.

41. How to infer types in conditional types?

TypeScript’s infer keyword lets you extract parts of a type inside a conditional type. You can grab a type variable from within another type and reuse it in your condition. It’s a powerful feature for building flexible utilities.

Here’s a practical example with function types, where you want to infer the return type:

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

type Example = ReturnType<() => number>; // number

With infer R, TypeScript figures out the return type for you. If the type matches, it assigns the result to R. If not, you get never.

This makes type definitions more expressive, allowing you to describe complex relationships without requiring excessive manual work.

42. Explain discriminated unions.

A discriminated union in TypeScript means you have a union of object types that share a common property, called the discriminant. Each variant uses a unique literal value for this property, so TypeScript can tell which type you’re working with. This makes type checking safer and keeps your code organized.

Developers often use discriminated unions to model data that can take several shapes. When you check the discriminant’s value, TypeScript narrows the type automatically for that branch.

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number };

function area(shape: Shape) {
  if (shape.kind === "circle") return Math.PI * shape.radius ** 2;
  if (shape.kind === "square") return shape.side ** 2;
}

This pattern keeps each variant clear and lets the compiler help you avoid mistakes.

43. What is the difference between TypeScript enums and const enums?

A regular enum in TypeScript defines named constants that exist at runtime. The compiler emits an object for the enum, so you can do value lookups and even reverse mapping with numeric enums. That means your compiled JavaScript includes some extra code for the enum itself.

enum Color {
  Red,
  Green,
  Blue
}

A const enum is different—it only exists at compile time. The compiler just replaces usages with the actual values, so you don’t get an object in the output. This can make your code smaller and a bit faster.

const enum Color {
  Red,
  Green,
  Blue
}

People usually pick const enum when they want to avoid runtime overhead. Regular enums are still useful if you need runtime reflection or reverse mapping.

44. How to use utility type Pick?

The Pick utility type lets you make a new type by selecting certain properties from an existing type. It’s perfect when you only care about part of an object’s structure and want your types to reflect that.

Say you have a User type with lots of fields, but you just need a couple for a feature. Pick helps you grab only what you want.

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

type BasicUser = Pick<User, 'id' | 'name'>;

Now, BasicUser just includes id and name. This keeps your types clean and avoids exposing extra data, especially in APIs or UI components.

45. What does the Omit utility type do?

The Omit utility type lets you create a new type by leaving out specific properties from an existing type. It’s a handy way to tweak data models without rewriting them.

You pass in the base type and a list of keys to exclude. The result is a type with the remaining properties.

Here’s how it looks:

type User = { id: number; name: string; password: string };
type PublicUser = Omit<User, 'password'>;

So, PublicUser has id and name, but not password. Omit is great for hiding sensitive info or customizing types for specific situations like API responses or props.

46. How to create custom types with utility types?

TypeScript’s utility types help you build custom types that are flexible and reusable. Helpers like Partial, Pick, and Omit make it easy to transform types without repeating yourself.

You can even combine them to fit your needs. For example, Omit and Partial together let you make certain fields optional while keeping others required.

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

type EditableUser = Partial<Omit<User, "id">>;

This creates a version of User where everything except id is optional. You can keep building on this with mapped or conditional types for more complex needs. It’s a practical way to keep codebases tidy and types consistent.

47. Explain the difference between ‘var’, ‘let’, and ‘const’ in TypeScript.

TypeScript supports var, let, and const for declaring variables, but they behave differently. var gives you function or global scope, and you can re-declare or update it in the same scope.

let is block-scoped, so it’s only available inside the nearest curly braces. You can reassign it, but not re-declare it in the same block.

const is also block-scoped, but you must assign it right away and can’t reassign it later. If you use const with objects or arrays, you can still change their contents, just not the binding itself.

var count = 10;
let total = 5;
const pi = 3.14;

48. How to compile TypeScript code?

You compile TypeScript code with the TypeScript compiler, tsc. It turns .ts files into JavaScript that runs in browsers or Node.js. First, you have to install TypeScript, either globally or in your project.

npm install -g typescript

After installing, just run the compiler command. It’ll check your code and spit out .js files.

tsc app.ts

Most teams add a tsconfig.json to manage compiler options like target version or module type. That way, builds stay consistent and projects are easier to manage.

49. What is the ‘moduleResolution’ compiler option?

The moduleResolution option tells TypeScript how to find and load imported modules. It sets the algorithm for mapping an import path to a file on disk.

TypeScript supports strategies like "classic" and "node". "Classic" matches older TypeScript behavior and works for browser-based projects. "Node" mimics Node.js resolution rules, so it’s good for CommonJS and ECMAScript modules.

There are also newer options like "node16", "nodenext", or "bundler" for modern Node.js and bundling setups.

Example:

{
  "compilerOptions": {
    "moduleResolution": "node"
  }
}

50. Explain the purpose of source maps.

Source maps help you debug TypeScript after it’s compiled to JavaScript. Since the output code doesn’t look like your original TypeScript, source maps keep track of how the lines match up.

When you generate a source map, you get a .map file that links TypeScript lines to JavaScript ones. Browsers and dev tools use this to show you the TypeScript source during debugging.

For example, if you enable source maps in tsconfig.json, the compiler makes both .js and .js.map files:

{
  "compilerOptions": {
    "sourceMap": true
  }
}

This makes tracking down errors in your TypeScript a whole lot easier.

51. Describe how to use TypeScript with React.

Using TypeScript with React adds static typing and helps catch errors before you even run the code. It makes sure your components get the right props and return the right stuff, which boosts reliability.

To use TypeScript in a React project, just create files with the .tsx extension. If you use Create React App, you can set up TypeScript right from the start:

npx create-react-app my-app --template typescript

Define your prop and state types with interfaces or type aliases. Here’s a simple example:

interface GreetingProps {
  name: string;
}

const Greeting: React.FC<GreetingProps> = ({ name }) => {
  return <h1>Hello, {name}</h1>;
};

TypeScript in React also gives you better autocomplete, safer refactoring, and clearer component contracts. It’s honestly hard to go back once you get used to it.

52. How to enable strict mode in TypeScript?

To turn on strict mode in TypeScript, you’ll need to tweak the tsconfig.json file in your project. This mode enables advanced type-checking rules that can catch bugs early on.

Find or create a tsconfig.json in your project’s root, then add this setting:

{
  "compilerOptions": {
    "strict": true
  }
}

Setting "strict": true flips on all strict type-checking options. These cover things like null checks, unused variables, and function parameter checks.

If you want more control, you can enable strict options one by one, such as "noImplicitAny" or "strictNullChecks". This way, you can gradually ramp up strictness without breaking your existing codebase all at once.

53. What are literal inference types?

Literal inference types let TypeScript capture the exact value of a variable during type inference, not just a broad type. When you use const to declare a variable, TypeScript infers its literal value as the type instead of just string or number.

For example:

const color = "red";
type ColorType = typeof color; // "red"

In this snippet, color gets the literal type "red" rather than just string. This helps you lock variables to specific values, boosting type safety.

Literal inference is handy for discriminated unions, config objects, or any situation where you want to limit values to a set of fixed options.

54. Explain the ‘readonly’ modifier in arrays.

The readonly modifier in TypeScript makes an array immutable after it’s created. Once you declare an array as readonly, you can’t push, pop, or change its elements directly.

You’re still able to read elements or use methods like map() or forEach()—just nothing that would change the array.

const numbers: readonly number[] = [1, 2, 3];
// numbers.push(4); // Error: Property 'push' does not exist on type 'readonly number[]'

This helps protect your data from accidental changes. It’s especially useful when you pass arrays between functions or components and want to make sure the original stays untouched.

55. How does TypeScript support optional chaining?

TypeScript supports optional chaining with the ?. operator. This lets you safely access properties or call functions on objects that might be null or undefined without crashing your code.

The operator checks each part of the chain. If it hits null or undefined, it just returns undefined instead of throwing an error.

Here’s a quick example:

const user = { profile: { name: "Emma" } };
const city = user.profile?.address?.city;
console.log(city); // undefined

Optional chaining cuts down on repetitive checks and keeps code tidier. It works not just with objects, but arrays and function calls too—super helpful when dealing with data that might be incomplete.

56. What are branded types?

Branded types in TypeScript help you make stricter distinctions between values that share the same base type. For example, you can prevent mixing up user IDs and product IDs when they’re both just strings underneath.

To create a branded type, you intersect a primitive type with an object literal carrying a unique “brand.” This brand only exists during compile time, so it doesn’t change your JavaScript output at all.

type UserId = string & { __brand: "UserId" };
type ProductId = string & { __brand: "ProductId" };

function getUser(id: UserId) { /* logic */ }

Brands help make sure only the right types get passed into functions. They catch subtle mistakes before runtime and make your code more predictable.

57. What is the difference between type guards and type assertions?

Type guards are runtime checks that help TypeScript figure out what type a value has within a certain block. They narrow down a variable’s type based on conditions you provide.

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

When you call isString(input), TypeScript knows input is a string inside that block.

Type assertions, on the other hand, just tell TypeScript to treat a value as a specific type, regardless of what it actually is. The compiler doesn’t check if the assertion is correct.

const element = document.getElementById("title") as HTMLHeadingElement;

Type guards rely on real checks at runtime. Type assertions rely on your confidence as a developer—sometimes risky, but sometimes necessary.

58. Explain how to implement interface inheritance.

In TypeScript, interfaces can inherit from one or more other interfaces. This lets you organize code and keep shared structures consistent across related types.

You use the extends keyword to create a new interface that pulls in properties and methods from existing ones. For example:

interface Person {
  name: string;
  age: number;
}

interface Employee extends Person {
  employeeId: number;
}

Here, Employee inherits name and age from Person and adds employeeId. Any object implementing Employee needs all these members.

You can also extend multiple interfaces at once. It’s a flexible way to build up complex types without repeating yourself.

59. Describe how to use namespaces vs modules.

Namespaces and modules both organize TypeScript code, but they work differently. Namespaces group related code in a single global scope, while modules split code across files and use imports and exports for boundaries.

Create a namespace with the namespace keyword. Everything inside stays in that namespace unless you export it.

namespace Shapes {
  export function area(width: number, height: number) {
    return width * height;
  }
}

Modules use the ES module system. Each file is a module, and you share code with import and export statements.

// shapes.ts
export function area(width: number, height: number) {
  return width * height;
}

Most modern TypeScript projects lean toward modules—they offer better organization, tooling, and interoperability. Namespaces still pop up in older code or really simple scripts that just need a single global context.

60. How does TypeScript support decorators in classes?

TypeScript supports decorators, which are special functions you can use to modify or enhance classes and their members. You can stick decorators on classes, methods, properties, accessors, or parameters by putting @ before a function name.

When the code runs, a decorator gets info about whatever it’s decorating. That lets it add metadata or tweak behavior. Frameworks like Angular use decorators a lot for things like dependency injection or configuration.

function Log(target: any) {
  console.log(`Class decorated: ${target.name}`);
}

@Log
class ExampleClass {}

In this case, the decorator logs the class name when the class is defined. Don’t forget: you have to enable the experimentalDecorators option in your TypeScript config for this to work.

61. Explain TypeScript’s ‘type predicates’

A type predicate lets a function confirm a variable’s type at runtime. It uses the form parameterName is Type to help TypeScript narrow types inside conditional blocks.

Here’s what that looks like:

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

If this function returns true, TypeScript treats the checked variable as a string in that scope. It’s a clean way to improve type safety and avoid extra casting.

From TypeScript 5.5 onward, the compiler can sometimes infer type predicates automatically by looking at your function body. That means less boilerplate and simpler code for you.

62. What are index signatures?

Index signatures let you define the shape of objects with dynamic property names in TypeScript. They tell the compiler what type each value should have, even if you don’t know the property names ahead of time.

Usually, you’ll use a string as the key. For example:

type Scores = { [key: string]: number };

This means any string property on the object must be a number. Index signatures are super useful for configs, dictionaries, or API results where property names aren’t fixed.

You can also use number or symbol keys if you need. TypeScript checks that every property matches the declared value type, helping you avoid accidental type mismatches.

63. How to handle JSON parsing with TypeScript?

TypeScript handles JSON parsing with the standard JSON.parse() method from JavaScript. This turns a JSON string into a JavaScript object. You can add type checking to make sure the parsed data matches what you expect.

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

const json = '{"id": 1, "name": "Alice"}';
const user: User = JSON.parse(json);

Using interfaces or types helps catch mistakes during development. If a field is missing or the type’s wrong, TypeScript will flag it before you run the code.

With data from unknown sources, it’s smart to validate the parsed object before using it. Type guards or runtime validation libraries like Zod or io-ts can help make sure your data is really what you expect, keeping runtime errors at bay.

64. Explain usage of never type in exhaustive checks.

The never type in TypeScript stands for values that should never happen. Developers use it to make sure all possible cases in a discriminated union or switch are covered.

If you add a new case to a union but forget to handle it, the compiler will catch the missing branch thanks to never.

type Shape = { kind: "circle" } | { kind: "square" };

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI;
    case "square":
      return 1;
    default:
      const exhaustiveCheck: never = shape;
      return exhaustiveCheck;
  }
}

If you add a new shape type later and don’t handle it, TypeScript will give you a compile error until you fix it.

65. What are conditional mapped types?

Conditional mapped types blend mapped types and conditional types to create flexible transformations. You can apply conditions to each property in a mapped type, not just treat them all the same way.

This makes your type structures way more adaptable. For example, you can filter or change properties based on their value type.

type ConditionalMap<T> = {
  [K in keyof T]: T[K] extends string ? string : never;
};

type Example = { name: string; age: number; active: boolean };
type StringProps = ConditionalMap<Example>; 
// Result: { name: string; age: never; active: never }

This code keeps only the properties of type string. Conditional mapped types help you build reusable, type-safe utilities for trickier scenarios.

66. How to use the NonNullable utility type?

The NonNullable utility type in TypeScript strips out null and undefined from a given type. Developers use it to make sure a variable or property can’t hold those values, which helps avoid annoying runtime errors from unexpected nulls.

You can use NonNullable to refine a type definition. For example:

type Example = string | null | undefined;
type NonNullExample = NonNullable<Example>;
// NonNullExample becomes string

This tool narrows union types and clarifies what values a variable can actually have.

It’s especially handy when you’re dealing with optional values or data from external sources. Using NonNullable just makes the code clearer and more reliable, honestly.

67. Explain differences between JavaScript’s duck typing and TypeScript’s static typing

JavaScript relies on duck typing, which checks an object’s behavior at runtime. If an object has the right properties or methods, JavaScript treats it as compatible, no matter what type you say it is. This gives you a lot of flexibility but, let’s be honest, it also means more room for weird runtime errors.

TypeScript adds static typing, so types get checked at compile time. You define types with interfaces or type aliases, and the TypeScript compiler enforces them before you even run the code. This catches type mistakes early—super helpful.

// JavaScript example (duck typing)
function printLabel(item) {
  console.log(item.label);
}

// TypeScript example (static typing)
interface LabeledItem {
  label: string;
}
function printLabelTS(item: LabeledItem) {
  console.log(item.label);
}

TypeScript’s type system is actually structural, so it kind of imitates duck typing, but it checks structure safety while compiling, not while running.

68. How do you manage third-party library types?

Most developers use third-party libraries that might not ship with TypeScript types. If that’s the case, you can grab type definitions from DefinitelyTyped via npm, like this:

npm install --save-dev @types/library-name

If you can’t find official types, you can always write your own declaration file. These files usually have a .d.ts extension and define the types you need for that module.

declare module "library-name" {
  export function customMethod(): void;
}

It’s a good idea to keep type definitions updated for better type safety and IntelliSense. Sometimes maintainers add official types later, so it’s worth checking the docs or community updates.

69. What is the meaning of ‘declare module’?

The declare module statement in TypeScript tells the compiler about a module that exists but doesn’t have its own type definitions. It’s basically a way to help TypeScript “see” JavaScript libraries it can’t type-check by default.

You use it when you import a library that doesn’t come with a .d.ts file. With declare module, you can describe the functions, classes, or variables that the library exposes.

declare module "my-lib" {
  export function greet(name: string): void;
}

This example lets TypeScript know that my-lib exports a greet function. It helps with type safety, autocompletion, and avoids compile-time errors when using third-party packages.

70. Explain the concept of ambient declarations.

Ambient declarations in TypeScript describe code that exists outside the current file—usually from external JavaScript libraries or global variables. They tell the TypeScript compiler about the shape and types of these external things, so it can check your code without seeing the actual implementations.

Use the declare keyword for this. You can define variables, functions, classes, or modules that TypeScript assumes exist at runtime, even though they’re not in your current code.

For example:

declare function doSomething(value: string): void;
declare const apiUrl: string;

Here, TypeScript knows about doSomething and apiUrl even though their code isn’t shown. You’ll usually see ambient declarations in .d.ts files for third-party libraries.

71. How to Safely Use Third-Party JavaScript Libraries in TypeScript

You can use third-party JavaScript libraries in TypeScript by installing both the library and its type definitions. Lots of popular packages include their own types, but sometimes you need to grab them separately with @types.

npm install lodash
npm install --save-dev @types/lodash

Type declarations help TypeScript understand what the library looks like and what types it expects. That means better type checking and fewer surprises at runtime.

If you can’t find type definitions, you can create your own .d.ts files to declare minimal types or interfaces. Sometimes, type assertions can help if you’re not sure about a type.

Testing the integration is important. Double-check that imported functions work as expected and match your project’s type standards.

72. How to define tuple types with optional elements?

In TypeScript, tuples let you define arrays with fixed types and specific positions. Sometimes you want some elements to be optional, and you can do this by adding a question mark ? after the element type.

Here’s an example:

type UserTuple = [string, number?, boolean?];

This type works for ["Alice"], ["Alice", 25], or ["Alice", 25, true]. Optional elements give you flexibility while keeping type safety.

If a tuple element is optional, TypeScript treats its type as T | undefined when you access it by index. The tuple’s length becomes a union of possible lengths, so you can have different valid combinations without breaking things.

73. Explain the purpose of the ‘export =’ syntax

The export = syntax in TypeScript lets a module export a single object, class, or function as its main value. You’ll see this a lot with modules written in the CommonJS style, like older Node.js code.

If a module uses export =, you have to import it with import = require(). That keeps it compatible with non-ES module systems.

// myModule.ts
class Logger {
  log(message: string) {
    console.log(message);
  }
}
export = Logger;

// app.ts
import Logger = require('./myModule');
const logger = new Logger();
logger.log('Hello');

This syntax is super helpful for working with legacy JavaScript modules, but still getting the benefits of TypeScript’s type checking.

74. How to create a readonly tuple?

A readonly tuple in TypeScript prevents its elements from being changed after you create it. This is great for keeping things immutable and making sure your data structure stays the same throughout your code.

To make one, just put the readonly keyword before the tuple type. For example:

const userInfo: readonly [string, number] = ["Alice", 30];

Here, userInfo is a tuple with a string and a number. TypeScript will complain if you try to modify any element.

Readonly tuples are perfect when you want data to stay consistent, like for fixed coordinate pairs or configuration values that shouldn’t change. They help prevent accidental changes but you can still access each element normally.

75. How to use the ‘infer’ keyword in conditional types?

The infer keyword in TypeScript works inside a conditional type to grab a type for later use. It helps you extract specific parts of a type, which is super handy for complex type logic or utility types.

With infer, you introduce a type variable that TypeScript figures out for you. For example:

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

If T is a function, TypeScript infers the return type as R. If not, the type is never.

This lets the compiler analyze a type’s structure and pull out info without you having to specify everything. It really cleans up advanced type utilities.

76. Describe how TypeScript infers the return type of functions

TypeScript figures out a function’s return type by looking at its body and what it returns. If you don’t write an explicit return type, the compiler checks each return statement and tries to pick the best type. This helps catch mistakes early.

Say you write a function that adds two numbers—TypeScript infers the return type as number:

function add(a: number, b: number) {
  return a + b; // inferred as number
}

TypeScript does this for more complex cases too, like with generic functions. It looks at the relationship between parameters and return values to figure out the type.

function identity<T>(value: T) {
  return value; // inferred as type T
}

This automatic inference keeps your code type-safe and saves you from writing out repetitive annotations.

77. What’s the use of the Exclude utility type?

The Exclude utility type in TypeScript creates a new type by removing specific members from a union type. It takes two type parameters—T for the original union and U for what you want to exclude. The result is just the types from T that aren’t assignable to U.

It’s useful when you want to refine types for functions or variables. For example, you can filter out unwanted options but still keep type safety.

type Status = "active" | "inactive" | "archived";
type VisibleStatus = Exclude<Status, "archived">;
// VisibleStatus is "active" | "inactive"

Using Exclude lets you control allowed values without rewriting old type definitions. It just makes maintenance easier.

78. Explain pattern matching with TypeScript types.

Pattern matching in TypeScript lets you handle different types or values with clear, type-safe logic. TypeScript doesn’t have true pattern matching like some functional languages, but you can get close by narrowing types and using switch statements.

Usually, you check the type or shape of an object. Type guards like typeof, instanceof, or custom checks help the compiler figure out the right type for each case.

type Shape = { kind: "circle"; radius: number } | { kind: "square"; side: number };

function getArea(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "square":
      return shape.side ** 2;
  }
}

This approach keeps code readable and predictable, and you don’t need unnecessary type assertions.

79. Describe how to create union of string literal types.

A union of string literal types lets you restrict a variable to specific string values. It defines a type that can only be one of several constant string options—not just any string.

In TypeScript, you do this by listing possible strings separated by the union operator (|). For example:

type Color = "red" | "green" | "blue";

This type only allows those three exact strings. If you try to assign something else, you’ll get a compile-time error.

Developers use this trick to improve type safety and stop invalid values from sneaking in. Editors also give better autocomplete since the valid options are known.

You can generate literal unions from enums using keyof typeof, which is nice for keeping things linked and maintainable when working with fixed sets of string values.

80. What are the limitations of TypeScript?

TypeScript brings type safety and better tooling, but it still runs on JavaScript at runtime. The compiled output can’t actually prevent logical or runtime errors after transpilation.

So, you’ll still need to test your code thoroughly. There’s also an extra build step, which can slow down smaller projects or add setup headaches.

If your team isn’t used to static typing, expect some learning pains. The configuration files and compiler options can be a bit much for beginners, honestly.

TypeScript definitions for third-party libraries aren’t always complete or up-to-date. That can throw errors or just skip certain type checks.

Sometimes, you’ll end up writing custom declaration files to patch things up.

interface User {
  id: number;
  name: string;
}
const user: User = { id: 1, name: "Alex" }; // Still runs as JavaScript after compilation

81. How to annotate function parameters and return types?

In TypeScript, you can annotate both a function’s parameters and its return type. This makes code easier to follow and maintain.

It also helps the compiler catch mistakes before you even run the code. Here’s a simple example:

function add(a: number, b: number): number {
  return a + b;
}

Both a and b are numbers, and the function returns a number.

You can also use a function type annotation when assigning a function to a variable.

let greet: (name: string) => string;
greet = function (name: string) {
  return `Hello, ${name}`;
};

82. How to catch type errors early in development?

TypeScript helps you spot type errors before your code even runs. The compiler checks variable types, function parameters, and return values to make sure they match what you expect.

Turning on strict mode in tsconfig.json makes the type checks even tougher. Features like strictNullChecks and noImplicitAny help keep your code safer.

// Example: catching a type error early
function add(a: number, b: number): number {
  return a + b;
}

add('5', 10); // Error: Argument of type 'string' is not assignable to parameter of type 'number'

Using an IDE with TypeScript support is a huge help, too. It highlights type issues as you write, so you can fix them right away.

83. Explain how to work with enums and objects interchangeably.

TypeScript enums define a set of named constants. Sometimes, you’ll want to convert enums to objects or just use objects instead for more flexibility.

This is handy when you need to serialize data to JSON or use it in dynamic structures. A common trick is to map enum values to an object so you can reference both forms.

enum Status {
  Pending = "PENDING",
  Approved = "APPROVED",
  Rejected = "REJECTED"
}

const StatusObject = { ...Status };

The spread operator gives you a plain object copy of the enum. You can access properties dynamically, like StatusObject["Approved"].

If you want to mimic enums with objects, you can add as const to lock in literal types.

const Roles = { Admin: "ADMIN", User: "USER" } as const;
type Roles = typeof Roles[keyof typeof Roles];

84. Describe the TypeScript type checker workflow

The TypeScript type checker scans your code to make sure types are used correctly before compiling to JavaScript. It looks through each file, gathers type info, and builds an internal type graph.

This helps it understand how everything fits together. Then, it checks that type assignments and operations follow the rules.

For example, it won’t let you use a string where a number is expected. The type checker also uses declaration files (.d.ts) to bring in types from outside libraries.

let value: number = "42"; // Type error: string is not assignable to number

85. What is the difference between ‘static’ and ‘instance’ members in classes?

In TypeScript, static members belong to the class itself, not any particular object. They’re great for things like counters or utility methods shared by all instances.

You access static members using the class name, not an instance. Instance members, though, belong to each object you create from the class.

Each instance gets its own copy, so their values can be different. You access these through the object’s reference.

class Example {
  static count = 0;
  name: string;

  constructor(name: string) {
    this.name = name;
    Example.count++;
  }
}

const a = new Example("Alex");
const b = new Example("Jordan");
console.log(Example.count); // 2

Here, count is shared by all instances, but each object has its own name.

86. Explain the ‘as const’ assertion.

The ‘as const’ assertion in TypeScript tells the compiler to treat a value as a constant literal. It keeps the type from being widened.

For example, a string "blue" would normally just be typed as string, but with as const, it stays as the literal type "blue".

This also makes object properties and array elements readonly, so you can’t change them after initialization. It’s useful for stricter immutability.

const colors = ["red", "green", "blue"] as const;
// colors[0] = "yellow"; // Error: cannot modify a readonly array

People use as const to keep type safety, especially with config objects or fixed lists. It makes sure TypeScript keeps the exact literal values.

87. How to create a recursive type alias?

A recursive type alias in TypeScript lets a type refer to itself. This is perfect for modeling nested structures like trees, JSON, or even linked lists.

To do this, just include a reference to the type within its own definition. TypeScript supports this with type aliases, which are pretty flexible.

type TreeNode = {
  value: number;
  children?: TreeNode[];
};

Here, each node has a value and can have a list of child nodes of the same type. TypeScript keeps the typing strict all the way down.

88. Describe how TypeScript supports mixins.

TypeScript lets you use mixins to combine multiple classes into one. This is great for sharing functionality without classic inheritance.

You can reuse code when classes need similar methods or properties. Usually, a mixin uses a function that takes base classes and returns a new class with combined behavior.

class CanFly {
  fly() {
    console.log("Flying");
  }
}

class CanSwim {
  swim() {
    console.log("Swimming");
  }
}

type Constructor<T = {}> = new (...args: any[]) => T;

function mixin<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    move() {
      console.log("Moving");
    }
  };
}

89. What is the use of ‘never’ in exhaustive switch statements?

The never type in TypeScript switch statements helps make sure you handle every possible case of a union type. This keeps you from missing branches that could cause runtime bugs.

If you add a new case to the union type later, TypeScript will flag it if you forget to update the switch. That’s pretty handy for catching mistakes early.

type Shape = "circle" | "square" | "triangle";

function getArea(shape: Shape) {
  switch (shape) {
    case "circle":
      return "area of circle";
    case "square":
      return "area of square";
    case "triangle":
      return "area of triangle";
    default:
      const _exhaustive: never = shape; // ensures all cases handled
      return _exhaustive;
  }
}

90. Explain using TypeScript generics with constraints

TypeScript generics let you write flexible, type-safe code. Constraints make sure only certain types can be used with a generic, so things stay consistent.

By using the extends keyword, you can limit a generic type parameter. For instance, a function can require that a type has a certain property.

function getId<T extends { id: string }>(item: T): string {
  return item.id;
}

Here, T must have an id property of type string. This keeps the function working only with compatible objects.

91. How to override types in declaration files?

You can override or extend TypeScript declarations by creating a new .d.ts file with the same module name. This is useful when a library’s types are incomplete or just wrong.

Use the declare module statement to say which module you’re extending. Inside, only define the new or fixed parts you need.

// custom-validate.d.ts
declare module "validate.js" {
  interface ValidateOptions {
    customRule?: boolean;
  }
}

Adding a property like this expands the type. Put this file in your project’s type root or set its path in tsconfig.json so the compiler picks it up.

92. What is the ‘this’ parameter in functions?

In TypeScript, you can explicitly declare the type of a function’s this value. This parameter goes first in the function’s parameter list, but it’s not a real argument you pass in.

This helps describe how this should work inside the function, which is especially helpful for callbacks or when assigning the function to different objects.

function showName(this: { name: string }) {
  console.log(this.name);
}

Here, the this parameter makes sure any object calling showName has a name property. TypeScript ignores this parameter in the compiled JavaScript, but it still checks it during development.

93. How to use conditional types to extract types?

In TypeScript, you can use conditional types to create types that depend on a condition. The base syntax looks like T extends U ? X : Y.

If T matches U, TypeScript picks X. Otherwise, it goes with Y.

Conditional types can pull out subtypes or properties from more complex types. The infer keyword comes in handy here, letting the compiler guess a type variable inside a conditional.

For example:

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

Here, TypeScript extracts the return type R from any function type you pass to ReturnTypeOf. If T isn’t a function, it just returns never.

94. Describe how to create factory functions with TypeScript

A factory function in TypeScript is just a regular function that returns a new object. It’s a nice alternative to classes and can make your code easier to test and adapt.

You can set the factory’s return type so TypeScript checks the structure for you. This way, both the creator and the caller know what to expect.

type User = { name: string; age: number };

function createUser(name: string, age: number): User {
  return { name, age };
}

const user = createUser("Alice", 25);

Factory functions can return different objects depending on the input or some configuration. They’re great for keeping object creation consistent throughout your app.

95. What are template literal types?

Template literal types in TypeScript let you build new string literal types by combining existing ones. They look a lot like JavaScript template strings, but they work at the type level.

This lets you define exact string patterns for your variables. It’s surprisingly useful for enforcing consistent naming or formatting rules.

type EventName = `on${Capitalize<string>}`;
type ButtonEvent = `on${"Click" | "Hover"}`;

So, ButtonEvent only allows "onClick" or "onHover". That helps catch typos and keeps your codebase tidy, especially when you need predictable string formats.

96. Explain type widening and type narrowing

Type widening happens when TypeScript gives a variable a broader type than its starting value. For example, if you write let x = null, TypeScript might let you assign anything to x later.

let value = "hello"; // inferred type: string
value = "world";     // still string, no widening
let result = null;   // widened to any

Type narrowing does the opposite. It shrinks the set of possible types, often using checks like typeof, instanceof, or property checks.

This helps TypeScript figure out which type is safe to use right now.

function printId(id: string | number) {
  if (typeof id === "string") {
    console.log(id.toUpperCase()); // narrowed to string
  }
}

97. How to create complex types with intersection and union?

You can combine types in TypeScript using union (|) and intersection (&) operators. A union lets a value be one of several types, which is handy for flexible input.

type Result = string | number;
let value: Result = "Success";
value = 200;

An intersection merges several types into one, so an object has to meet all the type requirements. This gives you stronger type safety.

type Person = { name: string };
type Employee = { id: number };
type Staff = Person & Employee;

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

Unions and intersections let you describe complex data more clearly. They help reduce mistakes when you mix and match different type sources.

98. Explain how to prevent object mutation with Readonly and const

TypeScript gives you two main ways to avoid unwanted changes: the readonly modifier and the const keyword. They work at different levels to help keep your data safe.

The readonly modifier stops you from reassigning properties in objects, classes, or interfaces—at least at compile time. Once you set a value, you can’t change it directly.

interface User {
  readonly id: number;
  name: string;
}
const user: User = { id: 1, name: "Sam" };
user.name = "Alex"; // valid
user.id = 2; // Error

The const keyword keeps you from reassigning the variable itself. It doesn’t make the object truly immutable, but if you mix const with readonly, both the variable and its properties get some extra protection.

99. Describe how to work with optional chaining and nullish coalescing in TypeScript.

Optional chaining (?.) lets you safely access properties that might not exist. Instead of throwing an error when you hit null or undefined, it just returns undefined and moves on.

This makes working with nested objects a lot less painful. Nullish coalescing (??) helps you set default values, but only when the variable is actually null or undefined.

Unlike ||, it doesn’t treat 0 or an empty string as false. That’s pretty useful if you want to keep those values around.

const user = { profile: { name: "Anna" } };
const userName = user?.profile?.name ?? "Guest";

Here, optional chaining checks if profile and name exist. If either is missing, nullish coalescing sets userName to “Guest” instead of blowing up.

100. What is the difference between structural and nominal typing?

Structural typing checks if types are compatible based on their shape. If two objects have the same properties with the same types, TypeScript says they’re compatible, even if you declared them separately.

This model gives you the flexibility to combine and reuse objects. Nominal typing, which you see in Java or C++, cares about explicit type names or inheritance.

Here’s a quick TypeScript example showing structural typing:

interface Point { x: number; y: number; }
let coord = { x: 10, y: 20 };
let p: Point = coord; // valid because coord matches Point’s structure

So, TypeScript cares more about structure than type identity. That makes its type system pretty adaptable, honestly.

Conclusion

This collection of 100 TypeScript interview questions and answers can really help candidates strengthen their technical understanding. You’ll also get a chance to sharpen your communication skills along the way.

The questions reinforce key concepts like types, generics, interfaces, and decorators. Practical examples are sprinkled in, echoing what you might face in real-world scenarios.

If you go through each topic, you can spot areas where you need a little more practice. It’s also easier to see how TypeScript improves JavaScript development with static typing and better tooling support; honestly, that alone makes it worth learning.

When you’re prepping for interviews, it pays off to write quick code snippets like:

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

const greetUser = (user: User): string => `Hello, ${user.name}`;

Exercises like this build confidence. They also show off your problem-solving chops.

Focus AreaPurpose
Syntax and TypesBuild a foundation for safe and readable code
Advanced FeaturesUse decorators, generics, and modules effectively
Practical ExamplesApply theory to realistic coding tasks

Honestly, a structured study approach, mixed with regular practice, goes a long way. TypeScript’s a valuable tool for modern development, and thoughtful prep can make interviews a lot less intimidating.

You may also read:

51 Python Programs

51 PYTHON PROGRAMS PDF FREE

Download a FREE PDF (112 Pages) Containing 51 Useful Python Programs.

pyython developer roadmap

Aspiring to be a Python developer?

Download a FREE PDF on how to become a Python developer.

Let’s be friends

Be the first to know about sales and special discounts.