Top Typescript Interview Questions And Answers (2024)

TypeScript is an open-source programming language developed and maintained by Microsoft. It is a superset of JavaScript, which means that any valid JavaScript code is also valid TypeScript code TypeScript adds static typing, along with additional language features, on top of JavaScript to enhance the development experience

TypeScript and JavaScript are both programming languages used for web development, but there are some key differences between them. Here are a few notable differences with examples:

1. Static Typing: TypeScript is a statically typed superset of JavaScript, which means it supports static typing. In TypeScript, you can declare the types of variables, function parameters, and return types. This allows for better code quality, early error detection, and improved code documentation. Example in TypeScript:

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

const result: number = addNumbers(5, 10);
console.log(result); // Output: 15

Equivalent example in JavaScript:

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

const result = addNumbers(5, 10);
console.log(result); // Output: 15

2. Language Features: TypeScript includes many features that JavaScript does not have, such as classes (ES6 also have classes but without public, protected,private modifiers), interfaces, enums, and modules. These features enable better code organization, encapsulation, and maintainability. Example in TypeScript:

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

class Employee implements Person {
  name: string;
  age: number;
  id: number;

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

  getDetails(): string {
    return `Name: ${this.name}, Age: ${this.age}, ID: ${this.id}`;
  }
}

const employee = new Employee("John Doe", 30, 12345);
console.log(employee.getDetails()); // Output: Name: John Doe, Age: 30, ID: 12345

Equivalent example in JavaScript (without interfaces and classes):

const employee = {
  name: "John Doe",
  age: 30,
  id: 12345,
  getDetails: function() {
    return `Name: ${this.name}, Age: ${this.age}, ID: ${this.id}`;
  }
};

console.log(employee.getDetails()); // Output: Name: John Doe, Age: 30, ID: 12345

3. Tooling and Ecosystem: TypeScript has its own compiler (`tsc`) that transpiles TypeScript code into JavaScript, providing additional checks and optimizations. TypeScript integrates well with popular development tools and has a rich ecosystem of libraries and frameworks. 4. Compatibility: Since TypeScript is a superset of JavaScript, any valid JavaScript code is also valid TypeScript code. This allows you to gradually introduce TypeScript into existing JavaScript projects without needing to rewrite the entire codebase. Conclusion : TypeScript offers advantages in terms of type safety, code organization, and tooling, while JavaScript provides simplicity and broader browser compatibility. The choice between the two depends on the specific project requirements and developer preferences.
There are several benefits to using TypeScript for developing web applications: 1. Type Safety: TypeScript introduces static typing to JavaScript, which helps catch errors at compile time rather than at runtime. This improves code reliability and makes it easier to maintain, especially in larger projects with many developers. 2. Code Readability: By adding type annotations, interfaces, and other language constructs, TypeScript makes code more self-documenting and easier to understand. This is especially useful for team collaboration and code reviews. 3. Tooling Support: The TypeScript compiler provides powerful tooling support, including code completion, syntax highlighting, and error highlighting. This helps developers write code faster and with fewer errors. 4. ECMAScript Compatibility: TypeScript is a superset of JavaScript, so all valid JavaScript code is also valid TypeScript code. This means developers can gradually adopt TypeScript into an existing codebase without rewriting everything from scratch. 5. Scalability: TypeScript was designed with scalability in mind, and includes language constructs like modules, namespaces, and classes that are essential for building large-scale applications. TypeScript also supports gradual typing, which allows developers to incrementally add type annotations to a codebase as it grows. 6. Community Support: TypeScript has a large and active community of developers, which means there are many resources available, including documentation, libraries, and tools. Overall, using TypeScript can help improve code quality, reduce errors, and increase developer productivity, especially in larger projects.
In TypeScript, you can declare a variable using the `let` or `const` keyword, just like in JavaScript. However, TypeScript also allows you to specify the variable type using type annotations. Here are a few examples:

// Declaring a variable with a type annotation
let myNumber: number = 42;

// Declaring a variable without a type annotation
let myString = "Hello, world!";

// Declaring a constant with a type annotation
const PI: number = 3.14;

// Declaring a variable with an array type
let myArray: string[] = ["apple", "banana", "orange"];

// Declaring a variable with an object type
let myObject: { name: string, age: number } = { name: "Alice", age: 30 };

In the first example, we declare a variable `myNumber` of type `number` and assign it the value `42`. In the second example, we declare a variable `myString` and TypeScript infers its type to be `string` based on the value `"Hello, world!"`. In the third example, we declare a constant `PI` of type `number` and assign it the value `3.14`. In the fourth example, we declare a variable `myArray` of type `string[]`, which is an array of strings. We initialize it with an array of string values. In the fifth example, we declare a variable `myObject` with an object type that has two properties: `name` of type `string` and `age` of type `number`. We initialize it with an object that has these properties and their corresponding values. Note that in TypeScript, you can also declare variables using the `var` keyword, but it's generally recommended to use `let` or `const` instead, as they have more predictable scoping rules.
In TypeScript, a union type allows a variable to have more than one possible type. It is defined using the vertical bar `|` to separate the different types that are allowed. For example:

let myVariable: number | string;

This declaration specifies that `myVariable` can be either a `number` or a `string`. This can be useful in situations where a function or method can accept arguments of multiple types. Here's an example of a function that accepts a union type as a parameter:

function printId(id: number | string) {
  console.log(`ID is ${id}`);
}

printId(101); // Output: ID is 101
printId("abc"); // Output: ID is abc

In this example, the `printId` function accepts an argument of type `number | string`, which means it can accept either a `number` or a `string` as its parameter. The function then simply logs the value of the `id` parameter to the console. Union types can be combined with other TypeScript features like type guards and conditional types to write more complex and robust code.
In TypeScript, an interface defines the structure of an object. It specifies the names and types of properties that an object must have. To define an interface, you use the `interface` keyword, followed by the interface name and the property definitions enclosed in braces `{ }`. Here's an example:

interface Person {
  firstName: string;
  lastName: string;
  age: number;
  email?: string;
}

In this example, we define an interface called `Person` that has four properties: `firstName` and `lastName`, both of type `string`, `age` of type `number`, and an optional property `email` of type `string`. To use an interface, you can define an object that conforms to its structure:

let person: Person = {
  firstName: "John",
  lastName: "Doe",
  age: 30,
  email: "john.doe@example.com"
};

Here, we define an object `person` that conforms to the `Person` interface. It has properties `firstName`, `lastName`, `age`, and `email`. If an object doesn't have all the required properties defined in an interface, TypeScript will generate an error:

let person: Person = {
  firstName: "John",
  age: 30 // Error: Property 'lastName' is missing
};

You can also use interfaces as types for function parameters and return values:

function getFullName(person: Person): string {
  return `${person.firstName} ${person.lastName}`;
}

Here, the `getFullName` function takes a parameter of type `Person` and returns a string. Interfaces can also extend other interfaces and define optional and readonly properties, among other things.
In TypeScript, generics allow you to create functions, classes, and interfaces that can work with a variety of types rather than a single specific type. This makes your code more reusable and flexible. To use generics in TypeScript, you use the angle bracket notation `< >` to define a placeholder type that will be replaced with a concrete type when the code is used. Here's an example of a simple generic function that returns the first element of an array:

function getFirst(arr: T[]): T | undefined {
  return arr.length > 0 ? arr[0] : undefined;
}

In this example, the function is defined with the generic type parameter `T`, which is used to declare the type of the array. The function returns the first element of the array, which has the same type as the elements in the array. To use this function, you call it with an array of a specific type:

let numbers: number[] = [1, 2, 3];
let firstNumber = getFirst(numbers);

Here, the `numbers` array has the type `number[]`, so the generic type parameter `T` in the `getFirst` function is inferred as `number`. The return value of the function is `number | undefined`, which means it can be either a number or undefined if the array is empty. You can also explicitly specify the type parameter when calling the function:

let strings: string[] = ["foo", "bar", "baz"];
let firstString = getFirst(strings);

In this case, the generic type parameter `T` is explicitly set to `string`, so the return value of the function is `string | undefined`. Generics can also be used with classes and interfaces to create generic types that can work with a variety of types. For example, here's a simple interface that defines a generic `Box` type:

interface Box {
  value: T;
}

This interface defines a `Box` type that has a single property `value` of type `T`. When you create an instance of `Box`, you can specify the concrete type of `T`:

let numberBox: Box = { value: 42 };
let stringBox: Box = { value: "hello" };

In this example, `numberBox` has the type `Box` and `stringBox` has the type `Box`.
In TypeScript, a namespace is a way to group related code together and prevent naming conflicts. It is similar to a module in that it can contain functions, classes, and other code, but it is primarily used for organizing code that doesn't need to be exported. To use a namespace in TypeScript, you can define it using the `namespace` keyword, followed by the namespace name and a block of code containing the definitions of its members. For example:

namespace MyNamespace {
  export function myFunction() {
    // code here
  }

  export class MyClass {
    // code here
  }
}

In this example, we define a namespace called `MyNamespace` that contains a function called `myFunction` and a class called `MyClass`. We also use the `export` keyword to make these members accessible outside of the namespace. To use the members of a namespace, you can reference them using the namespace name followed by a dot notation. For example:

MyNamespace.myFunction();
const myInstance = new MyNamespace.MyClass();

In this example, we call the `myFunction` function and create an instance of the `MyClass` class, both using the `MyNamespace` prefix. Using namespaces can help organize your code and prevent naming conflicts, especially when working on large projects with multiple developers. However, it is important to use them judiciously and not create too many layers of nesting, which can make the code harder to read and maintain.
In TypeScript, decorators provide a way to add metadata or behavior to classes, methods, properties, and other declarations at design time. Decorators are applied using the `@` symbol followed by the decorator function name, which can be defined separately. To create a custom decorator in TypeScript, you can follow these steps: Step 1: Define the Decorator Function Start by defining a function that will serve as your decorator. This function will receive the target (class, property, or method) and any additional arguments you want to pass to the decorator. The decorator function should return a modified version of the target or perform any desired actions. Here's an example of a decorator function that logs a message before and after invoking a method:

function logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;

  descriptor.value = function(...args: any[]) {
    console.log(`Calling ${propertyKey} with arguments:`, ...args);

    const result = originalMethod.apply(this, args);

    console.log(`Method ${propertyKey} returned:`, result);

    return result;
  };

  return descriptor;
}

Step 2: Apply the Decorator Now you can apply the decorator to a class, property, or method. To apply the decorator, use the `@` symbol followed by the decorator function name. Here's an example of applying the `logMethod` decorator to a class method:

class Example {
  @logMethod
  greet(name: string) {
    return `Hello, ${name}!`;
  }
}

In this example, whenever the `greet` method is called, the decorator will log the method name and its arguments before executing the original method. Afterward, it will log the returned value. Step 3: Use the Decorated Class or Method You can now use the decorated class or its methods as usual. When you invoke the decorated method, the decorator's logic will be executed alongside the original method's logic.

const instance = new Example();
instance.greet('John');

Output:

Calling greet with arguments: John
Method greet returned: Hello, John!

That's it! We have successfully created and applied a custom decorator in TypeScript. Decorators provide a powerful way to add behavior to classes, properties, or methods without modifying their implementation directly. Conclusion: Using decorators can help you write more expressive and modular code in TypeScript, You can apply decorators to various components and have them automatically execute specific code before, after, or around the execution of the target component. This promotes modular and maintainable code architectures
In TypeScript, you can use the `public`, `private`, and `protected` modifiers to control the visibility and accessibility of class members (properties and methods). These modifiers determine whether a member can be accessed from outside the class, or only from within the class or its subclasses. - public: A `public` member can be accessed from anywhere, both within and outside the class. This is the default modifier for class members in TypeScript, so you don't need to specify it explicitly.

class MyClass {
  public myPublicProperty: string;
  public myPublicMethod() {
    // code here
  }
}

const myInstance = new MyClass();
myInstance.myPublicProperty = "value";
myInstance.myPublicMethod();

In this example, the `myPublicProperty` and `myPublicMethod` members are both `public`, so they can be accessed and modified from outside the class. - private: A `private` member can only be accessed from within the class. It cannot be accessed from outside the class or its subclasses.

class MyClass {
  private myPrivateProperty: string;
  private myPrivateMethod() {
    // code here
  }
}

const myInstance = new MyClass();
myInstance.myPrivateProperty = "value"; // ERROR: private property cannot be accessed from outside the class
myInstance.myPrivateMethod(); // ERROR: private method cannot be accessed from outside the class

In this example, the `myPrivateProperty` and `myPrivateMethod` members are both `private`, so they cannot be accessed from outside the class. - protected: A `protected` member can be accessed from within the class or its subclasses. It cannot be accessed from outside the class or its subclasses.

class MyBaseClass {
  protected myProtectedProperty: string;
  protected myProtectedMethod() {
    // code here
  }
}

class MySubClass extends MyBaseClass {
  myMethod() {
    this.myProtectedProperty = "value"; // OK: can access protected property from subclass
    this.myProtectedMethod(); // OK: can access protected method from subclass
  }
}

const myInstance = new MySubClass();
myInstance.myProtectedProperty = "value"; // ERROR: protected property cannot be accessed from outside the class hierarchy
myInstance.myProtectedMethod(); // ERROR: protected method cannot be accessed from outside the class hierarchy

In this example, the `myProtectedProperty` and `myProtectedMethod` members are both `protected`, so they can be accessed from within the `MySubClass` subclass, but not from outside the class hierarchy. Using the `public`, `private`, and `protected` modifiers can help you encapsulate and control access to class members in TypeScript, making your code more modular and maintainable.
In TypeScript, a type assertion is a way to tell the compiler the type of a value, even if the compiler cannot infer it automatically. This can be useful in situations where you know the type of a value, but the compiler does not, such as when working with external libraries or APIs that return values of unknown types. Type assertions are expressed using the `as` keyword, followed by the desired type. There are two forms of type assertions in TypeScript: "angle-bracket" syntax and "as" syntax. - Angle-bracket syntax:

let myValue: any = "Hello, TypeScript!";
let myLength: number = (myValue).length;

In this example, we use angle-bracket syntax to tell the compiler that `myValue` is a string, so we can access its `length` property. The `myLength` variable is assigned the length of the string. - "as" syntax:

let myValue: any = "Hello, TypeScript!";
let myLength: number = (myValue as string).length;

In this example, we use "as" syntax to tell the compiler that `myValue` is a string, so we can access its `length` property. The `myLength` variable is assigned the length of the string. Both forms of type assertions work in a similar way, but the "as" syntax is preferred in TypeScript, as it is more consistent with other language constructs and is easier to read. Type assertions should be used with caution, as they can override the compiler's type checks and lead to runtime errors if used incorrectly. It is generally better to use type annotations and let the compiler infer types wherever possible, to ensure type safety and maintainability of the codebase. However, type assertions can be a useful tool in some situations, such as when working with third-party libraries or legacy code.