7. Have you used TypeScript's advanced features such as decorators, generics, or conditional types in your projects?

Basic

7. Have you used TypeScript's advanced features such as decorators, generics, or conditional types in your projects?

Overview

TypeScript, an extension of JavaScript, introduces strong typing and advanced features like decorators, generics, and conditional types. These features enable developers to write more robust and maintainable code by providing compile-time type checks and meta-programming capabilities. Understanding and utilizing these advanced features are crucial for developing complex applications and contributing to TypeScript projects effectively.

Key Concepts

  • Decorators: Functions that modify the behavior of classes, methods, or properties.
  • Generics: Enable classes, interfaces, and methods to operate with various types while maintaining consistency.
  • Conditional Types: Allow types to be chosen based on conditions, making types more flexible.

Common Interview Questions

Basic Level

  1. What is a TypeScript decorator, and can you give a basic example?
  2. How do generics improve type safety in TypeScript?

Intermediate Level

  1. Explain how conditional types work in TypeScript with an example.

Advanced Level

  1. Discuss how to use decorators for performance optimization in a TypeScript project.

Detailed Answers

1. What is a TypeScript decorator, and can you give a basic example?

Answer: A decorator is a special kind of declaration that can be attached to a class declaration, method, accessor, property, or parameter. Decorators use the form @expression, where expression must evaluate to a function that will be called at runtime with information about the decorated declaration.

Key Points:
- Decorators provide a way to add annotations and a meta-programming syntax for class declarations and members.
- They can be used for logging, performance measurement, setting metadata, and validation.
- TypeScript must be configured to enable experimental support for decorators.

Example:

// Enable experimental support for decorators in tsconfig.json

function logged(target, name, descriptor) {
    const original = descriptor.value;
    descriptor.value = function(...args) {
        console.log(`Calling ${name} with`, args);
        return original.apply(this, args);
    };
    return descriptor;
}

class ExampleClass {
    @logged
    method(arg) {
        return "Logged " + arg;
    }
}

const e = new ExampleClass();
e.method("test");

2. How do generics improve type safety in TypeScript?

Answer: Generics provide a way to create reusable components that work with any data type, not just one. This allows users to consume these components and use their own types without compromising on type safety.

Key Points:
- Promote code reusability without sacrificing type safety.
- They can apply to classes, interfaces, and functions.
- Enable the creation of data structures that work with any type.

Example:

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

let outputString = identity<string>("myString");
let outputNumber = identity<number>(100);

3. Explain how conditional types work in TypeScript with an example.

Answer: Conditional types select types based on a condition expressed as a type relationship test. They are written as T extends U ? X : Y, meaning that if T is assignable to U, the type is X, otherwise Y.

Key Points:
- They enable type inference in templates and higher-order operations on types.
- Conditional types support complex type manipulation and composition.
- Useful for library authors and complex type transformations.

Example:

type NonNullable<T> = T extends null | undefined ? never : T;

// Using the NonNullable type
function process(input: NonNullable<string>) {
    // input is guaranteed not to be null or undefined here
    console.log(input);
}

process("Hello"); // Works
process(null); // TypeScript error

4. Discuss how to use decorators for performance optimization in a TypeScript project.

Answer: Decorators can be strategically used to enhance performance by implementing techniques such as memoization, lazy loading, and logging performance metrics. They can intercept and modify the behavior of class methods, properties, or accessors without altering the original code base, making them ideal for adding performance optimizations.

Key Points:
- Memoization stores the results of expensive function calls and returns the cached result when the same inputs occur again.
- Lazy loading defers initialization of objects until they are needed.
- Performance metrics can be gathered by adding logging decorators around critical paths or functions.

Example:

function memoize(target, name, descriptor) {
    const original = descriptor.value;
    const cache = new Map();
    descriptor.value = function(...args) {
        const key = JSON.stringify(args);
        if (!cache.has(key)) {
            const result = original.apply(this, args);
            cache.set(key, result);
            return result;
        }
        return cache.get(key);
    };
    return descriptor;
}

class Example {
    @memoize
    computeExpensive(value: number): number {
        // Simulate expensive operation
        console.log(`Computing for ${value}`);
        return Math.pow(value, value);
    }
}

const example = new Example();
example.computeExpensive(2); // Computed
example.computeExpensive(2); // Cached

This approach can significantly improve the performance of applications by reducing redundant computations, delaying unnecessary processing, and providing insights into critical operations.