TypeScript Interview Questions

Last Updated: Nov 10, 2023

Table Of Contents

TypeScript Interview Questions For Freshers

What is a module in TypeScript?

Summary:

In TypeScript, a module is a way of organizing code into separate files and namespaces to enhance modularity and reusability. It allows you to write reusable and maintainable code by encapsulating related functionality within a module and exposing only the necessary parts to other parts of the application.

Detailed Answer:

A module in TypeScript

In TypeScript, a module is a container for a set of related code. It allows developers to organize their code into separate files and make it more manageable and reusable. Modules provide a way to encapsulate code, ensuring that variables, functions, and classes defined in one module do not conflict with those defined in another module.

By default, every TypeScript file is considered a module, meaning that its contents are hidden from other files unless explicitly exported. This helps maintain the principle of encapsulation and modularity, allowing developers to control the visibility and accessibility of their code.

Modules in TypeScript provide various benefits:

  • Encapsulation: Modules allow developers to hide implementation details and only expose necessary functionalities to the outside world. This helps in creating more maintainable and easier to understand codebases.
  • Code Reusability: Modules enable developers to reuse code by importing and using functionalities defined in one module across multiple other modules. This promotes cleaner and more DRY (Don't Repeat Yourself) code.
  • Dependency Management: When working with large projects, modules help manage dependencies between different parts of the codebase. By importing and exporting various functionalities, modules make it easier to understand and resolve dependencies.

In TypeScript, a module can be defined using the export keyword to indicate which entities (variables, functions, classes, etc.) are accessible outside the module. Other modules can then import these exported entities using the import keyword.

// sample-module.ts
export const PI = 3.14;

export function calculateArea(radius: number): number {
  return PI * radius * radius;
}

export class Circle {
  private radius: number;

  constructor(radius: number) {
    this.radius = radius;
  }

  getArea(): number {
    return calculateArea(this.radius);
  }
}

// main.ts
import { PI, calculateArea, Circle } from './sample-module';

console.log(PI); // 3.14

console.log(calculateArea(5)); // 78.5

const circle = new Circle(5);
console.log(circle.getArea()); // 78.5

What are the benefits of using TypeScript?

Summary:

Benefits of using TypeScript include: 1. Type Safety: TypeScript detects and prevents potential errors at compile-time, reducing the chances of runtime crashes. 2. Enhanced Tooling: TypeScript provides rich IDE support and integrated development tools for code navigation, refactoring, and autocompletion. 3. Improved Maintainability: TypeScript's strong type checking and static analysis help in catching errors early and making code more maintainable. 4. Increased Productivity: TypeScript's features like type inference and improved code readability help developers write code faster and with fewer bugs. 5. Easy Integration: TypeScript is a superset of JavaScript, so it can seamlessly integrate with existing JavaScript codebases and libraries. 6. Better Collaboration: TypeScript's static typing enables better collaboration among developers, as it provides clearer contracts and prevents common mistakes.

Detailed Answer:

Benefits of using TypeScript:

TypeScript is a statically typed superset of JavaScript that offers several benefits for developers. Some of the key advantages of using TypeScript include:

  • Stronger type checking: TypeScript introduces static typing, allowing developers to detect and fix errors at compile-time rather than at runtime. This helps catch common errors such as typos, incorrect method invocations, and variable misuse, improving code quality and reducing bugs.
  • Enhanced tooling and IDE support: TypeScript is supported by modern IDEs and provides better autocompletion, code navigation, and error-checking features. It enhances code refactoring capabilities and enables developers to be more productive by offering a more guided and intuitive development experience.
  • Better maintainability and scalability: TypeScript supports features such as interfaces, classes, modules, and generics, which promote code reuse, modularity, and separation of concerns. This makes code easier to read, understand, and maintain, especially in large codebases, leading to improved scalability and collaboration among developers.
  • Improved developer productivity: TypeScript speeds up development by enabling developers to leverage modern JavaScript features, such as arrow functions, destructuring, and async/await, even when targeting older JavaScript environments. It also provides a rich set of predefined types and utilities, reducing the need to write boilerplate code and making development more efficient.
  • Enhanced code stability: TypeScript supports features like optional static typing, type inference, and type annotations, which provide additional checks for code correctness. This helps reduce runtime errors, improves code stability, and ensures more robust applications.
  • Compatibility with JavaScript ecosystem: TypeScript is a superset of JavaScript, which means that existing JavaScript code is valid TypeScript code. It allows developers to incrementally adopt TypeScript in their projects and leverage the vast JavaScript ecosystem, including libraries, frameworks, and tools.
    const fullName = (firstName: string, lastName: string) => {
        return firstName + ' ' + lastName;
    };

    console.log(fullName('John', 'Doe')); // Output: John Doe

Explain the difference between TypeScript and JavaScript.

Summary:

TypeScript is a superset of JavaScript, meaning it adds additional features to the JavaScript language. It introduces static typing, which allows developers to catch errors during development rather than runtime. TypeScript is then transpiled into JavaScript before being executed in a browser or server.

Detailed Answer:

Difference between TypeScript and JavaScript

TypeScript and JavaScript are both programming languages used for developing web applications, but they have distinct differences. TypeScript is a superset of JavaScript, which means that any valid JavaScript code is also valid TypeScript code.

The main difference between TypeScript and JavaScript lies in their type systems. JavaScript is dynamically typed, meaning that variables can hold values of any type without prior declaration. On the other hand, TypeScript is statically typed, which requires explicit declaration of variable types. This allows TypeScript to catch potential errors during development, resulting in more robust and reliable code.

TypeScript provides additional features and enhancements compared to JavaScript:

  • Static Typing: TypeScript introduces static typing, allowing developers to specify the type of variables, function parameters, and return values. This enables better code organization, improves readability, and catches type-related errors during compile-time.
  • Object-Oriented Programming: TypeScript supports object-oriented programming concepts such as classes, interfaces, and inheritance. It enforces strong typing for class members and provides support for access modifiers like public, private, and protected.
  • ES6+ Support: TypeScript extends the features of JavaScript to include support for newer ECMAScript versions. This means that TypeScript can compile down to older versions that are compatible with all browsers.
  • Compiler: TypeScript requires a compilation step before execution. The TypeScript compiler (tsc) transpiles TypeScript code into plain JavaScript code. This allows developers to write modern code using TypeScript's features and have it transpiled into JavaScript that can be executed by all browsers.
  • Type Inference: TypeScript's compiler can often infer the types of variables and function returns if they are not explicitly declared. This reduces the need for excessive type annotations.
// Example TypeScript code
function addNumbers(a: number, b: number): number {
  return a + b;
}

let num1: number = 5;
let num2: number = 10;
let result: number = addNumbers(num1, num2);

console.log(result); // Output: 15

// Example JavaScript code
function addNumbers(a, b) {
  return a + b;
}

let num1 = 5;
let num2 = 10;
let result = addNumbers(num1, num2);

console.log(result); // Output: 15

In summary, TypeScript is an extension of JavaScript that adds static typing, additional features, and a compilation step. It provides enhanced development experience, better code organization, and improved runtime error handling compared to JavaScript.

What are the key features of TypeScript?

Summary:

The key features of TypeScript include: 1. Strong typing: TypeScript introduces static typing to JavaScript, allowing developers to catch errors early and improve code quality. 2. Object-oriented programming: TypeScript supports classes, interfaces, and inheritance, making it easier to write and maintain complex code. 3. ES6+ compatibility: TypeScript is a superset of JavaScript, meaning it supports all the latest ES6+ features and syntax. 4. Tooling and autocompletion: TypeScript provides better tooling and autocompletion support, making it easier for developers to write code faster and with fewer errors. 5. Enhanced error detection: TypeScript performs additional checks during compilation, catching potential errors and providing useful error messages. 6. Better scalability: With TypeScript, large codebases become more manageable, as the language provides features like modules and namespaces. 7. Improved code readability: TypeScript allows developers to write more expressive code, thanks to features like type annotations and interfaces.

Detailed Answer:

TypeScript is a superset of JavaScript that adds static typing and other features to enhance the development experience. Here are some of the key features of TypeScript:

  1. Static Typing: TypeScript introduces static typing, which allows developers to define the types of variables, function parameters, and return values. This helps catch errors during development and enables better code comprehension and tool support.
  2. Optional Parameters and Default Values: TypeScript allows developers to define optional function parameters by appending a question mark to their names, and default values can be assigned as well. This provides flexibility and improves code readability.
  3. Interfaces and Type Definitions: TypeScript supports the definition of interfaces and custom type definitions, enabling developers to create complex and self-documented data structures and objects.
  4. Classes and Object-Oriented Programming: TypeScript includes support for classes, constructors, inheritance, and other Object-Oriented Programming (OOP) concepts. It provides syntax similar to languages like Java or C#, making it easier for developers familiar with OOP principles.
  5. Modules and Namespaces: TypeScript supports modular development by allowing code to be organized into separate files and modules. It provides syntax for importing and exporting functionality between files, helping in code organization and reducing naming conflicts.
  6. Enhanced Tooling and IntelliSense: TypeScript provides improved tooling features and enhanced IntelliSense support in popular code editors and Integrated Development Environments (IDEs). This includes autocompletion, type inference, and code navigation, resulting in a more productive development experience.
  7. Compatibility with JavaScript: TypeScript is a superset of JavaScript, meaning that existing JavaScript code can be gradually converted to TypeScript without the need for major rewrites. This makes it easier to adopt TypeScript in existing projects.
  8. Built-in Support for Modern JavaScript Features: TypeScript supports the latest ECMAScript standards and features, such as arrow functions, destructuring, async/await, and more. This allows developers to leverage the latest JavaScript capabilities while still enjoying the benefits of static typing.
    // Example interface in TypeScript
    interface Person {
        name: string;
        age: number;
        email?: string;
    }

    // Example class in TypeScript
    class Student implements Person {
        constructor(public name: string, public age: number) {}

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

How can you install TypeScript?

Summary:

To install TypeScript, you can use npm (Node Package Manager) by running the command "npm install -g typescript" in your command prompt or terminal. This will install TypeScript globally on your machine, allowing you to use it from anywhere.

Detailed Answer:

To install TypeScript, follow these steps:

  1. First, make sure you have Node.js installed on your machine. TypeScript is distributed through npm, which is the package manager for Node.js.
  2. Open your command line interface.
  3. Run the following command to install TypeScript globally:
  npm install -g typescript

This command will install TypeScript globally on your system, allowing you to use it anywhere on your machine.

  1. Verify that TypeScript is installed correctly by running the following command:
  tsc --version
  1. If TypeScript is installed successfully, you should see the version number displayed in the command line.
  2. Once TypeScript is installed, you can start using it to compile your TypeScript files into JavaScript files.
  3. Create a new TypeScript file with a .ts extension (e.g., script.ts).
  4. Write your TypeScript code in the file.
  5. Compile the TypeScript file into JavaScript by running the following command:
  tsc script.ts

This command will generate a JavaScript file with the same name as your TypeScript file (e.g., script.js).

  1. You can now run the generated JavaScript file using node command:
  node script.js

By following these steps, you will be able to install and use TypeScript on your machine. TypeScript provides additional features and static typing to JavaScript, making it a powerful tool for building scalable and maintainable applications.

What are the data types supported by TypeScript?

Summary:

In TypeScript, the supported data types are: 1. Boolean: represents true or false values. 2. Number: represents numeric values. 3. String: represents textual data. 4. Array: represents a collection of elements. 5. Tuple: represents an array with a fixed number of elements of different types. 6. Enum: represents a set of named constant values. 7. Any: represents any type of value. 8. Void: represents the absence of a value. 9. Null: represents the absence of any object value. 10. Undefined: represents the absence of initialized value. 11. Never: represents values that never occur. 12. Object: represents non-primitive types.

Detailed Answer:

TypeScript is a strict syntactical superset of JavaScript that adds optional static typing to the language. This means that TypeScript supports all the basic data types present in JavaScript, such as:

  • Boolean: Represents a logical value of true or false.
  • Number: Represents numeric values, including integers and floating-point numbers.
  • String: Represents a sequence of characters enclosed in single or double quotation marks.
  • Array: Represents an ordered list of values of a specific type.
  • Tuple: Represents an array with a fixed number of elements, where each element can be of a different type.
  • Enum: Represents a set of named constants. Each constant is assigned a numeric value.
  • Any: Represents a value of any type, similar to the behavior of variables in JavaScript.
  • Void: Represents the absence of any type. It is commonly used as the return type of functions that do not return a value.
  • Null and Undefined: Represent absence of value. They are subtypes of all other types.
  • Never: Represents the type of values that never occur. It is used as the return type for functions that always throw an exception or have an infinite loop.

Additionally, TypeScript also supports a few advanced data types that are not available in JavaScript:

  • Union Types: Represents a type that can be one of multiple types.
  • Intersection Types: Represents a type that combines multiple types into one.
  • Type Aliases: Allows developers to create their own names for types.
  • Literal Types: Represents a type that can only have a specific literal value.
  • Nullable Types: Represents a type that can be either the specified type or null.
  • Readonly Types: Represents a type where all properties are read-only.
// Example code snippets
let isCompleted: boolean = false;

let age: number = 30;

let firstName: string = "John";

let numbers: number[] = [1, 2, 3, 4, 5];

let employee: [number, string] = [1, "John"]; // Tuple

enum Color {Red, Green, Blue};
let backgroundColor: Color = Color.Blue;

let anyType: any = 10;

function greet(): void {
    console.log("Hello!");
}

let nullValue: null = null;

let undefinedValue: undefined = undefined;

function throwError(): never {
    throw new Error("An error occurred!");
}

let unionType: string | number = "John";

type Point = {
    x: number;
    y: number;
};

let point: Point = { x: 1, y: 2 };

type Age = 30;
let ageValue: Age = 30;

type Weekday = "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday";
let today: Weekday = "Monday";

let canBeNull: string | null = null;

type ReadonlyPoint = Readonly;
let readonlyPoint: ReadonlyPoint = { x: 1, y: 2 };
readonlyPoint.x = 3; // Error: Cannot assign to 'x' because it is a read-only property.

What is the use of 'any' data type in TypeScript?

Summary:

The 'any' data type in TypeScript allows for dynamic typing, as it lets you assign any value to a variable without any type checking. It is mainly used when the type of a value is not known or when working with legacy JavaScript code that does not have type annotations. However, its usage should be minimized as much as possible to maintain type safety.

Detailed Answer:

The 'any' data type in TypeScript

The 'any' data type in TypeScript is a special type that allows a variable to hold values of any type. It is a dynamic type, meaning that the variable can be reassigned to different types without any type checking or errors at compile-time. While TypeScript promotes static typing and the use of specific data types, the 'any' type provides flexibility when the type of a variable is not known or when dealing with values from external sources that lack type information.

Below are some use cases for the 'any' data type:

  1. Migration from JavaScript: When migrating existing JavaScript code to TypeScript, the 'any' type can be used to handle cases where the type of a variable is ambiguous or not explicitly defined.
  2. Lack of Type Definitions: Type definitions might not exist for all external libraries or APIs used in a TypeScript project. The 'any' type can be used to handle situations where type information is missing or incomplete.
  3. Dynamically Typed Data: In scenarios where data is dynamically generated or retrieved, the data's type may not be known ahead of time. The 'any' type allows for dynamic typing of variables that hold such data.

While the 'any' type provides flexibility, it should be used sparingly, as it undermines the benefits of static typing in TypeScript. Overusing the 'any' type can lead to loss of type safety and increase the chances of runtime errors. It is recommended to use more specific types whenever possible or to use type inference to determine the type automatically.

// Example usage of 'any' type
let data: any;

data = 10; // Assigning a number

console.log(data); // Output: 10

data = "Hello"; // Reassigning a string

console.log(data); // Output: Hello

data = true; // Reassigning a boolean

console.log(data); // Output: true

What is the use of 'void' data type in TypeScript?

Summary:

The 'void' data type in TypeScript is used to indicate that a function does not return any value. Functions with a void return type can still execute code and perform actions, but they do not produce a value that needs to be utilized or assigned.

Detailed Answer:

The use of 'void' data type in TypeScript

In TypeScript, the 'void' data type is used to indicate the absence of any type. It commonly denotes that a function does not return any value.

  • Usage: The 'void' data type is typically used as the return type of a function when the function does not have a return statement or explicitly returns undefined. This is useful when a function is intended to perform some side effects, such as modifying variables or invoking other functions, without producing any output.
  • Example: Consider a simple function in TypeScript that displays a message on the console:
function showMessage(): void {
  console.log("Hello, World!");
}

This function has a return type of 'void' as it does not return any value. The function only logs a message to the console.

  • Interface Method: The 'void' data type can also be used when defining methods in interfaces:
interface Printer {
  print(): void;
}

The above code snippet defines an interface named 'Printer' with a method named 'print'. The 'print' method does not return anything, denoted by the 'void' return type.

Summary:

The 'void' data type in TypeScript is used to indicate that a function does not return any value. It is useful for functions that perform side effects or when declaring methods in interfaces that do not have a return value. By specifying the 'void' return type, it provides clarity and ensures that the function does not unintentionally return a value.

What is the use of 'never' data type in TypeScript?

Summary:

The 'never' data type in TypeScript represents values that will never occur. It is typically used to indicate functions that never return a value, or for variables that can never be assigned a value. It is useful for exhaustiveness checking and error handling.

Detailed Answer:

The 'never' data type in TypeScript

The 'never' data type in TypeScript represents the type of values that will never occur. It is a special type that indicates the absence of a value. This type is useful in scenarios where functions are expected to not return any value or have unreachable end points.

  • Functions that do not return: When a function does not have a return statement or has a return statement without a value, its return type is inferred as 'void'. However, if we want to explicitly indicate that the function will never return, we can assign the 'never' type to the return value. This can be useful for signaling errors or terminating the program.
  • function throwError(message: string): never {
      throw new Error(message);
    }
    
    function infiniteLoop(): never {
      while (true) {
        // do something indefinitely
      }
    }
    
  • Unreachable code: When a block of code has an unreachable end point, the type of that block is considered to be 'never'. This can occur when the code contains a throw statement, an infinite loop, or a conditional statement that will always evaluate to false.
  • function example(n: number): string {
      if (n > 0) {
        return 'positive';
      } else if (n < 0) {
        return 'negative';
      } else {
        throw new Error('Zero is not allowed');
      }
    }
    
    function unreachableCode(): never {
      throw new Error('This code will never be reached');
    }
    

The 'never' type can be used to provide additional type safety and expressiveness in TypeScript. It helps to catch potential errors and improve the readability of code by clearly indicating the absence of certain values or execution paths.

What are the different access modifiers in TypeScript?

Summary:

In TypeScript, there are three access modifiers: public, private, and protected. 1. Public: Public members are accessible from anywhere, both within the class and outside of it. 2. Private: Private members are only accessible within the class they are defined in. They cannot be accessed from outside the class. 3. Protected: Protected members are only accessible within the class they are defined in and its subclasses. They cannot be accessed from outside the class hierarchy.

Detailed Answer:

The different access modifiers in TypeScript are:

  1. Public: The public access modifier allows the associated member to be accessed from anywhere, both within and outside of its defining class.
  2. Private: The private access modifier restricts the associated member to be accessed only within its defining class. It cannot be accessed from outside the class, including its child classes.
  3. Protected: The protected access modifier allows the associated member to be accessed within its defining class as well as by its child classes. It cannot be accessed from outside the class hierarchy.

Here are some examples showing the use of different access modifiers in TypeScript classes:

class Person {
    public firstName: string;
    private lastName: string;
    protected age: number;

    constructor(firstName: string, lastName: string, age: number) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }

    public getFullName(): string {
        return this.firstName + " " + this.lastName;
    }

    private displayAge(): void {
        console.log(this.age);
    }
}

class Employee extends Person {
    private employeeId: number;

    constructor(firstName: string, lastName: string, age: number, employeeId: number) {
        super(firstName, lastName, age);
        this.employeeId = employeeId;
    }

    public getEmployeeInfo(): string {
        return this.getFullName() + ", Employee ID: " + this.employeeId;
    }
}

let person = new Person("John", "Doe", 25);
console.log(person.firstName); // accessed using public modifier
// console.log(person.lastName); // Error: private member
// console.log(person.age); // Error: protected member

let employee = new Employee("Jane", "Smith", 30, 12345);
console.log(employee.getEmployeeInfo()); // accessing inherited and public member

// console.log(employee.age); // Error: protected member cannot be accessed outside class hierarchy

In the example above, the "firstName" property of the Person class has a public access modifier, which allows it to be accessed from outside the class. The "lastName" property has a private access modifier, which restricts its access to only within the class. The "age" property has a protected access modifier, which allows it to be accessed within the class hierarchy.

The Person class also has public and private methods, representing the display of age. The getFullName method is public and can be accessed from anywhere, while the displayAge method is private and can only be accessed within the class.

The Employee class extends the Person class, allowing it to inherit the properties and methods. In the example, the getEmployeeInfo method of the Employee class makes use of the inherited getFullName method and also accesses the employeeId property defined within the Employee class, which has a private access modifier.

Explain the 'interface' keyword in TypeScript.

Summary:

The 'interface' keyword in TypeScript is used to define the structure of an object. It allows you to specify the properties and methods that an object should have. Interfaces in TypeScript are used for type-checking, providing a way to ensure that objects adhere to a specific contract or shape.

Detailed Answer:

The 'interface' keyword in TypeScript is used to define a structure or contract for an object. It represents a blueprint that defines the properties and methods that an object must have in order to adhere to the specified interface.

Interfaces in TypeScript play a crucial role in achieving strong typing and ensuring type safety. They provide a way to define the shape and behavior of objects without implementing the actual functionality.

Here is the syntax to define an interface in TypeScript:

    interface InterfaceName {
        property1: type;
        property2: type;
        ...
        method1(): returnType;
        method2(): returnType;
        ...
    }

Let's break down the components of an interface:

  • InterfaceName: It is the name of the interface.
  • property1, property2: These are the properties of the interface. Each property is defined with a name and a specified type.
  • method1, method2: These are the methods of the interface. Each method is defined with a name and a specified return type. Method parameters can also be specified within parentheses.

An object implementing an interface must adhere to the structure defined by the interface. It must have all the properties and methods as specified by the interface. For example:

    interface Person {
        name: string;
        age: number;
        greet(): void;
    }

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

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

        greet(): void {
            console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
        }
    }

    const john: Person = new Employee("John", 25);
    john.greet(); // Output: Hello, my name is John and I'm 25 years old.

In the above example, the 'Person' interface defines the structure for a person object with 'name', 'age', and 'greet' properties. The 'Employee' class implements the 'Person' interface, providing the required properties and method. The 'john' object is created using the 'Employee' class and can be treated as a 'Person' object as it adheres to the 'Person' interface.

What is the use of 'class' keyword in TypeScript?

Summary:

The 'class' keyword in TypeScript is used to define a blueprint for creating objects. It encapsulates data and behavior into a single unit called an object, allowing for easy code reuse and organization. Classes can have properties, methods, and can also be used for inheritance and polymorphism.

Detailed Answer:

The 'class' keyword in TypeScript is used to define a blueprint for creating objects. It allows developers to create reusable and structured code by encapsulating properties and methods within a class.

Some main uses of the 'class' keyword in TypeScript are:

  1. Object Creation: Using the class blueprint, developers can create multiple instances of objects with similar properties and methods, providing a way to efficiently manage and manipulate data.
  2. Inheritance: TypeScript supports inheritance, where classes can inherit properties and methods from other classes. This allows developers to create a hierarchy of classes, with each class inheriting behavior from its parent class.
  3. Encapsulation: Classes help in encapsulating properties and methods, preventing direct access from external sources. In TypeScript, class members can be private, protected, or public, providing control over access levels.
  4. Code Organization: Classes provide a way to organize and structure code, making it easier to understand and maintain. Developers can group related properties and methods within a class, improving code readability and organization.
  5. Type Checking: TypeScript is a statically typed language that supports type annotations. Classes allow developers to define types for properties and method signatures, enabling type checking and catching errors at compile-time.
// Example of a class in TypeScript
class Car {
    private brand: string;
    private color: string;

    constructor(brand: string, color: string) {
        this.brand = brand;
        this.color = color;
    }

    public startEngine(): void {
        console.log(`The ${this.brand} car with color ${this.color} is starting...`);
    }
}

// Creating instances of the Car class
const car1 = new Car("Toyota", "Red");
const car2 = new Car("BMW", "Blue");

car1.startEngine(); // Output: The Toyota car with color Red is starting...
car2.startEngine(); // Output: The BMW car with color Blue is starting...

What is TypeScript?

Summary:

TypeScript is an open-source programming language developed by Microsoft. It is a statically typed superset of JavaScript that compiles to plain JavaScript code, making it easier to write and maintain large-scale applications. TypeScript offers additional features like type checking, classes, modules, and strong tooling support for building robust web applications.

Detailed Answer:

TypeScript is a programming language developed by Microsoft. It is an open-source superset of JavaScript that adds static typing and other advanced features to JavaScript. TypeScript code is transpiled into plain JavaScript code, which can be run on any JavaScript runtime.

TypeScript provides a number of benefits over JavaScript:

  • Static Typing: TypeScript introduces static typing, allowing developers to declare variable types and catch type-related errors during compilation. This helps in writing more robust and maintainable code.
  • Enhanced Tooling and IDE Support: TypeScript has excellent tooling and is well-integrated with popular IDEs like Visual Studio Code. This enables features such as code navigation, autocompletion, and refactoring tools, making developers more productive.
  • Modern Language Features: TypeScript includes features not available in JavaScript, such as support for classes, interfaces, modules, generics, async/await, and more. These features enable developers to write cleaner and more structured code.
  • Compatibility with JavaScript Ecosystem: Since TypeScript is a superset of JavaScript, existing JavaScript code can be gradually migrated to TypeScript without any major disruptions. TypeScript can also import and utilize JavaScript libraries, making it compatible with the JavaScript ecosystem.
  • Strong Community Support: TypeScript has gained significant popularity and has a large and active community. This means there are countless resources, tutorials, and libraries available to help developers learn and build applications using TypeScript.
// Example TypeScript code
class Person {
  private name: string;
  
  constructor(name: string) {
    this.name = name;
  }
  
  sayHello() {
    console.log(`Hello, my name is ${this.name}`);
  }
}

const person = new Person("John");
person.sayHello();

Overall, TypeScript enhances JavaScript development by adding static typing, modern language features, and improved tooling. It offers a more structured and maintainable approach to building large-scale applications while maintaining compatibility with the JavaScript ecosystem.

TypeScript Intermediate Interview Questions

What is the use of 'unknown' type in TypeScript?

Summary:

The 'unknown' type in TypeScript is used to represent a value that could be of any type. It is similar to the 'any' type, but with additional type safety. When using 'unknown', you must perform type-checks or type-assertions before using the value, making it useful for scenarios where the type is not known in advance or for handling dynamic data.

Detailed Answer:

The 'unknown' type in TypeScript

The 'unknown' type in TypeScript is a type that represents values that could be of any type. It is similar to the 'any' type, but with some key differences. Unlike the 'any' type, which allows any operations without type checking, the 'unknown' type requires type checking before performing any operations on values of this type.

The primary use of the 'unknown' type is when we have variables or function parameters that may come from external sources, such as user input or data fetched from an API, and we cannot guarantee their type. By assigning the 'unknown' type to these variables or function parameters, we can enforce explicit type checking before performing any further operations.

When using the 'unknown' type, we need to explicitly narrow down the type through type assertions or type guarding before using the value. This ensures type safety and avoids run-time errors that could occur if we assume a specific type without proper validation.

Example:

function processInput(input: unknown) {
  if (typeof input === 'string') {
    // Type assertion: treating 'input' as string
    console.log(input.toUpperCase());
  }
}

let userInput: unknown = 'hello';

processInput(userInput);
  • The 'unknown' type is used to declare the 'input' parameter in the 'processInput' function.
  • If the 'input' parameter is determined to be a string through type checking, we can safely use string-specific operations like 'toUpperCase()'.
  • We need to perform type assertion ('typeof input === 'string'') to narrow down the type to 'string' before using 'toUpperCase()'.
  • This ensures that if the 'input' value is not a string, the code will not attempt to execute 'toUpperCase()', avoiding potential runtime errors.

In summary, the 'unknown' type in TypeScript is used when dealing with values of uncertain type, requiring explicit type checking before performing any operations. It provides a safer alternative to the 'any' type by enforcing type validation and reducing the likelihood of runtime errors.

Explain the 'Tuple' type in TypeScript.

Summary:

Detailed Answer:

Tuple type in TypeScript

In TypeScript, a 'Tuple' type is a type that represents an array with a fixed number of elements, where each element can have a different type.

Here is the syntax to define a Tuple type:

    type Tuple = [Type1, Type2, ..., TypeN];

In the above syntax, 'Type1', 'Type2', etc. represent the types of the elements in the Tuple. The number of types specified must match the number of elements in the Tuple. Once defined, the Tuple type enforces the order and the types of the elements.

Tuples are useful when you want to represent a collection of values where each value has a specific type and position.

Here is an example of how to create and use a Tuple in TypeScript:

    // Define a Tuple type
    type Person = [string, number, boolean];
    
    // Declare a variable of Tuple type
    let person: Person = ["John", 30, true];
    
    // Access Tuple elements using index
    let name: string = person[0];
    let age: number = person[1];
    let isActive: boolean = person[2];
    
    console.log(name);       // Output: John
    console.log(age);        // Output: 30
    console.log(isActive);   // Output: true

One important thing to note about Tuples is that the types of the elements are constrained based on their positions. The number of elements and their types must match the Tuple type definition. If we try to assign a Tuple with a different number of elements or incompatible types, TypeScript will give a compilation error.

  • Tuples support Optional and Rest elements: TypeScript allows specifying optional or rest elements in Tuples. For example, in a Tuple type definition, you can mark the last element as optional or use the '...' syntax to capture any number of additional elements of a specific type.
    // Optional element in Tuple
    type Person = [string, number, boolean?];
    
    // Rest elements in Tuple
    type Numbers = [number, ...number[]];

Tuples provide a way to define and enforce the expected shape of an array with fixed positions and types. They are particularly useful when you want to represent a collection of values where each value has a specific meaning and type.

How to import and export modules in TypeScript?

Summary:

Detailed Answer:

In TypeScript, modules are used to organize and manage code into reusable and independent units. Modules allow us to import and export functionalities and data from one module to another. There are two ways to import and export modules in TypeScript: using the CommonJS module syntax and using the ES module syntax.

CommonJS Module Syntax:

In the CommonJS module syntax, you can use the require keyword to import modules and the exports keyword to export functionalities or data.

// exporting module
exports.myFunction = function() {
    console.log("Hello, World!");
};

// importing module
const myModule = require("./myModule");
myModule.myFunction(); // Output: Hello, World!

ES Module Syntax:

In the ES module syntax, you can use the import and export keywords to import and export functionalities or data.

// exporting module
export function myFunction() {
    console.log("Hello, World!");
}

// importing module
import { myFunction } from "./myModule";
myFunction(); // Output: Hello, World!

When using the ES module syntax, you can also have default exports:

// exporting module as default
export default function() {
    console.log("Hello, World!");
}

// importing default module
import myFunction from "./myModule";
myFunction(); // Output: Hello, World!

Furthermore, you can also export multiple functionalities or data from a module:

// exporting multiple functionalities
export function sayHello() {
    console.log("Hello!");
}

export function sayGoodbye() {
    console.log("Goodbye!");
}

// importing multiple functionalities
import { sayHello, sayGoodbye } from "./myModule";
sayHello(); // Output: Hello!
sayGoodbye(); // Output: Goodbye!
  • Note: When using the ES module syntax, it's important to ensure that your TypeScript configuration file (tsconfig.json) has the "module" option set to "ESNext" or "ES2015". Also, if you are running your TypeScript code in a browser environment, you may need to use a module bundler, such as webpack, to bundle your modules into a single JavaScript file.

What is Type Inference in TypeScript?

Summary:

Type inference is a feature in TypeScript that allows the compiler to automatically determine the type of a variable based on its value. This eliminates the need for explicit type annotations in most cases, making the code more concise and readable.

Detailed Answer:

Type Inference in TypeScript:

Type Inference is a powerful feature in TypeScript that allows the compiler to automatically determine the type of a variable based on its value. This means that we can omit explicit type annotations in our code and TypeScript will automatically infer the type for us.

TypeScript's type inference works by analyzing the values assigned to variables during their initialization and usage throughout the code. The compiler examines the expressions and uses them to deduce the most appropriate type.

Here is an example to demonstrate how type inference works:

    let message = "Hello, TypeScript"; // TypeScript infers the type of 'message' as string
    let count = 10; // TypeScript infers the type of 'count' as number
    let isEnabled = true; // TypeScript infers the type of 'isEnabled' as boolean

    // The following statement will result in a compilation error because 'count' is inferred as number
    // count = "20"; // Error: Type '"20"' is not assignable to type 'number'

Type inference not only applies to variables, but it also works with function return types, function arguments, and object properties. By relying on type inference, we can write TypeScript code that is concise and still benefits from static type checking.

In cases where the desired type cannot be inferred, TypeScript provides us with the option to provide explicit type annotations using the syntax `: `. This is especially useful when the inferred type is not accurate or when we want to improve code readability.

Type inference is a key feature of TypeScript as it reduces the need for explicit typing, making the code more concise and maintainable. However, it's still important to strike a balance and use explicit type annotations when necessary for clarity and correctness.

How to declare variables with explicit types in TypeScript?

Summary:

In TypeScript, you can declare variables with explicit types by using the colon (:) syntax after the variable name followed by the type. For example, to declare a variable x with type number, you would write: let x: number; You can also assign a value to the variable in the same statement: let x: number = 5;

Detailed Answer:

In TypeScript, you can declare variables with explicit types by using the syntax `: `. This allows you to specify the type of the variable explicitly, which can help improve code readability and catch potential type-related errors during compilation. Here is an example of declaring variables with explicit types in TypeScript:
  
    let name: string = "John";
    let age: number = 30;
    let isStudent: boolean = true;
    let hobbies: string[] = ["coding", "reading", "gaming"];
  
In the above example: - The variable `name` is explicitly declared with the type `string`. - The variable `age` is explicitly declared with the type `number`. - The variable `isStudent` is explicitly declared with the type `boolean`. - The variable `hobbies` is explicitly declared as an array of strings using the syntax `string[]`. By declaring variables with explicit types, you can ensure that the values assigned to those variables are of the specified type. If you attempt to assign a value of a different type to a variable with an explicit type, TypeScript will throw a compilation error. Explicitly declaring variable types can also provide benefits when working with TypeScript editors or IDEs that support static type checking. This can enable features such as autocomplete and improved code suggestions, making it easier to work with your codebase. In addition to explicitly declaring types for variables, TypeScript also allows you to use type inference, where the type is automatically inferred based on the assigned value. However, using explicit type declarations is recommended for better code clarity and maintainability.

How to define optional properties in TypeScript?

Summary:

Detailed Answer:

In TypeScript, optional properties can be defined using the "?" symbol after the property name in the object type declaration. This indicates that the property is optional and may or may not be present in the object. Here is an example of how to define optional properties in TypeScript: ``` interface User { name: string; age?: number; email?: string; } ``` In the above code snippet, the `age` and `email` properties are defined as optional by adding the "?" symbol after the property name. This means that when creating an object of type `User`, these properties can be omitted or included, depending on the requirements. Here's an example of how to create an object using the `User` interface with optional properties: ``` const user1: User = { name: "John", age: 25, email: "[email protected]", }; const user2: User = { name: "Jane", }; const user3: User = { name: "Mike", age: 30, }; ``` In the above code snippet, `user1` includes all three properties (`name`, `age`, and `email`), `user2` omits the optional properties, and `user3` includes only the `name` and `age` properties. When accessing optional properties, it is important to check for their existence before using them to avoid potential runtime errors. This can be done using an if statement or the optional chaining operator (?.) introduced in TypeScript 3.7. ``` if (user1.email) { console.log(user1.email); } console.log(user2.email); // Output: undefined console.log(user3.email?.toUpperCase()); // Output: undefined ``` In the above code snippet, the existence of the `email` property is checked before accessing it using an if statement. For `user2`, which does not have the `email` property defined, accessing it will return `undefined`. The optional chaining operator `?.` can also be used to safely access the property and perform operations on it, as shown in the case of `user3`. By defining optional properties in TypeScript, we can create flexible object structures that allow certain properties to be included or omitted based on the context or requirements of our code.

What is the use of 'readonly' modifier in TypeScript?

Summary:

Detailed Answer:

The 'readonly' modifier in TypeScript is used to make a property or variable read-only, meaning that its value cannot be changed once it is assigned or initialized. This provides immutability to the property or variable, ensuring that it cannot be accidentally modified or reassigned.

By declaring a property or variable as 'readonly', it signals to other developers that the value should not be changed and enforces this restriction at compile-time. This can help prevent bugs and improve code clarity, as developers can trust that a 'readonly' property will not be modified.

The 'readonly' modifier can be applied to properties within a class or interface, as well as function parameters. When applied to a parameter, it restricts the caller from modifying the value passed to the function.

  • Example:
class Car {
  readonly brand: string;
  
  constructor(brand: string) {
    this.brand = brand;
  }
}

const myCar = new Car('Tesla');
myCar.brand = 'BMW'; // Error: Cannot assign to 'brand' because it is a read-only property.
  • Explanation:

In the above example, the 'brand' property of the 'Car' class is declared as 'readonly'. Therefore, once a value is assigned to it in the constructor, it cannot be changed later on. The attempt to assign a new value to 'myCar.brand' results in a compilation error.

The 'readonly' modifier is particularly useful when working with immutable data structures or when dealing with values that should not be modified throughout the execution of the program.

Explain the differences between 'public', 'private', and 'protected' access modifiers.

Summary:

Detailed Answer:

Access modifiers in TypeScript allow us to control the accessibility of class members (properties and methods) from outside the class. TypeScript provides three access modifiers: public, private, and protected.

1. Public: Public access modifier allows class members to be accessed from anywhere, both within and outside of the class. This is the default access modifier in TypeScript if no access modifier is specified.

  • Example:
class Person {
  public name: string;

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

let person = new Person("John");
console.log(person.name); // Output: John

2. Private: Private access modifier restricts access to class members only within the same class. They cannot be accessed from outside the class or even from child classes.

  • Example:
class Person {
  private age: number;

  constructor(age: number) {
    this.age = age;
  }

  getAge(): number {
    return this.age; // Can access 'age' within the class
  }
}

let person = new Person(25);
console.log(person.age); // Error: Property 'age' is private
console.log(person.getAge()); // Output: 25

3. Protected: Protected access modifier allows class members to be accessed within the same class as well as from its child classes. However, they cannot be accessed from outside of the class hierarchy.

  • Example:
class Person {
  protected address: string;

  constructor(address: string) {
    this.address = address;
  }
}

class Employee extends Person {
  getFullAddress(): string {
    return this.address; // Can access 'address' through inheritance
  }
}

let employee = new Employee("123 Main Street");
console.log(employee.address); // Error: Property 'address' is protected
console.log(employee.getFullAddress()); // Output: 123 Main Street

In summary, the public modifier allows access from anywhere, private restricts access to within the class, and protected allows access within the class and its child classes.

How to implement inheritance in TypeScript?

Summary:

Detailed Answer:

In TypeScript, you can implement inheritance using the "extends" keyword. This allows a class to inherit properties and methods from another class, known as the parent or base class. The child class can then add its own properties and methods or override the ones inherited from the parent class. To implement inheritance in TypeScript, follow these steps: 1. Define the parent class: Start by defining the parent class with its properties and methods.
class Animal {
  constructor(public name: string, public age: number) {}

  eat() {
    console.log(`${this.name} is eating.`);
  }
}
2. Create the child class: Create a new class that extends the parent class using the "extends" keyword. This establishes the inheritance relationship.
class Dog extends Animal {
  bark() {
    console.log(`${this.name} is barking.`);
  }
}
3. Use the inherited properties and methods: The child class can now access the properties and methods of the parent class. It can also add its own properties and methods.
const dog = new Dog("Lucky", 3);
console.log(dog.name);   // Output: Lucky
console.log(dog.age);    // Output: 3
dog.eat();               // Output: Lucky is eating.
dog.bark();              // Output: Lucky is barking.
4. Override methods if needed: If the child class needs to modify the behavior of an inherited method, it can override it by redefining the method with the same name in the child class.
class Cat extends Animal {
  eat() {
    console.log(`${this.name} is eating quietly.`);
  }
}

const cat = new Cat("Whiskers", 5);
cat.eat();    // Output: Whiskers is eating quietly.
By using the "extends" keyword in TypeScript, you can easily implement inheritance and create class hierarchies, allowing for code reusability and achieving a more organized and structured codebase.

What is a module loader in TypeScript?

Summary:

Detailed Answer:

A module loader in TypeScript is a tool or runtime mechanism that is responsible for loading and executing modules in a TypeScript application.

In TypeScript, a module is a file that contains code and declarations that can be imported or exported to/from other modules. Module loaders help in resolving module dependencies, handling the loading and execution of modules, and ensuring that the required modules are available when needed.

There are several module loaders available in TypeScript:

  1. CommonJS: CommonJS is a module loader that is commonly used in Node.js environments. It uses the require() function to import modules, and the module.exports or exports keywords to export modules.
  2. // importing modules
    const module1 = require('./module1');
    const module2 = require('./module2');
    
    // exporting modules
    function foo() {
      // some functionality
    }
    
    module.exports = foo;
    
  3. AMD (Asynchronous Module Definition): AMD is a module loader that is primarily used in web browsers. It focuses on loading modules asynchronously, allowing for better performance in web applications. It uses the define() function to define modules and the require() function to import modules.
  4. // defining modules
    define(['module1', 'module2'], function(module1, module2) {
      // module functionality
    });
    
    // importing modules
    require(['module1', 'module2'], function(module1, module2) {
      // module functionality
    });
    
  5. SystemJS: SystemJS is a universal module loader that can handle modules written in any module format, including CommonJS, AMD, and ES modules. It provides a flexible and powerful way to load and execute modules in a TypeScript application.
  6. // importing modules
    import { module1, module2 } from './modules';
    
    // exporting modules
    export function foo() {
      // some functionality
    }
    

Module loaders are essential in TypeScript applications as they enable modular code organization, code reusability, and dependency management. They make it easier to work with large-scale applications by allowing developers to split their code into smaller, manageable modules.

Explain the 'namespace' feature in TypeScript.

Summary:

Detailed Answer:

Some important line in the answer

The 'namespace' feature in TypeScript allows developers to organize their code into logical containers and avoid global scope pollution. It provides a way to structure and encapsulate code within a named namespace, similar to modules in other programming languages.

  • Namespaces: Namespaces are simply a way to group related code together. They can be used to encapsulate variables, functions, classes, and interfaces.
  • Declaration: Namespaces can be declared using the 'namespace' keyword, followed by the desired namespace name.
    
    namespace MyNamespace {
        // code goes here
    }
    
  • Using Namespaces: To use the code within a namespace, it needs to be referenced using the dot notation.
    
    MyNamespace.myFunction();
    
  • Nested Namespaces: Namespaces can also be nested within other namespaces by using the dot notation.
    
    namespace OuterNamespace {
        export namespace InnerNamespace {
            // code goes here
        }
    }
    
    OuterNamespace.InnerNamespace.myFunction();
    
  • Exporting: When code within a namespace needs to be accessible outside the namespace, it needs to be explicitly exported using the 'export' keyword.
    
    namespace MyNamespace {
        export function myFunction() {
            // code goes here
        }
    }
    

Overall, namespaces in TypeScript provide a way to organize and modularize code, making it easier to manage and prevent naming collisions. However, it is important to note that namespaces should be used sparingly and not as a replacement for modules, especially when working on larger projects.

What is a type assertion in TypeScript?

Summary:

Detailed Answer:

Type Assertion in TypeScript:

In TypeScript, type assertion is a way to tell the compiler about the specific type of a variable when the compiler is unable to infer it automatically. It is like type casting in other programming languages.

  • Syntax:
variableName as Type
or
<Type>variableName
  • Example:
let num: any = 5;
let strLength: number = (num as string).length;
or
let num: any = 5;
let strLength: number = (<string>num).length;

In the above example, the variable "num" is of type "any" which means it can hold any type of value. Here, we are asserting the type of "num" to the type "string" using type assertion. Then, we are accessing the "length" property of the string.

  • Usage:

Type assertion is commonly used in scenarios such as:

  • When migrating JavaScript code to TypeScript, where types are not explicitly defined.
  • When working with union types or any type, where the type needs to be narrowed down to a specific type.
  • When using a library that does not have type definitions.
  • Precautions:

It is important to note that type assertion does not perform any runtime type checking or validation. It is just a way to inform the compiler about the type. If the assertion is incorrect, it may lead to unexpected runtime errors.

It is recommended to use type assertion carefully and diligently, and rely more on TypeScript's type inference capabilities wherever possible.

How to use interfaces with classes in TypeScript?

Summary:

Detailed Answer:

How to use interfaces with classes in TypeScript?

In TypeScript, interfaces can be used to define the structure and contract of a class. By implementing an interface, a class is required to provide the properties and methods specified by the interface.

To use an interface with a class, the "implements" keyword is used:

interface Animal {
  name: string;
  sound: string;
  
  makeSound(): void;
}

class Dog implements Animal {
  name: string;
  sound: string;
  
  constructor(name: string, sound: string) {
    this.name = name;
    this.sound = sound;
  }
  
  makeSound(): void {
    console.log(this.sound);
  }
}

const dog = new Dog("Buddy", "Woof");
dog.makeSound(); // Output: "Woof"

In the example above, the class "Dog" implements the "Animal" interface. This means that the class is required to have properties "name" and "sound", as well as a method "makeSound".

The "implements" keyword ensures that the class adheres to the interface contract. If the class does not provide all the properties or methods defined by the interface, a compile-time error will occur.

By using interfaces with classes, it becomes easier to create reusable and maintainable code. Interfaces define the shape and behavior of objects, allowing for consistent usage across different classes.

  • Interface: An interface is used to define the structure and contract that a class must adhere to. It specifies the properties and methods that a class must provide.
  • Class: A class is an object-oriented programming construct that encapsulates data and behavior. It can implement one or more interfaces to fulfill their contract.
  • Implements keyword: The "implements" keyword is used to indicate that a class adheres to an interface. It establishes the relationship between the interface and the class.

What is 'ambient declaration' in TypeScript?

Summary:

Detailed Answer:

An ambient declaration in TypeScript is a way to declare types and interfaces for existing JavaScript code or for APIs that exist in the runtime environment. It allows you to describe the shape and structure of external code without actually providing an implementation. Ambient declarations provide a way to extend the TypeScript type system to work with code that is not written in TypeScript.

Ambient declarations are commonly used when working with JavaScript libraries or frameworks that do not have built-in TypeScript support. By providing ambient declarations, developers can benefit from TypeScript's static type checking and IntelliSense features while working with external code.

There are two main ways to create ambient declarations in TypeScript:

  1. Declaration Files: Declaration files have a .d.ts extension and are used to declare types, interfaces, and global variables specific to a particular module or library. These files can be created manually, or they can be downloaded from DefinitelyTyped, a community-driven repository of declaration files for popular JavaScript libraries.
  2. Triple-Slash Directives: Triple-slash directives are special comments placed at the top of a TypeScript file that reference external declaration files. These directives instruct the TypeScript compiler to include the definitions from the referenced declaration files in the current compilation.

Here's an example of an ambient declaration for the jQuery library:

/// <reference types="jquery" />

$(document).ready(function() {
  $('button').click(function() {
    console.log('Button clicked');
  });
});

In this example, the triple-slash directive references the jQuery declaration file, which provides type information for the jQuery library. This allows TypeScript to provide IntelliSense and type checking for jQuery code.

Ambient declarations are a powerful feature of TypeScript that enable developers to integrate with existing JavaScript codebases and leverage TypeScript's type system and tooling. They provide a way to describe the structure of external code and enhance the development experience when working with JavaScript libraries and frameworks.

TypeScript Interview Questions For Experienced

What is 'Module Resolution' in TypeScript?

Summary:

Detailed Answer:

Module Resolution

In TypeScript, module resolution is the mechanism used to locate and load external modules (files) in a TypeScript application. It determines how the compiler searches for and resolves dependencies between different modules, allowing them to be imported and used in a TypeScript file.

There are two main strategies for module resolution in TypeScript:

  1. Classic Resolution (default): This strategy is used when the targeted module is not an ES2015 module. It follows a set of rules to locate the module file.
  2. Node Resolution: This strategy is used when the targeted module is an ES2015 module or when the --esModuleInterop flag is enabled. It relies on Node.js module resolution algorithm and the node_modules directory structure.

Module resolution can be specified in the tsconfig.json file by setting the moduleResolution option to either "node" or "classic". If not provided, the TypeScript compiler will use the default strategy (classic resolution).

In Classic Resolution, TypeScript compiler looks for modules using several rules:

  • Relative Paths: If the module path starts with "./" or "../", the compiler resolves it relative to the importing file.
  • Absolute Paths: If the module path starts with "/", the compiler resolves it relative to the root of the project.
  • Non-relative Paths: If the module path does not start with "./", "../", or "/", the compiler looks up the module in a set of predefined locations, including the node_modules directories and type declaration files.

In Node Resolution, the compiler first follows the same rules as in Classic Resolution. If the module is not found, it uses Node.js module resolution algorithm, which looks for the module using the node_modules directory structure.

// Example of module resolution in TypeScript

// Classic Resolution
import { MyModule } from './my-module';
import { AnotherModule } from 'another-module';

// Node Resolution
import * as _ from 'lodash';

In conclusion, module resolution is an important aspect of TypeScript that determines how the compiler locates and loads external modules. By understanding the different strategies and rules involved in module resolution, developers can effectively import and use modules in their TypeScript applications.

What is Type Guard in TypeScript?

Summary:

Detailed Answer:

Type Guard in TypeScript

TypeScript is a statically typed superset of JavaScript that provides optional type annotations and type checking at compile time. One of the key features in TypeScript is the concept of Type Guards.

A Type Guard is a special syntax that allows the TypeScript compiler to narrow down the type of a variable within a conditional block. It helps in performing type analysis during runtime to determine the specific type of the variable.

  • Instanceof Type Guard: The instanceof type guard is used to check if an object is an instance of a specific class. It returns a boolean value indicating whether the object is an instance of the specified class or not. Here's an example:
class Animal {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
}

class Cat extends Animal {
    purr() {
        console.log("Cat is purring");
    }
}

function isCat(animal: Animal): animal is Cat {
    return animal instanceof Cat;
}

let animal: Animal = new Cat("Whiskers");

if (isCat(animal)) {
    animal.purr(); // Compiles successfully as the type is narrowed down to Cat
}
  • Type Predicate: Type Predicates are user-defined functions that return a type predicate. It can be used to perform type narrowing based on custom logic. Here's an example:
type Fish = { swim: () => void };
type Bird = { fly: () => void };

function isFish(animal: Fish | Bird): animal is Fish {
    return typeof (animal as Fish).swim !== "undefined";
}

function move(animal: Fish | Bird) {
    if (isFish(animal)) {
        animal.swim(); // Compiles successfully as the type is narrowed down to Fish
    } else {
        animal.fly(); // Compiles successfully as the type is narrowed down to Bird
    }
}

Type Guards are a powerful feature in TypeScript that helps in writing more robust and type-safe code. It allows developers to perform conditional operations based on the specific type of a variable, leading to better code quality and fewer runtime errors.

How to use decorators in TypeScript?

Summary:

Detailed Answer:

Using decorators in TypeScript

In TypeScript, decorators are a way to add metadata to classes or class members (properties, methods, accessors, etc.) and modify their behavior at runtime. Decorators are declared using the @ symbol followed by the decorator name, and are usually placed just before the class or class member that they are decorating.

To use decorators in TypeScript, you need to enable the "experimentalDecorators" option in your tsconfig.json file by setting it to true. This allows TypeScript to compile decorators into JavaScript code.

Here is an example of how to use decorators in TypeScript:

    function Logger(target: Function) {
        console.log('Logging...');
        console.log(target);
    }

    @Logger
    class MyClass {
        constructor() {
            console.log('Creating an instance of MyClass...');
        }
    }

    const myInstance = new MyClass();

In this example, the Logger decorator is declared as a function and takes the target (which represents the constructor function of the class being decorated) as a parameter. When the MyClass class is instantiated, the Logger decorator logs a message to the console before the class constructor is called.

Decorators can also take additional parameters to customize their behavior. For example:

    function logProperty(target: any, key: string) {
        console.log('Logging property...');
        console.log(key);
        console.log(target[key]);
    }

    class MyClass {
        @logProperty
        myProperty: string = 'Hello, world!';
    }

    const myInstance = new MyClass();

In this example, the logProperty decorator logs information about the myProperty property of the MyClass class when an instance of MyClass is created.

  • Advantages of using decorators:
    • Decorators provide a clean and declarative way to add functionality to classes or class members without modifying their original code.
    • They can be easily applied or removed, making the code more flexible and maintainable.
    • Decorators enable aspect-oriented programming, allowing you to separate cross-cutting concerns from the core logic of your application.
  • Limitations of decorators:
    • Decorators are still an experimental feature in TypeScript, so their behavior may change in future versions.
    • Decorators can only be used with classes and class members, and not with variables or functions.
    • Decorators are not supported in all JavaScript environments, so they may not work in some runtime environments or with older browsers.

What are Generics in TypeScript?

Summary:

Detailed Answer:

Generics in TypeScript

Generics in TypeScript are a feature that allows the creation of reusable components or functions that can work with various types. They provide a way to define functions or classes that can work with different data types, making the code more flexible and reusable.

  • Benefits of Generics:

1. Type Safety: Generics ensure that the type of data used in a generic component or function is maintained consistently throughout the code. This helps catch type errors at compile time rather than at runtime, leading to more robust and reliable software.

2. Code Reusability: Generics make it possible to write generic code that can work with a wide range of data types. This eliminates the need to duplicate code for each specific data type and promotes code reuse.

  • Using Generics:

Generics can be used in various contexts in TypeScript:

  1. Functions: Generics can be used to create functions that work with different types of data. The type parameter is declared using angle brackets (<>) after the function name and is used as a placeholder for the actual data type:
function printArray(arr: T[]): void {
  for (let i = 0; i < arr.length; i++) {
    console.log(arr[i]);
  }
}

// Example usage
let numbers: number[] = [1, 2, 3, 4];
let strings: string[] = ["Hello", "World"];

printArray(numbers); // Output: 1, 2, 3, 4
printArray(strings); // Output: Hello, World
  1. Classes: Generics can also be used with classes to create reusable components. The type parameter is declared using angle brackets (<>) after the class name and can be used in the class properties, methods, and constructors:
class Box<T> {
  private item: T;

  constructor(item: T) {
    this.item = item;
  }

  getItem(): T {
    return this.item;
  }
}

// Example usage
let stringBox = new Box<string>("Hello");
console.log(stringBox.getItem()); // Output: Hello

let numberBox = new Box<number>(42);
console.log(numberBox.getItem()); // Output: 42

Generics provide powerful tools for creating reusable and type-safe code in TypeScript. By leveraging generics, developers can improve code quality, reduce code duplication, and enhance the maintainability of their software.

Explain the 'keyof' keyword in TypeScript.

Summary:

Detailed Answer:

The 'keyof' keyword in TypeScript is a type operator that allows you to obtain a union type of all the keys of a given object type. It essentially creates a new type that represents all possible property names in the object.

Here is an example to illustrate the usage of 'keyof':

type Person = {
  name: string;
  age: number;
  gender: string;
};

type PersonKeys = keyof Person;

// PersonKeys is now a union type: 'name' | 'age' | 'gender'

In the example above, we create an object type 'Person' with three properties: 'name', 'age', and 'gender'. By applying the 'keyof' operator on 'Person', we obtain a new type 'PersonKeys' that is a union of all the keys in 'Person'.

Using the 'keyof' operator allows us to enforce type safety when working with object property names. It enables us to access properties dynamically and perform type checking at compile-time, preventing potential runtime errors.

  • Advantages of 'keyof':
  • Enables compile-time type checking: By using 'keyof' with indexed types or generics, we can ensure that we only access valid property names.
  • Facilitates dynamic property access: We can use the resulting union type to access properties dynamically without resorting to 'key in object' checks or 'object[key]' syntax.
  • Improved code documentation: By capturing all the keys in a type, it provides a clear reference for the available properties.

Overall, the 'keyof' keyword in TypeScript is a powerful tool that allows us to work with object properties in a type-safe and efficient manner. It brings compile-time safety when accessing object properties and facilitates dynamic property access.

What is 'Conditional Types' in TypeScript?

Summary:

Detailed Answer:

Conditional Types in TypeScript:

Conditional types are a feature in TypeScript that allow you to create types that depend on conditions or other types. They are used to perform type inference based on the values of other types, enabling you to define more complex and flexible types.

Conditional types make use of conditional expressions, which are expressions that evaluate to either true or false at compile time. These expressions can be used to define different types based on specific conditions.

  • Basic Syntax: Conditional types are defined using the following syntax:
type MyType = T extends SomeCondition ? TypeA : TypeB;
  • Explanation: In the above example, MyType is a conditional type that takes a generic type T as an argument. It checks if T extends SomeCondition, and if true, it assigns TypeA to MyType; otherwise, it assigns TypeB.
  • Common Use Cases: Conditional types can be used in various scenarios, such as:
  1. Mapping Types: You can use conditional types to map one type to another based on certain conditions or transform the types in specific ways.
  2.         type Convert = T extends string ? number : T;
            
            type ConvertedString = Convert; // ConvertedString will be number
            type ConvertedBoolean = Convert; // ConvertedBoolean will be boolean
        
  3. Distributive Types: Conditional types can distribute over union types, allowing you to create types that operate on each element of a union type individually.
  4.         type StringOrNumber = T extends string ? string : number;
            
            type Result = StringOrNumber<'a' | 'b' | 1 | 2>; // Result will be 'a' | 'b' | number
        
  5. Filtering Types: Conditional types can be used to filter out specific types from a union type based on certain conditions.
  6.         type ExcludeFalsy = T extends falsy ? never : T;
            
            type NonFalsyValues = ExcludeFalsy<'' | 0 | false | null | undefined>; // NonFalsyValues will be never
        

Conditional types provide a powerful tool to create polymorphic types that can adapt based on specific conditions or types. They enable more precise type checking and inference in TypeScript, allowing for increased flexibility and type safety.

How to create custom type guards in TypeScript?

Summary:

Detailed Answer:

To create custom type guards in TypeScript, you can use the `is` keyword to define a function that returns a boolean indicating whether a value satisfies a specific type. This allows you to perform type checks at runtime and provide more type safety in your code. Here's an example of creating a custom type guard for checking if a value is an array: ```typescript function isArray(value: any): value is any[] { return Array.isArray(value); } ``` In this example, the `isArray` function takes a value and uses the `is` keyword to define the return type as `value is any[]`. The function then uses `Array.isArray` to perform the type check and returns a boolean. You can then use this custom type guard to narrow down the type of a variable: ```typescript function processValue(value: unknown) { if (isArray(value)) { // value is now recognized as an array value.forEach((item) => console.log(item)); } else { console.log(value); } } ``` In this example, `processValue` takes an `unknown` value and uses the `isArray` type guard to determine if the value is an array. If it is, the function can safely call array-specific methods like `forEach`. If not, it can handle the value as a generic `unknown` type. You can create custom type guards for different types or combinations of types by defining specific conditions using `typeof`, `instanceof`, or other type predicates. Here's an example of a type guard using `typeof`: ```typescript function isString(value: unknown): value is string { return typeof value === "string"; } ``` And here's an example of a type guard using `instanceof`: ```typescript class MyClass { // class implementation } function isMyClass(value: unknown): value is MyClass { return value instanceof MyClass; } ``` By creating custom type guards, you can add more precise type checking to your TypeScript code and ensure safer operations on your values.

Explain the 'infer' keyword in TypeScript.

Summary:

Detailed Answer:

The 'infer' keyword in TypeScript is used in conditional types to infer or deduce the type of a type parameter based on the type of another parameter or expression.

When defining generic types or conditional types, TypeScript allows us to use the 'infer' keyword to capture the type that will be inferred by the compiler. This can be useful when working with complex type systems and conditional logic.

Here is an example to illustrate the usage of the 'infer' keyword:

    type ExtractReturnType = T extends (...args: any[]) => infer R ? R : any;
    
    function sum(a: number, b: number): number {
        return a + b;
    }
    
    type Result = ExtractReturnType; // Result will be inferred as 'number'

In the above example, the 'ExtractReturnType' type takes a generic type parameter 'T' and checks if it extends a function type '(...args: any[]) => infer R'. If it does, the inferred return type 'R' is captured by the 'infer' keyword and assigned to the conditional type 'R'. If it doesn't extend a function type, 'any' is assigned as the default type.

By using the 'infer' keyword, we can extract and use the inferred type when defining other types or performing further type transformations.

  • Advantages of using 'infer':
  • The 'infer' keyword allows us to capture and use the inferred type within a conditional type.
  • It helps in writing more generic and reusable type definitions.
  • It enables type inference based on the input or output type of a function or expression.

Overall, the 'infer' keyword is a powerful tool in TypeScript's type system that enables the capture and usage of inferred types within conditional types, allowing for more flexible and generic type definitions.

What is the use of 'mapped types' in TypeScript?

Summary:

Mapped types in TypeScript allow for the creation of new types based on an existing type by mapping over its properties. They are useful for creating new types with modified or transformed properties, such as making all properties optional or readonly. This helps in reducing code duplication and providing flexibility in type definitions.

Detailed Answer:

Mapped types in TypeScript are a powerful tool that allow you to create new types based on existing ones. They provide a way to transform and manipulate the properties of an object type, allowing you to create new types with modified or filtered properties.

One common use case for mapped types is to create readonly versions of existing types. This can be done using the Readonly utility type provided by TypeScript. The Readonly utility takes an object type as input and returns a new type with all properties marked as readonly:

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

type ReadonlyPerson = Readonly;

const person: ReadonlyPerson = {
  name: 'John',
  age: 25
};

// The following line would result in a type error, because the 'name' property is readonly
person.name = 'Alice';

Another use case for mapped types is to create partial versions of existing types. This can be done using the Partial utility type provided by TypeScript. The Partial utility takes an object type as input and returns a new type with all properties marked as optional:

type Book = {
  title: string;
  author: string;
  year: number;
};

type PartialBook = Partial;

const book: PartialBook = {
  title: 'TypeScript in Action'
};

// The 'author' and 'year' properties are optional, so the following line is allowed
book.author = 'John Doe';

Furthermore, mapped types can also be used to pick or omit properties from an object type. This can be done using the Pick and Omit utility types provided by TypeScript:

type SensitiveData = {
  id: number;
  password: string;
  email: string;
  address: string;
};

type PublicData = Pick;

const publicData: PublicData = {
  id: 123,
  email: '[email protected]'
};

// The 'password' and 'address' properties are omitted, so the following line is not allowed
publicData.password = 'password123';

In summary, mapped types in TypeScript provide a convenient way to manipulate and transform object types. They allow you to create new types with modified or filtered properties, making your code more expressive and type-safe.

What are 'intersection types' and 'union types' in TypeScript?

Summary:

Detailed Answer:

Intersection types

Intersection types in TypeScript allow you to combine multiple types into one, creating a new type that has all the properties and methods from each individual type. It represents the intersection of two or more types, meaning a value of this type must satisfy all the individual types involved.

  • Example: Suppose we have two types: 'Person' and 'Employee'. We can create an intersection type called 'PersonEmployee' that represents a person who is also an employee:
    type Person = {
      name: string;
      age: number;
    };
    
    type Employee = {
      id: number;
      salary: number;
    };
    
    type PersonEmployee = Person & Employee;
    
    const personEmployee: PersonEmployee = {
      name: "John",
      age: 30,
      id: 123,
      salary: 5000
    };
  • Explanation: In the example, we define two types 'Person' and 'Employee', and then we create an intersection type 'PersonEmployee' using the '&' operator. This intersection type represents a person who is also an employee, and it has all the properties from both types. We can then declare a variable 'personEmployee' of the 'PersonEmployee' type and assign it an object that satisfies both 'Person' and 'Employee' types.

Union types

Union types in TypeScript allow a value to have more than one type, meaning a value of this type can be any of the specified types. It represents the union of two or more types, and the value of a union type can be one of the specified types.

  • Example: Suppose we have two types: 'StringOrNumber' and 'BooleanOrObject'. We can create a union type called 'CombinedType' that represents a value that can be either a string or a number, or a boolean or an object:
    type StringOrNumber = string | number;
    type BooleanOrObject = boolean | object;
    type CombinedType = StringOrNumber | BooleanOrObject;
    
    let value: CombinedType;
    
    value = "Hello"; // Valid
    value = 123; // Valid
    value = true; // Valid
    value = { key: "value" }; // Valid
  • Explanation: In the example, we define two types 'StringOrNumber' and 'BooleanOrObject', and then we create a union type 'CombinedType' using the '|' operator. This union type represents a value that can be either a string or a number, or a boolean or an object. We can then declare a variable 'value' of the 'CombinedType' type and assign it a value of any of the specified types.

How to create utility types in TypeScript?

Summary:

Detailed Answer:

Utility types in TypeScript

In TypeScript, utility types are pre-defined types that provide convenient ways to manipulate and transform existing types. These utility types are built into TypeScript and can be used to create new types based on existing types.

  • Partial: The Partial utility type allows you to make all properties of a given type optional. It takes an object type as an argument and returns a new type with all properties marked as optional.
  • type Person = {
      name: string;
      age: number;
    };
    
    type PartialPerson = Partial<Person>;
    // The PartialPerson type now has optional properties
    // name?: string;
    // age?: number;
    
  • Readonly: The Readonly utility type allows you to create a new type with all properties of a given type marked as readonly. It takes an object type as an argument and returns a new type with readonly properties.
  • type Person = {
      name: string;
      age: number;
    };
    
    type ReadonlyPerson = Readonly<Person>;
    // The ReadonlyPerson type now has readonly properties
    // readonly name: string;
    // readonly age: number;
    
  • Required: The Required utility type allows you to make all properties of a given type required. It takes an object type as an argument and returns a new type with all properties marked as required.
  • type PartialPerson = {
      name?: string;
      age?: number;
    };
    
    type RequiredPerson = Required<PartialPerson>;
    // The RequiredPerson type now has required properties
    // name: string;
    // age: number;
    
  • Pick: The Pick utility type allows you to create a new type by picking a subset of properties from a given type. It takes an object type and a list of property names as arguments and returns a new type with only the specified properties.
  • type Person = {
      name: string;
      age: number;
      address: string;
    };
    
    type PersonNameAndAge = Pick<Person, 'name' | 'age'>;
    // The PersonNameAndAge type only contains the 'name' and 'age' properties
    
  • Omit: The Omit utility type allows you to create a new type by omitting specified properties from a given type. It takes an object type and a list of property names as arguments and returns a new type without the specified properties.
  • type Person = {
      name: string;
      age: number;
      address: string;
    };
    
    type PersonWithoutAge = Omit<Person, 'age'>;
    // The PersonWithoutAge type does not contain the 'age' property
    

These are just a few examples of the utility types available in TypeScript. The utility types provide powerful and concise ways to manipulate and transform types in TypeScript projects, making it easier to work with complex types and improve type safety.

Explain the 'this' type in TypeScript.

Summary:

Detailed Answer:

The 'this' type in TypeScript refers to the type of the current object or instance that a function is being called on.

In JavaScript, the value of 'this' is determined by how a function is called. It can refer to different objects depending on the context. However, in TypeScript, the 'this' type allows for capturing and preserving the type shape of the object on which the function is invoked.

Here's an example to illustrate how 'this' works in TypeScript:

class Person {
  name: string;
  
  constructor(name: string) {
    this.name = name;
  }
  
  sayHello() {
    console.log(`Hello, my name is ${this.name}`);
  }
}

const person = new Person("John");
person.sayHello(); // Output: Hello, my name is John

In the above example, the 'this' type within the sayHello() method refers to the type 'Person', because the method is being called on an instance of the Person class.

The 'this' type is useful for maintaining type safety within TypeScript. It allows the compiler to infer and use the correct type information when accessing properties or calling methods on an object. Additionally, it helps prevent errors by catching potential type mismatches at compile-time.

It's worth noting that in TypeScript, 'this' is considered to be an implicit parameter within a function. This means that if a function expects a specific type as 'this' parameter, it can be explicitly annotated using the 'this' type annotation:

function logName(this: Person) {
  console.log(`Logging name: ${this.name}`);
}

const person = new Person("John");
logName.call(person); // Output: Logging name: John

In the above example, the 'this' parameter is explicitly annotated as 'Person', ensuring that the function is called with the correct type.

The 'this' type is an important aspect of TypeScript that helps ensure type safety and maintain the type shape of objects across function calls.

What is 'declaration merging' in TypeScript?

Summary:

Detailed Answer:

Declaration merging in TypeScript refers to the process of combining multiple declarations with the same name into a single definition. It allows developers to extend or combine types and interfaces without the need for complex inheritance or explicit redefinition.

Declaration merging can be useful in scenarios where you need to add additional properties or methods to an existing type or interface, and provides a way to create a more cohesive and organized codebase.

Here are a few examples of how declaration merging can be used in TypeScript:

  • Merging interfaces: When two interfaces with the same name are declared, TypeScript automatically merges them by combining their properties and methods into a single interface.
  •     interface Animal {
          name: string;
        }
    
        interface Animal {
          age: number;
        }
    
        const animal: Animal = {
          name: "Leo",
          age: 5
        };
      
  • Merging interfaces with classes: TypeScript allows you to merge interfaces with classes, which can be useful when you want to add additional properties or methods to instances of a class.
  •     class Person {
          name: string;
          age: number;
        }
    
        interface Person {
          address: string;
        }
    
        const person: Person = {
          name: "John Doe",
          age: 25,
          address: "123 Main St"
        };
      
  • Merging namespaces: TypeScript allows you to merge multiple namespaces with the same name into a single namespace, combining their properties and functions.
  •     namespace Math {
          export function multiply(a: number, b: number): number {
            return a * b;
          }
        }
    
        namespace Math {
          export function add(a: number, b: number): number {
            return a + b;
          }
        }
    
        console.log(Math.multiply(2, 3)); // Output: 6
        console.log(Math.add(2, 3)); // Output: 5
      

Overall, declaration merging in TypeScript provides a powerful way to extend and combine types and interfaces, making the code more reusable, modular, and easier to maintain.

How to use 'type guards' with 'custom type predicates' in TypeScript?

Summary:

Detailed Answer:

TypeScript is a statically typed programming language developed by Microsoft. It is a superset of JavaScript and provides optional static typing, which enables developers to catch potential errors during development and improve code maintainability. One powerful feature of TypeScript is the ability to use 'type guards' with 'custom type predicates' to refine the type information of variables.

A 'type guard' is a runtime check that allows TypeScript to narrow down the type of a variable within a conditional block. When a type guard is true, it provides additional type information to the TypeScript compiler, enabling more precise type checking and autocompletion.

A 'custom type predicate' is a user-defined type guard that checks a variable against a specific condition and returns a boolean value indicating whether the condition is satisfied. Custom type predicates are typically defined as functions with a return type of 'variable is Type', where 'variable' is the name of the input parameter and 'Type' is the desired type.

Here's an example of how to use 'type guards' with 'custom type predicates' in TypeScript:

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

function doSomething(value: unknown) {
  if (isString(value)) {
    // TypeScript now knows that 'value' is a string
    console.log(value.toUpperCase());
  } else {
    console.log('Input value is not a string');
  }
}

doSomething('Hello'); // Output: HELLO
doSomething(42); // Output: Input value is not a string
  • Line 1: Defines a custom type predicate 'isString' that checks if a value is a string.
  • Line 5: Uses the 'isString' type guard to narrow down the type of 'value' within the conditional block.
  • Line 7: TypeScript now knows that 'value' is a string, allowing us to use string-specific methods or properties.
  • Lines 9-12: Demonstrates how the 'doSomething' function behaves when passed different types of values.

Using 'type guards' with 'custom type predicates' can greatly enhance TypeScript's type checking capabilities and improve the overall safety and correctness of your code.

Explain the 'Implicit This Parameter' in TypeScript.

Summary:

Detailed Answer:

Implicit This Parameter in TypeScript

In TypeScript, the concept of "Implicit This Parameter" refers to the automatic binding of the "this" keyword within class methods. It allows access to the instance properties and methods of a class without explicitly specifying the object on which the method is called.

  • Background: In JavaScript, the value of the "this" keyword is determined dynamically at runtime. It typically refers to the object that invokes the function. However, in TypeScript, the "this" keyword can have a static type annotation to provide more precise type checking during development.

When a class method is declared in TypeScript, it implicitly receives a special parameter called "this". The "this" parameter has the same type as the class itself and represents the instance of the class being used at runtime.

class MyClass {
  private value: number = 42;
  
  getValue(): number {
    return this.value;
  }
}

const myObject = new MyClass();
console.log(myObject.getValue()); // Output: 42

In the above example, the "getValue" method is defined within the "MyClass" class. The "this" parameter allows the method to access the "value" property of the class instance without having to explicitly provide the object.

This implicit binding of the "this" parameter makes the code more concise and readable by reducing the need for repetitive variable declarations or explicit use of the object name.

It is important to note that arrow functions in TypeScript do not have their own "this" object. Instead, they inherit the "this" value from the surrounding lexical scope. This behavior makes arrow functions particularly useful in scenarios where the "this" context needs to be preserved across different scope levels.

  • Advantages:
  • Enhanced Readability: The implicit this parameter reduces the noise in the code by removing the need to explicitly reference the object on which the method is called.
  • Type Safety: Using the implicit this parameter provides type-checking benefits in TypeScript, ensuring that the methods are invoked on the correct type of object.
  • Arrow Function Consistency: The implicit this parameter aligns with the behavior of arrow functions, where the this value is inherited from the surrounding context.

What are 'Conditional Expressions' in TypeScript?

Summary:

Detailed Answer:

Conditional Expressions in TypeScript

In TypeScript, conditional expressions are used to make decisions based on certain conditions. They allow you to choose between different values or actions based on the outcome of a condition.

Conditional expressions in TypeScript are typically used in if-else statements or ternary expressions, which evaluate a condition and perform different operations depending on the result.

  • If-else statements: In TypeScript, you can use if-else statements to execute a block of code if a certain condition is true, and another block if the condition is false. Here is an example:
if (condition) {
   // code to execute if condition is true
} else {
   // code to execute if condition is false
}
  • Ternary expressions: Ternary expressions are a compact way of writing if-else statements in TypeScript. They have the following syntax:
condition ? expression1 : expression2

If the condition is true, expression1 is evaluated and returned. Otherwise, expression2 is evaluated and returned. Here is an example:

let result = (num % 2 === 0) ? "Even" : "Odd";

In the example above, if the number stored in the num variable is even, the result will be "Even". Otherwise, it will be "Odd".

Conditional expressions are useful in TypeScript when you need to make decisions and perform different actions based on certain conditions. They allow you to write more flexible and dynamic code.

How to use 'type parameter defaults' in TypeScript?

Summary:

To use 'type parameter defaults' in TypeScript, you can define a default value for a type parameter by using the syntax ``. This allows you to specify a default value for the type parameter in case no explicit value is provided when invoking the generic type. For example: ``` function myFunction(arg: T): void { // ... } myFunction("hello"); // T is inferred as string myFunction(123); // T is inferred as number myFunction(true); // T is explicitly set to boolean ```

Detailed Answer:

Type Parameter Defaults

In TypeScript, you can use type parameter defaults to provide default values for type parameters in generic types or functions. This allows you to define a default type for a generic parameter if no type is explicitly provided when calling the generic type or function.

To use type parameter defaults in TypeScript, you need to define the type parameter with a default value using the `=` syntax.

    function myFunction(arg: T): T {
        return arg;
    }

    const result1 = myFunction(42); // Specify type argument explicitly
    const result2 = myFunction("Hello"); // Default to string type

In the above example, the `myFunction` function has a generic type parameter `T` with a default value of `string`. When calling the function without specifying the type argument, it defaults to `string`.

  • Type parameter defaults in generic types: You can also use type parameter defaults when defining generic types. Here's an example:
    interface MyArray {
        length: number;
        push(...items: T[]): number;
        pop(): T | undefined;
    }

    const myArray: MyArray = {
        length: 0,
        push(...items: number[]): number {
            return this.length += items.length;
        },
        pop(): number | undefined {
            return this.length ? --this.length : undefined;
        }
    };

In the above example, the `MyArray` interface is defined with a generic type parameter `T` that defaults to `any`. This allows you to create instances of `MyArray` without explicitly specifying the type argument.

Type parameter defaults provide flexibility and convenience when working with generics in TypeScript. They allow you to define a default type for a generic parameter, which is used when the type argument is not explicitly provided. This feature makes your code more reusable and easier to work with in different scenarios.

Explain 'Recursive Type Aliases' in TypeScript.

Summary:

Detailed Answer:

Recursive Type Aliases

In TypeScript, recursive type aliases allow us to define a type that refers to itself. This means that a type can have a reference to another type that is the same as itself. Recursive types are useful when dealing with data structures that have a recursive nature, such as linked lists, trees, or graphs.

Recursive type aliases can be defined using the `type` keyword followed by the name of the type and the type definition. To create a recursive type alias, we can use a union type or an intersection type.

  • Union Type: A recursive type alias that uses a union type creates a type that can be either the base case or a recursive case. The base case represents the end of the recursion, while the recursive case represents an iteration of the structure.
type LinkedList = T | { value: T, next: LinkedList };
  • Intersection Type: A recursive type alias that uses an intersection type allows us to add additional properties to the recursive case. This can be useful when defining more complex data structures.
type Tree = { value: T, left?: Tree, right?: Tree };

Recursive type aliases can also be used with other type operators and modifiers, such as `Partial`, `Readonly`, or `keyof`, to create more specialized recursive types.

type PartialTree = Partial>;
type ReadonlyLinkedList = Readonly>;

Using recursive type aliases, we can create expressive and self-referential type definitions that accurately model recursive data structures in TypeScript. This allows us to leverage the power of the type system to catch potential errors and provide better tooling support.

What are the 'Triple-Slash Directives' in TypeScript?

Summary:

Detailed Answer:

Triple-Slash Directives in TypeScript:

Triple-slash directives are special comments in TypeScript that provide instructions to the TypeScript compiler. These directives start with three slashes ('///') and can be placed at the top of a TypeScript file, before any code.

Triple-slash directives are used to:

  1. Reference external dependencies: Using triple-slash directives, you can reference external libraries or type definitions that are required for your TypeScript file to compile successfully. It helps the TypeScript compiler to locate and include the necessary files during the compilation process.
  2. Declare module dependencies: Triple-slash directives allow you to declare the dependency on another module or script. It helps TypeScript to understand the import and export statements correctly.
  3. Generate declaration files: By adding a triple-slash directive with the 'reference' keyword and the path to another TypeScript file, you can instruct the TypeScript compiler to include the referenced file's declarations in the generated declaration file (.d.ts).
  4. Control the output file structure: Triple-slash directives can be used to control the output file structure when generating JavaScript files from TypeScript code. By using the 'outFile' directive, you can specify the name and location of the output file.
  5. Set compiler options: Triple-slash directives can be used to set or override compiler options. For example, you can enable strict mode or specify a target ECMAScript version using these directives.

Here is an example of triple-slash directives used in a TypeScript file:

/// 
/// 

import { myFunction } from './myModule';

// TypeScript code here

How to use 'Type guards and type assertions' together in TypeScript?

Summary:

Detailed Answer:

TypeScript provides two powerful features called type guards and type assertions that can be used together to enhance the type safety of our code. Type guards allow us to narrow down the type of a variable within a conditional block based on runtime checks. By using type guards, we can make more precise assumptions about the types of variables and avoid runtime errors. Type assertions, on the other hand, allow us to explicitly override the inferred type of a variable and specify a different type that we know to be correct. This can be useful in situations where the type inference may not be accurate or when we want to temporarily treat a variable as a different type. To use type guards and type assertions together, we can follow these steps: 1. Define a type guard function that performs a runtime check on the variable and returns a boolean indicating whether the variable satisfies a certain type condition.
    
        function isNumber(value: any): value is number {
            return typeof value === 'number';
        }
    
2. Use the type guard function within a conditional block to narrow down the type of the variable.
    
        const value: unknown = 123;

        if (isNumber(value)) {
            // Within this block, the type of 'value' is narrowed down to 'number'
            console.log(value.toFixed(2));
        }
    
3. If necessary, use a type assertion to tell the TypeScript compiler that a variable should be treated as a different type, overriding the inferred type.
    
        const value: unknown = 'Hello TypeScript';

        if (isNumber(value)) {
            // This line will result in a compile-time error, as 'value' is still considered 'unknown'
            console.log(value.toFixed(2));
        }

        // Using a type assertion to treat 'value' as 'string'
        console.log((value as string).toUpperCase());
    
By combining type guards and type assertions, we can write safer and more expressive code in TypeScript. However, it's important to use type assertions with caution and make sure that we are not making assumptions that could lead to runtime errors. Always double-check the types of variables before performing any operations on them.

What are 'Literal Type Widening' and 'Numeric Separators' in TypeScript?

Summary:

Detailed Answer:

'Literal Type Widening' in TypeScript:

'Literal Type Widening' refers to the behavior in TypeScript where the type of a variable or parameter is widened from its specific literal value to a broader type. In other words, when a literal type is assigned to a variable or parameter, TypeScript will automatically infer a broader type for it instead of preserving the exact literal type. This widens the type to provide more flexibility when working with the variable or parameter.

  • Example: Consider the following code snippet:
```typescript
let a = 10; // Type of 'a' is number
let b = "hello"; // Type of 'b' is string
let c = true; // Type of 'c' is boolean
```

Here, the types of variables 'a', 'b', and 'c' are automatically inferred by TypeScript based on the values assigned to them. These types are widened to their respective broader types instead of being preserved as literals.

  • Advantages:
  • Allows for more flexibility when working with literal values.
  • Enables the use of variables or parameters in a wider range of contexts.

'Numeric Separators' in TypeScript:

'Numeric Separators' in TypeScript refer to a feature that allows for improved readability of numeric literals by using underscore (_) as a separator between groups of digits. This feature is particularly useful when working with large numbers that may be difficult to read or understand at a glance.

  • Example:
```typescript
let population = 7_594_000_000; // Equivalent to 7,594,000,000
```

Here, the underscore (_) serves as a visual separator and does not affect the value of the numeric literal. It improves readability and makes the number easier to comprehend.

  • Advantages:
  • Enhances code readability by making large numbers more understandable.
  • Minimizes the risk of errors when dealing with complex or lengthy numeric literals.

Explain the 'unknown' vs 'any' types in TypeScript.

Summary:

Detailed Answer:

'unknown' vs 'any' types in TypeScript

In TypeScript, the 'unknown' and 'any' types are used to handle situations when the type of a value is not known at compile-time. However, there are significant differences between these two types and when to use them.

The 'any' type allows for values of any type and essentially disables type checking, making it less desirable compared to other more specific types. It is often used when the type of a variable is uncertain or when working with external JavaScript libraries without type definitions.

On the other hand, the 'unknown' type was introduced in TypeScript 3.0 as a safer alternative to 'any'. With 'unknown', the type is still not known, but you cannot perform any operations on an 'unknown' value without first narrowing its type explicitly or using type assertions. This enforces type safety and prevents unintentional misuse.

  • Example: Declaring variables with 'any' and 'unknown'
let value1: any;
let value2: unknown;
  • Type assignment: Using 'any' and 'unknown'
value1 = 10; // No type checking
value2 = 10; // No type checking

const result1 = value1.toFixed(2); // No compiler error
const result2 = value2.toFixed(2); // Compiler error: Object is of type 'unknown'

As shown in the example, using 'any' allows for any operation to be performed on the value without restriction, while using 'unknown' enforces strict type checking and prevents potential runtime errors. It is generally recommended to use 'unknown' instead of 'any' whenever possible to ensure type safety.

In summary, both 'unknown' and 'any' provide flexibility in handling uncertain types in TypeScript. However, 'unknown' provides better safety by enforcing explicit type narrowing or type assertions, reducing the likelihood of runtime errors. It is generally considered a safer alternative to 'any' and should be preferred in TypeScript codebases.