New 120 TypeScript Interview Question

Introduction
TypeScript is a popular programming language that enhances JavaScript with static typing and additional features. If you’re preparing for a TypeScript interview, it’s essential to familiarize yourself with some common questions. These questions often focus on TypeScript’s key concepts, such as types, interfaces, classes, modules, and generics. Additionally, you may be asked about the benefits of using TypeScript, its compatibility with JavaScript, and its integration with popular frameworks like Angular or React. By understanding these fundamentals and practicing related questions, you’ll be better prepared to showcase your TypeScript knowledge and excel in your interview.
Basic Questions
1. What is TypeScript and why should one use it?
TypeScript is a statically typed superset of JavaScript that adds optional static typing to the language. It allows developers to write more robust, maintainable, and scalable code by catching type-related errors during development. TypeScript code is transpiled into JavaScript, which means it can run in any browser or Node.js environment.
Example:
// TypeScript code
function addNumbers(a: number, b: number): number {
return a + b;
}
const result: number = addNumbers(5, 10);
console.log(result); // Output: 15
In this example, we define a function addNumbers
that takes two parameters a
and b
, both of type number
, and returns a value of type number
. By specifying types for parameters and return values, TypeScript helps catch type-related errors early in the development process.
2. List the built-in types in TypeScript.
TypeScript includes several built-in types:
number
: Represents numeric values like integers or floating-point numbers.string
: Represents textual data, enclosed in single or double quotes.boolean
: Represents true/false values.object
: Represents a non-primitive type (anything that is notnumber
,string
,boolean
,symbol
,null
, orundefined
).array
: Represents an array of values of a specific type.tuple
: Represents an array with a fixed number of elements, where each element may have a different type.enum
: Represents a set of named constants.any
: Represents a dynamic type that can hold any value, similar to regular JavaScript behavior.void
: Represents the absence of a value (usually used as a return type for functions that don’t return anything).null
andundefined
: Represents null and undefined values, respectively.never
: Represents a type for values that never occur (e.g., return type for functions that throw exceptions or have infinite loops).
3. Explain generics in TypeScript.
Generics in TypeScript allow us to create reusable components or functions that work with different types while maintaining type safety. They provide a way to parameterize types and create flexible code that can be used with various data types.
Example:
// Generic function to reverse an array
function reverseArray<T>(arr: T[]): T[] {
return arr.reverse();
}
const numbers: number[] = [1, 2, 3, 4, 5];
const reversedNumbers: number[] = reverseArray(numbers);
const strings: string[] = ["apple", "banana", "orange"];
const reversedStrings: string[] = reverseArray(strings);
console.log(reversedNumbers); // Output: [5, 4, 3, 2, 1]
console.log(reversedStrings); // Output: ["orange", "banana", "apple"]
In this example, we define a generic function reverseArray
that takes an array arr
of type T
and returns an array of the same type T
. The function can be used with both number
and string
arrays without having to rewrite the logic for each data type.
4. What are Modules in TypeScript?
Modules in TypeScript are used to organize code into reusable and independent units. Each file is considered a separate module, and you can use the export
keyword to expose elements (variables, functions, classes) that can be imported and used in other modules.
Example:
mathOperations.ts
export function add(a: number, b: number): number {
return a + b;
}
export function subtract(a: number, b: number): number {
return a - b;
}
app.ts
import { add, subtract } from "./mathOperations";
const result1 = add(5, 3);
const result2 = subtract(10, 4);
console.log(result1); // Output: 8
console.log(result2); // Output: 6
In this example, we have two files: mathOperations.ts
containing math functions, and app.ts
importing and using these functions. The export
keyword allows us to make the add
and subtract
functions accessible in other files, and the import
keyword lets us bring these functions into the app.ts
file.
5. What is TypeScript, and why would I use it in place of JavaScript?
// TypeScript code with type annotations
function calculateArea(radius: number): number {
return Math.PI * radius * radius;
}
const circleRadius: number = 5;
const area: number = calculateArea(circleRadius);
console.log(area); // Output: 78.53981633974483
In this example, we have a function calculateArea
that takes the radius
of a circle as a parameter and returns its area. The type annotations (: number
) provide type safety, ensuring that the function is called with a numeric value and that the result is also of type number
. If we mistakenly pass a non-numeric value, TypeScript will catch the error during development, preventing potential runtime issues.
6. Do we need to compile TypeScript files, and why?
Yes, TypeScript code needs to be compiled into regular JavaScript before it can be executed in a browser or Node.js environment. The reason for this is that browsers and Node.js only understand JavaScript, not TypeScript. The TypeScript compiler (tsc) converts TypeScript code into JavaScript code, preserving the intended behavior and type annotations.
During compilation, TypeScript performs various tasks:
- Type Checking: It checks the code for type errors and ensures type safety.
- Transpilation: It transforms modern TypeScript features into compatible JavaScript code that works across different platforms and browsers.
- ECMAScript Targeting: It allows targeting specific ECMAScript versions based on the project’s requirements (e.g., ES5, ES6, etc.).
- Bundling: It can bundle multiple TypeScript files into a single output file for optimization.
7. What are the benefits of TypeScript?
- Enhanced Type Safety: TypeScript adds static typing, catching type-related errors during development, leading to more reliable and robust code.
- Improved Tooling: TypeScript provides better tooling support with code editors and IDEs, offering features like autocompletion, type inference, and code navigation.
- Readability and Maintainability: The use of type annotations makes the codebase more self-documenting, enhancing readability and maintainability.
- Code Refactoring: With type information, refactoring becomes more comfortable and safer as the compiler can find references to the renamed elements.
- Easier Collaboration: Type annotations make it easier for team members to understand each other’s code, reducing communication overhead.
- Catching Errors Early: The type checking during development catches many potential bugs before the code reaches the runtime environment.
- IDE Support: Editors and IDEs provide helpful suggestions and auto-completions due to TypeScript’s static typing, improving developer productivity.
- Compatibility: TypeScript code can be transpiled to target specific ECMAScript versions, making it compatible with various browsers and Node.js environments.
8. How to call the base class constructor from a child class in TypeScript?
In TypeScript, to call the base class constructor from a child class, we use the super
keyword within the child class constructor.
Example:
class Animal {
constructor(private name: string) {}
makeSound() {
console.log("Animal sound");
}
}
class Dog extends Animal {
constructor(name: string, private breed: string) {
super(name); // Calling the base class constructor
}
makeSound() {
console.log("Bark!");
}
displayInfo() {
console.log(`Name: ${this.name}, Breed: ${this.breed}`);
}
}
const dog1 = new Dog("Buddy", "Golden Retriever");
dog1.makeSound(); // Output: Bark!
dog1.displayInfo(); // Output: Name: Buddy, Breed: Golden Retriever
In this example, we have a base class Animal
with a constructor that takes a name
parameter. The child class Dog
extends Animal
, and in its constructor, we call the base class constructor using super(name)
. This ensures that the name
property from the base class is properly initialized.
10. How to perform string interpolation in TypeScript?
String interpolation in TypeScript can be done using template literals, which are enclosed in backticks () and allow embedding expressions using ${expression}
syntax.
Example:
function greet(name: string): string {
return `Hello, ${name}!`;
}
const personName: string = "Alice";
const greeting: string = greet(personName);
console.log(greeting); // Output: Hello, Alice!
In this example, we use string interpolation to embed the name
variable inside the greeting message using ${name}
.
11. What is the difference between .ts and .tsx extensions in TypeScript?
File Extension | Purpose | JSX Support |
---|---|---|
.ts | Used for regular TypeScript files without JSX syntax | No |
.tsx | Used for TypeScript files with JSX syntax | Yes |
The .ts
extension is used for regular TypeScript files that do not contain JSX syntax. The .tsx
extension is used for TypeScript files that include JSX syntax, which is used for working with React components and other JSX-based frameworks or libraries.
12. What is the difference between Classes and Interfaces in TypeScript?
Classes:
- Classes are blueprint for creating objects with properties and methods.
- They can have constructors to initialize object properties.
- Classes support inheritance and encapsulation.
Example:
class Animal {
constructor(private name: string) {}
makeSound() {
console.log("Animal sound");
}
}
const dog = new Animal("Buddy");
console.log(dog.name); // Error: 'name' is private and only accessible within class 'Animal'
Interfaces:
- Interfaces define the structure of an object.
- They cannot be instantiated directly and do not contain implementation details.
- Interfaces support multiple inheritance, allowing a class to implement multiple interfaces.
Example:
interface Animal {
name: string;
makeSound(): void;
}
class Dog implements Animal {
constructor(public name: string) {}
makeSound() {
console.log("Bark!");
}
}
const dog = new Dog("Buddy");
console.log(dog.name); // Output: Buddy
In this example, we have a Animal
class with a constructor and a makeSound
method. On the other hand, we have an Animal
interface with the same structure. While the class defines the implementation details, the interface defines the contract that a class must adhere to.
13. What is an Interface in TypeScript?
In TypeScript, an interface is a structural contract that defines the shape of an object. It describes the properties and methods an object must have without specifying the implementation.
Example:
interface Person {
name: string;
age: number;
sayHello(): void;
}
class Student implements Person {
constructor(public name: string, public age: number) {}
sayHello() {
console.log(`Hello, my name is ${this.name}, and I'm ${this.age} years old.`);
}
}
const student = new Student("Alice", 25);
student.sayHello(); // Output: Hello, my name is Alice, and I'm 25 years old.
In this example, we define an interface Person
with name
, age
, and sayHello
properties. The Student
class implements the Person
interface, ensuring that it adheres to the contract specified by the interface.
14. What are Decorators in TypeScript?
Decorators in TypeScript are a feature inspired by the ESNext proposal and are used to modify or enhance classes, methods, properties, or parameters. They are prefixed with an @
symbol and are followed by a decorator factory function.
Example:
function myDecorator(target: any, propertyKey: string) {
console.log(`Decorating property ${propertyKey} of class ${target.constructor.name}`);
}
class MyClass {
@myDecorator
myProperty: string = "Hello, decorators!";
}
const obj = new MyClass();
In this example, we define a decorator function myDecorator
, which logs information about the property being decorated. The decorator is applied to the myProperty
of the MyClass
class. When an instance of MyClass
is created, the decorator is executed, and the output will be: “Decorating property myProperty of class MyClass”.
15. What are the differences between TypeScript and JavaScript?
TypeScript | JavaScript |
---|---|
Statically typed language | Dynamically typed language |
Supports optional static typing | No static typing (all types determined at runtime) |
Provides type checking during development | Type checking only at runtime (may cause runtime errors) |
Allows the use of interfaces and generics | No interfaces, limited support for generics |
Supports modern ECMAScript features | Supports ECMAScript features based on the environment |
Needs compilation to JavaScript for execution | Can be directly run in browsers and Node.js environment |
Enhanced tooling support | Limited tooling support |
16. When to use interfaces and when to use classes in TypeScript?
Interfaces:
- Use interfaces when you need to describe the structure of an object and its contract.
- Use interfaces when you want to enforce a specific shape on objects but don’t need implementation details.
- Interfaces are useful when defining contracts that multiple classes can implement.
Example:
interface Shape {
calculateArea(): number;
}
class Circle implements Shape {
constructor(private radius: number) {}
calculateArea() {
return Math.PI * this.radius * this.radius;
}
}
class Square implements Shape {
constructor(private sideLength: number) {}
calculateArea() {
return this.sideLength * this.sideLength;
}
}
const circle = new Circle(5);
console.log(circle.calculateArea()); // Output: 78.53981633974483
const square = new Square(4);
console.log(square.calculateArea()); // Output: 16
Classes:
- Use classes when you need to create objects with both properties and methods.
- Use classes when you need to encapsulate data and behavior within the same unit.
- Classes support constructors, inheritance, and access modifiers (e.g., private, protected).
Example:
class Person {
constructor(private name: string,
private age: number) {}
sayHello() {
console.log(`Hello, my name is ${this.name}, and I'm ${this.age} years old.`);
}
}
const person = new Person("Alice", 25);
person.sayHello(); // Output: Hello, my name is Alice, and I'm 25 years old.
In this example, we use interfaces to define the Shape
contract with a method calculateArea
, and then we implement that interface in the Circle
and Square
classes. On the other hand, we use a class to create a Person
object with properties and methods, encapsulating data and behavior within the same unit.
17. How to implement class constants in TypeScript?
In TypeScript, class constants can be implemented using the static
keyword within the class. These constants belong to the class and not to individual instances.
Example:
class MathOperations {
static readonly PI: number = 3.14159;
static calculateArea(radius: number): number {
return MathOperations.PI * radius * radius;
}
}
const circleRadius: number = 5;
const area: number = MathOperations.calculateArea(circleRadius);
console.log(area); // Output: 78.53975
In this example, we define a MathOperations
class with a PI
constant marked as readonly
and static
. The readonly
modifier ensures that the constant cannot be modified after it is initialized. We can access the constant using the class name without creating an instance of the class.
18. How could you check null and undefined in TypeScript?
In TypeScript, you can check for null
and undefined
using strict equality (===
or !==
) or optional chaining (?.
) for safer property access.
Example:
function processValue(value: string | null | undefined): void {
if (value === null || value === undefined) {
console.log("Value is null or undefined.");
} else {
console.log("Value is:", value);
}
}
const stringValue: string | null = "Hello";
processValue(stringValue); // Output: Value is: Hello
const nullValue: null = null;
processValue(nullValue); // Output: Value is null or undefined
const undefinedValue: undefined = undefined;
processValue(undefinedValue); // Output: Value is null or undefined
In this example, we have a function processValue
that takes a parameter of type string
, null
, or undefined
. We check for null
and undefined
using strict equality (===
), and if the value is either null
or undefined
, we log an appropriate message.
19. Does TypeScript support all object-oriented principles?
Yes, TypeScript supports all object-oriented principles like encapsulation, inheritance, and polymorphism.
Example:
// Encapsulation: Using private and public access modifiers
class Circle {
constructor(private radius: number) {}
getRadius(): number {
return this.radius;
}
setRadius(radius: number): void {
if (radius >= 0) {
this.radius = radius;
}
}
calculateArea(): number {
return Math.PI * this.radius * this.radius;
}
}
const circle = new Circle(5);
circle.setRadius(10);
console.log(circle.getRadius()); // Output: 10
console.log(circle.calculateArea()); // Output: 314.1592653589793
// Inheritance
class Square extends Circle {
constructor(radius: number, private sideLength: number) {
super(radius);
}
calculateArea(): number {
return this.sideLength * this.sideLength;
}
}
const square = new Square(5, 4);
console.log(square.calculateArea()); // Output: 16
// Polymorphism
function printArea(shape: Circle | Square): void {
console.log("Area:", shape.calculateArea());
}
printArea(circle); // Output: Area: 314.1592653589793
printArea(square); // Output: Area: 16
In this example, we demonstrate the object-oriented principles of encapsulation by using private and public access modifiers in the Circle
class. We showcase inheritance by creating a Square
class that extends Circle
, inheriting its properties and methods. Finally, we demonstrate polymorphism by using the same function printArea
to calculate and display the area of both a Circle
and a Square
.
20. Which object-oriented terms are supported by TypeScript?
TypeScript supports the following object-oriented terms:
- Classes: Used to define blueprints for creating objects with properties and methods.
- Objects: Instances created from classes.
- Properties: Data members or attributes of an object.
- Methods: Functions that belong to objects and perform specific actions.
- Inheritance: The ability of a class to inherit properties and methods from another class.
- Encapsulation: The bundling of data and methods within a class to hide implementation details.
- Polymorphism: The ability of different classes to be treated as instances of a common superclass.
21. What are getters/setters in TypeScript?
Getters and setters are special methods in TypeScript (and JavaScript) used to access and modify the properties of an object. They provide a way to control how properties are accessed and set.
Example:
class Person {
private _age: number;
constructor(private _name: string, age: number) {
this._age = age;
}
// Getter for 'name' property
get name(): string {
return this._name;
}
// Setter for 'name' property
set name(newName: string) {
this._name = newName.trim();
}
// Getter for 'age' property
get age(): number {
return this._age;
}
// Setter for 'age' property
set age(newAge: number) {
if (newAge >= 0) {
this._age = newAge;
}
}
}
const person = new Person("Alice", 30);
console.log(person.name); // Output: Alice
person.name = " Bob ";
console.log(person.name); // Output: Bob
console.log(person.age); // Output: 30
person.age = -5; // Setter won't update the age because it's negative
console.log(person.age); // Output: 30
In this example, we define a Person
class with private properties _name
and _age
. We provide getter and setter methods for each property, allowing controlled access and modification of the properties. The name
setter, for example, trims any leading or trailing spaces from the provided value before updating the _name
property.
22. Could we use TypeScript on the backend, and how?
Yes, TypeScript can be used on the backend, allowing developers to write server-side applications using TypeScript. Here’s how you can do it:
- Node.js Environment: TypeScript can be used with Node.js, as both TypeScript and Node.js use the same JavaScript runtime. You can install TypeScript globally using npm and compile TypeScript files to JavaScript using the TypeScript compiler (tsc).
- Express.js Framework: TypeScript can be used with Express.js, a popular web application framework for Node.js. By using TypeScript, you get the benefits of static typing, better IDE support, and improved code organization.
- Nest.js Framework: Nest.js is a progressive Node.js framework built with TypeScript. It provides a solid architectural design and leverages TypeScript decorators for dependency injection and routing.
Example:
// server.ts
import express from "express";
const app = express();
const port = 3000;
app.get("/", (req, res) => {
res.send("Hello, TypeScript on the backend!");
});
app.listen(port, () => {
console.log(`Server is running on http://localhost:${port}`);
});
To run the TypeScript backend, compile the TypeScript file (tsc server.ts
) to generate the JavaScript file and then run the JavaScript file using Node.js (node server.js
).
23. What is a TypeScript Map file?
In TypeScript, a Map file (with a .map
extension) is a separate file generated during the compilation process. It contains mapping information that connects the generated JavaScript code to the original TypeScript code. This helps in debugging by allowing the debugger to map the executed JavaScript code back to the corresponding TypeScript code.
The TypeScript compiler generates a .map
file when the --sourceMap
option is set to true in the tsconfig.json
file or when using the --sourceMap
command-line flag.
Example:
tsconfig.json
{
"compilerOptions": {
"sourceMap": true,
"outDir": "dist"
}
}
After running the TypeScript compiler (tsc
), you’ll have the following files:
main.ts
function add(a: number, b: number): number {
return a + b;
}
const result: number = add(5, 10);
console.log(result);
main.js
function add(a, b) {
return a + b;
}
const result = add(5, 10);
console.log(result);
main.js.map
{"version":3,"file":"main.js","sourceRoot":"","sources":["main.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC"}
The .map
file contains JSON data that represents the mapping between the original main.ts
file and the generated main.js
file.
24. What is the difference between types String
and string
in TypeScript?
Type Name | Purpose | Type Definition |
---|---|---|
String | Represents a String object (primitive wrapper) | String('Hello') |
string | Represents a string primitive type | 'Hello' or new String() |
The String
type refers to the String object (wrapper object) in JavaScript, whereas string
refers to the string primitive type. It is recommended to use the string
type when working with string values as it is more natural and straightforward.
25. What is Type Erasure in TypeScript?
Type Erasure in TypeScript refers to the process of removing type annotations during the compilation process, leaving only the JavaScript code with no explicit type information. This is done to maintain compatibility with ECMAScript, which doesn’t support static types.
Example:
function addNumbers(a: number, b: number): number {
return a + b;
}
const result: number = addNumbers(5, 10);
console.log(result); // Output: 15
In this example, the TypeScript code contains type annotations, but after compilation, the resulting JavaScript code will have no type information:
function addNumbers(a, b) {
return a + b;
}
const result = addNumbers(5, 10);
console.log(result); // Output: 15
The type information (: number
) has been erased in the compiled JavaScript code.
26. How do we create an enum with string values?
To create an enum with string values in TypeScript, we can assign string literals to each enum member.
Example:
enum Color {
Red = "RED",
Green = "GREEN",
Blue = "BLUE",
}
function printColor(color: Color): void {
console.log("Selected color:", color);
}
printColor(Color.Green); // Output: Selected color: GREEN
In this example, we create an enum Color
with string values using string literals as the enum members. The printColor
function takes an argument of type Color
and logs the selected color.
27. What does the pipe (|
) mean in TypeScript?
In TypeScript, the pipe (|
) symbol is used to create a union type. A union type allows a variable or parameter to have multiple types.
Example:
function printResult(value: string | number): void {
console.log("Result:", value);
}
printResult("Hello"); // Output: Result: Hello
printResult(42); // Output: Result: 42
In this example, the printResult
function takes an argument of type string
or number
, allowing us to pass either a string or a number.
28. Describe what conditional types are in TypeScript?
Conditional types in TypeScript are used to create a type that depends on a condition. They use the infer
keyword to infer the type based on the condition. Conditional types are often used with generic types to create more flexible and reusable type definitions.
Example:
type IsNumber<T> = T extends number ? true : false;
const result1: IsNumber<42> = true;
const result2: IsNumber<"Hello"> = false;
console.log(result1); // Output: true
console.log(result2); // Output: false
In this example, we define a conditional type IsNumber
, which checks if the given type T
is a number
or not. If T
is a number
, the type IsNumber<T>
will be true
; otherwise, it will be false
.
29. How to make Arrays that can only be read, TypeScript?
In TypeScript, you can create read-only arrays using the ReadonlyArray
type or the readonly
modifier.
Example 1: Using ReadonlyArray
type
const readOnlyNumbers: ReadonlyArray<number> = [1, 2, 3, 4, 5];
readOnlyNumbers[0] = 10; // Error: Index signature in type 'readonly number[]' only permits reading
Example 2: Using readonly
modifier
const readOnlyColors: readonly string[] = ["red", "green", "blue"];
readOnlyColors[0] = "yellow"; // Error: Index signature in type 'readonly string[]' only permits reading
In both examples, we create read-only arrays using either ReadonlyArray<number>
or readonly string[]
. Attempting to modify the elements of these arrays will result in a compilation error.
30. How can we use optional chaining in TypeScript?
Optional chaining is a feature introduced in TypeScript 3.7, allowing you to safely access nested properties or methods without causing a runtime error if any intermediate value is null
or undefined
. It uses the ?.
operator.
Example:
interface Address {
city?: string;
postalCode?: string;
}
interface Person {
name: string;
age?: number;
address?: Address;
}
function getPostalCode(person: Person): string | undefined {
return person?.address?.postalCode;
}
const person1: Person = {
name: "Alice",
age: 30,
address: {
city: "New York",
postalCode: "10001",
},
};
const person2: Person = {
name: "Bob",
age: 25,
};
console.log(getPostalCode(person1)); // Output: 10001
console.log(getPostalCode(person2)); // Output: undefined
In this example, we define interfaces Address
and Person
. The function getPostalCode
safely accesses the postalCode
property using optional chaining. If any intermediate value (person
, address
, or postalCode
) is null
or undefined
, the function returns undefined
.
31. What is Optional Chaining in TypeScript?
Optional chaining is a feature introduced in TypeScript 3.7 that allows you to safely access nested properties or methods of an object without causing a runtime error if any intermediate value is null
or undefined
.
Example:
interface Address {
city?: string;
postalCode?: string;
}
interface Person {
name: string;
age?: number;
address?: Address;
}
function getPostalCode(person: Person): string | undefined {
return person?.address?.postalCode;
}
const person1: Person = {
name: "Alice",
age: 30,
address: {
city: "New York",
postalCode: "10001",
},
};
const person2: Person = {
name: "Bob",
age: 25,
};
console.log(getPostalCode(person1)); // Output: 10001
console.log(getPostalCode(person2)); // Output: undefined
In this example, we define interfaces Address
and Person
. The function getPostalCode
safely accesses the postalCode
property using optional chaining (?.
). If any intermediate value (person
, address
, or postalCode
) is null
or undefined
, the function returns undefined
.
32. What is the purpose of the Nullish Coalescing operator?
The Nullish Coalescing operator (??
) is a feature introduced in TypeScript (and JavaScript) to provide a default value for variables that are null
or undefined
. It returns the right-hand side operand if the left-hand side operand is null
or undefined
; otherwise, it returns the left-hand side operand.
Example:
const value1 = null;
const value2 = undefined;
const value3 = 0;
const result1 = value1 ?? "default"; // Output: "default"
const result2 = value2 ?? "default"; // Output: "default"
const result3 = value3 ?? "default"; // Output: 0
In this example, we use the Nullish Coalescing operator to provide default values for variables value1
and value2
, which are null
and undefined
, respectively. The operator returns the right-hand side operand "default"
for those cases.
33. What are assertion functions?
Assertion functions in TypeScript are user-defined functions used to assert or tell the TypeScript compiler that a certain type constraint is satisfied for a particular variable. They are useful when you know more about the type of a value than the TypeScript compiler does.
Example:
function assertNumber(val: any): asserts val is number {
if (typeof val !== "number") {
throw new Error("Assertion failed: Value is not a number");
}
}
function multiplyByTwo(value: unknown): number {
assertNumber(value); // Assert that 'value' is a number
return value * 2; // TypeScript knows 'value' is a number here
}
const result1 = multiplyByTwo(5); // Output: 10
const result2 = multiplyByTwo("hello"); // Runtime error: Assertion failed: Value is not a number
In this example, we define an assertion function assertNumber
that checks if the provided value is a number. If the condition is not met, it throws an error. The multiplyByTwo
function uses this assertion function to assert that the value
parameter is a number. This allows TypeScript to know that value
is a number when performing the multiplication operation.
34. Which access modifiers are implied when not specified?
In TypeScript, if an access modifier is not specified for a class member, it is implicitly considered public
. For constructors, it is implied to be public
.
Example:
class Person {
name: string; // Implied 'public'
constructor(name: string) {
this.name = name;
}
}
const person = new Person("Alice");
console.log(person.name); // Output: Alice
In this example, the name
property of the Person
class is not specified with an access modifier, so it is implicitly public
. This means that the name
property can be accessed and modified from outside the class instance.
35. Explain how and why we could use property decorators in TypeScript?
Property decorators in TypeScript are used to add extra behavior or metadata to class properties. They are applied to properties of a class and receive two parameters: the target object (prototype of the class) and the property name.
Example:
function uppercase(target: any, propertyName: string) {
let value: string;
const getter = function () {
return value;
};
const setter = function (newValue: string) {
value = newValue.toUpperCase();
};
Object.defineProperty(target, propertyName, {
get: getter,
set: setter,
enumerable: true,
configurable: true,
});
}
class Person {
@uppercase
name: string;
constructor(name: string) {
this.name = name;
}
}
const person = new Person("Alice");
console.log(person.name); // Output: ALICE
person.name = "Bob";
console.log(person.name); // Output: BOB
In this example, we define a property decorator uppercase
, which converts the assigned value to uppercase. When applied to the name
property of the Person
class, it transforms any assigned name to uppercase automatically.
36. What is Typings in TypeScript?
Typings in TypeScript refer to the declaration files (.d.ts
) that describe the shape of JavaScript libraries or modules. They provide type information for external code that does not have TypeScript typings by default.
Example:
custom-library.d.ts
declare module "custom-library" {
export function greet(name: string): string;
}
app.ts
import { greet } from "custom-library";
const greeting: string = greet("Alice");
console.log(greeting); // Output: Hello, Alice!
In this example, we have a custom library without TypeScript typings. We create a .d.ts
file, custom-library.d.ts
, where we declare the types for the functions or modules of the library. This allows TypeScript to provide type checking and better IDE support for the greet
function imported in app.ts
.
37. How can you allow classes defined in a module to be accessible outside of the module?
In TypeScript, by default, classes defined in a module are not accessible outside of the module. To allow classes to be accessible, you can use the export
keyword before the class declaration.
Example:
math-utils.ts
export class MathUtils {
static add(a: number, b: number): number {
return a + b;
}
}
app.ts
import { MathUtils } from "./math-utils";
const result: number = MathUtils.add(5, 10);
console.log(result); // Output: 15
In this example, we define a MathUtils
class in math-utils.ts
and use the export
keyword to make it accessible outside the module. We then import the class in app.ts
using the import
statement and use it to perform addition.
38. How TypeScript is optionally a statically typed language?
TypeScript is optionally a statically typed language because it allows developers to choose whether to use static types or not. TypeScript provides a way to define types for variables, parameters, and return values, but it also allows dynamic typing when types are not explicitly specified.
Developers can choose to:
- Explicitly Specify Types: By providing type annotations, TypeScript enforces static typing and performs type checking during development.
Example:
function add(a: number, b: number): number {
return a + b;
}
- Let TypeScript Infer Types: If type annotations are not provided, TypeScript can infer types from the assigned values and perform type checking based on the context.
Example:
function add(a, b) {
return a + b; // TypeScript infers the return type as 'number'
}
39. What is the default access modifier for members of a class in TypeScript?
In TypeScript, if an access modifier is not explicitly specified for a class member, it is implicitly considered public
.
Example:
class Person {
name: string; // Implied 'public'
constructor(name: string) {
this.name = name;
}
}
const person = new Person("Alice");
console.log(person.name); // Output: Alice
In this example, the name
property of the Person
class is not specified with an access modifier, so it is implicitly considered public
. This means that the name
property can be accessed and modified from outside the class instance.
40. What are different components of TypeScript?
The different components of TypeScript are as follows:
- TypeScript Compiler (
tsc
): The TypeScript compiler,tsc
, converts TypeScript code (.ts
files) into JavaScript code (.js
files) and optionally generates source map files (.map
) for debugging purposes. - TypeScript Language Service: The TypeScript Language Service provides rich IDE support, offering features like code completion, code navigation, and error checking while typing. It enhances the development experience in TypeScript-aware editors and IDEs.
- Declaration Files (.d.ts): Declaration files (
.d.ts
) contain type information for existing JavaScript libraries and modules. They allow TypeScript to understand and provide type checking for external JavaScript code. - TypeScript Language Specification: The TypeScript language specification defines the syntax, semantics, and behavior of the TypeScript language. It serves as the official reference for the language features and rules.
- TypeScript Playground: The TypeScript Playground is an online tool provided by the TypeScript team that allows developers to write and experiment with TypeScript code in a web-based environment. It includes a code editor and shows the resulting JavaScript output.
Intermediate Questions
41. How to use external plain JavaScript libraries in TypeScript?
To use external plain JavaScript libraries in TypeScript, you typically need to install the TypeScript type definitions for those libraries using DefinitelyTyped or other community-driven type definition repositories. These type definitions provide TypeScript with information about the library’s types, allowing for better type checking and IntelliSense support.
Here’s a step-by-step example using the popular library “lodash”:
- Install the library and its type definitions:
npm install lodash
npm install @types/lodash --save-dev
- Create a TypeScript file (e.g., “app.ts”) and import the library as follows:
import * as _ from 'lodash';
// Now you can use lodash functions with type support
const numbers: number[] = [1, 2, 3, 4, 5];
const sum = _.sum(numbers);
console.log(sum); // Output: 15
42. Does TypeScript support function overloading?
Yes, TypeScript supports function overloading. Function overloading allows you to define multiple function signatures for a single function based on different parameter types or numbers of parameters.
Here’s an example of function overloading:
function greet(name: string): string;
function greet(age: number): string;
function greet(arg: any): string {
if (typeof arg === 'string') {
return `Hello, ${arg}!`;
} else if (typeof arg === 'number') {
return `You are ${arg} years old.`;
} else {
throw new Error('Invalid argument type.');
}
}
console.log(greet('John')); // Output: Hello, John!
console.log(greet(30)); // Output: You are 30 years old.
In this example, we have two function signatures for the greet
function: one that takes a string argument and another that takes a number argument. The actual function implementation handles both cases.
43. What is the difference between Private and Protected variables in TypeScript?
In TypeScript, private
and protected
are access modifiers used to control the visibility of class members.
private
: When a member is marked asprivate
, it can only be accessed within the class where it is defined. It is not accessible from outside the class, including its subclasses.
class MyClass {
private privateVar: number = 10;
private privateMethod() {
console.log('This is a private method.');
}
}
const instance = new MyClass();
console.log(instance.privateVar); // Error: Property 'privateVar' is private and only accessible within class 'MyClass'.
instance.privateMethod(); // Error: Property 'privateMethod' is private and only accessible within class 'MyClass'.
protected
: When a member is marked asprotected
, it can be accessed within the class where it is defined and its subclasses.
class ParentClass {
protected protectedVar: number = 20;
protected protectedMethod() {
console.log('This is a protected method.');
}
}
class ChildClass extends ParentClass {
public accessProtected() {
console.log(this.protectedVar); // Accessible in subclass
this.protectedMethod(); // Accessible in subclass
}
}
const childInstance = new ChildClass();
childInstance.accessProtected(); // Output: 20, This is a protected method.
console.log(childInstance.protectedVar); // Error: Property 'protectedVar' is protected and only accessible within class 'ParentClass' and its subclasses.
childInstance.protectedMethod(); // Error: Property 'protectedMethod' is protected and only accessible within class 'ParentClass' and its subclasses.
44. What is the difference between enum and const enums?
In TypeScript, both enum
and const enum
are used to define a set of named constants. However, there is a difference in how they are emitted to JavaScript code.
enum
: When you define anenum
in TypeScript, it generates a JavaScript object at runtime that maps the enum members to their numeric values. Enums support both numeric and string values.
enum Color {
Red,
Green,
Blue,
}
console.log(Color.Red); // Output: 0
console.log(Color[0]); // Output: Red
const enum
: When you define aconst enum
, it is removed during the transpilation process, and its values are inlined directly into the generated JavaScript code. This results in more efficient code but removes the object mapping present in regular enums.const enum
can only have numeric values.
const enum Fruit {
Apple = 1,
Orange = 2,
Banana = 3,
}
console.log(Fruit.Apple); // Output: 1
// console.log(Fruit[1]); // Error: 'Fruit' only refers to a value, but is being used as a type here.
The const enum
example will not have an object mapping like enum
does, and accessing enum members using reverse lookup (e.g., Fruit[1]
) will result in a compilation error.
45. Why do we need to use the abstract keyword for classes and their methods in TypeScript?
The abstract
keyword is used to define abstract classes and methods in TypeScript. An abstract class cannot be instantiated directly; instead, it serves as a blueprint for its subclasses to implement their own versions of abstract methods.
Here’s an example of using the abstract
keyword:
abstract class Shape {
abstract calculateArea(): number;
sayHello(): void {
console.log('Hello from Shape!');
}
}
class Circle extends Shape {
constructor(private radius: number) {
super();
}
calculateArea(): number {
return Math.PI * this.radius ** 2;
}
}
const circle = new Circle(5);
console.log(circle.calculateArea()); // Output: 78.53981633974483
circle.sayHello(); // Output: Hello from Shape!
In this example, the Shape
class is marked as abstract
, and it has an abstract method calculateArea()
. The Circle
class extends Shape
and provides its implementation of the calculateArea()
method. The Circle
class can be instantiated, but the Shape
class cannot, as it is abstract.
46. What is Structural Typing?
Structural Typing, also known as “duck typing,” is a type system used by TypeScript where the type compatibility is based on the shape or structure of the types, rather than their explicit declaration or inheritance. If two types have compatible structures, TypeScript considers them to be assignable to each other, regardless of their names.
Here’s an example of Structural Typing:
interface Point2D {
x: number;
y: number;
}
interface
Shape {
name: string;
draw(): void;
}
function printCoordinates(point: Point2D) {
console.log(`Coordinates: x=${point.x}, y=${point.y}`);
}
const point: Point2D = { x: 10, y: 20 };
printCoordinates(point); // Output: Coordinates: x=10, y=20
const circle: Shape = {
name: 'Circle',
draw: () => console.log('Drawing a circle.'),
};
printCoordinates(circle); // Output: Coordinates: x=undefined, y=undefined
In this example, even though circle
doesn’t strictly conform to the Point2D
interface, it has the required x
and y
properties. TypeScript allows circle
to be passed to printCoordinates
because they have compatible shapes.
47. What is Mixin Class in TypeScript?
In TypeScript, a Mixin is a way to add behavior to a class by combining the features of multiple classes without using traditional inheritance. It’s achieved by creating classes with specific behaviors and then mixing those behaviors into other classes.
Here’s an example of a Mixin:
// Mixin classes
class Printable {
print(): void {
console.log('Printing...');
}
}
class Loggable {
log(): void {
console.log('Logging...');
}
}
// Target class
class Document implements Printable, Loggable {
print: () => void;
log: () => void;
constructor(private content: string) {}
// Implement methods from mixins
print: Printable['print'] = Printable.prototype.print;
log: Loggable['log'] = Loggable.prototype.log;
getContent(): string {
return this.content;
}
}
const document = new Document('This is a document.');
document.print(); // Output: Printing...
document.log(); // Output: Logging...
console.log(document.getContent()); // Output: This is a document.
In this example, we define two Mixin classes, Printable
and Loggable
, each providing specific behavior (print
and log
, respectively). The Document
class implements both mixins, effectively combining their functionality into the Document
class.
48. What is the unique symbol used for?
Unique symbols in TypeScript are used to create unique and immutable values that can be used as property keys in objects. They help prevent accidental name collisions when defining properties on objects.
Here’s an example of using unique symbols:
// Create a unique symbol
const userIdSymbol = Symbol('user_id');
// Define an object with a property using the unique symbol
const user = {
name: 'John Doe',
[userIdSymbol]: 12345,
};
console.log(user.name); // Output: John Doe
console.log(user[userIdSymbol]); // Output: 12345
// Trying to access the property using a string will not work
console.log(user['user_id']); // Output: undefined
// Using a for...in loop won't show the symbol property
for (const key in user) {
console.log(key); // Output: name
}
In this example, we create a unique symbol userIdSymbol
and use it as a property key in the user
object. The symbol property is accessible using the symbol itself but not with the string 'user_id'
. Also, the for...in
loop does not include the symbol property, protecting it from accidental enumeration.
49. How to make a read-only tuple type in TypeScript?
In TypeScript, you can create a read-only tuple type by using the readonly
modifier in combination with square brackets []
to define a tuple type. Once defined as readonly
, the tuple elements cannot be modified after initialization.
Here’s an example of a read-only tuple type:
// Define a read-only tuple type
type Point = readonly [number, number];
// Create a read-only tuple
const point: Point = [10, 20];
// Attempt to modify the tuple (will result in a compilation error)
// point[0] = 5; // Error: Index signature in type 'readonly [number, number]' only permits reading
// point.push(30); // Error: Property 'push' does not exist on type 'readonly [number, number]'
// Access elements
console.log(point[0]); // Output: 10
console.log(point[1]); // Output: 20
In this example, the Point
type is defined as a read-only tuple type with two elements of type number
. When creating the point
variable, we initialize it with a specific tuple. Any attempt to modify the tuple, like assigning new values or using methods like push
, will result in a compilation error.
50. Explain Project References and their benefits.
Project References is a feature in TypeScript that allows breaking up large TypeScript projects into smaller, manageable pieces and improves build times and developer experience. The benefits of using Project References include:
- Improved Build Times: By splitting the project into smaller sub-projects, TypeScript can perform incremental compilation for individual sub-projects, reducing build times when only specific parts of the codebase change.
- Parallel Compilation: When using Project References, TypeScript can compile sub-projects in parallel, leveraging multiple CPU cores, further speeding up the build process.
- Better Isolation: Each sub-project can have its own configuration and dependencies, making it easier to manage and isolate concerns.
- Avoiding Duplicate Work: If multiple sub-projects depend on the same codebase, TypeScript intelligently reuses the compiled output, avoiding duplicate compilation.
- Enhanced Editor Experience: Editors like Visual Studio Code can benefit from Project References to provide accurate IntelliSense and type checking within each sub-project.
- Incremental Refactoring: Project References facilitate incremental refactoring, as changes in one sub-project can be reflected across dependent sub-projects, ensuring consistency.
- Clearer Project Structure: The use of Project References enforces a clear project structure and dependency hierarchy, leading to better organization and maintainability.
51. What are the use cases for a const assertion?
A const assertion is used in TypeScript to indicate that a value should be treated as a literal constant when inferring types. It’s denoted by appending as const
to a value or expression.
Use cases for const assertions include:
- Creating Immutable Arrays:
const colors = ['red', 'green', 'blue'] as const;
// colors: readonly ['red', 'green', 'blue']
- Creating Readonly Objects:
const person = {
name: 'John',
age: 30,
} as const;
// person: { readonly name: "John"; readonly age: 30; }
- Preventing Narrowing of Literal Types:
const value = 'Hello' as const;
let newValue: typeof value; // Without as const, newValue would be of type "string".
- Enhancing Discriminated Unions:
type Shape =
| { type: 'circle'; radius: number }
| { type: 'rectangle'; width: number; height: number }
| { type: 'square'; sideLength: number };
const
circle: Shape = { type: 'circle', radius: 5 } as const;
In this example, the const assertion
ensures that the circle
object is treated as a literal type with the property values 'circle'
and 5
. Without as const
, TypeScript would infer a wider type for circle
.
52. How to choose between never, unknown, and any in TypeScript?
In TypeScript, the choice between never
, unknown
, and any
depends on the desired level of type safety and information provided by the type.
any
: Useany
when you want to opt-out of type checking and provide maximum flexibility. It essentially turns off TypeScript’s type checking for the variable.
let data: any = 10;
data = 'Hello';
data = true;
unknown
: Useunknown
when you have data of an unknown type and need type safety. You have to perform a type check or assertion before using the value.
let data: unknown = 10;
if (typeof data === 'number') {
const result = data + 5;
}
never
: Usenever
when you want to represent a value that will never occur, like in the case of functions that throw errors or never return.
function throwError(message: string): never {
throw new Error(message);
}
function infiniteLoop(): never {
while (true) {}
}
The choice depends on the specific use case and how much type safety you want. any
provides the least type safety but allows maximum flexibility. unknown
provides better type safety compared to any
but requires explicit type checks. never
is used to represent unreachable or error-throwing code paths.
53. What is the fundamental difference between Optional Chaining (?.) and Non-null assertion operator (!) in TypeScript?
The fundamental difference between Optional Chaining (?.
) and the Non-null Assertion Operator (!
) in TypeScript lies in their behavior when dealing with potentially undefined or null values.
- Optional Chaining (
?.
): The optional chaining operator allows you to safely access properties or methods on an object that may be undefined or null. If the property or method doesn’t exist, the expression will returnundefined
instead of throwing an error.
interface Person {
name: string;
address?: {
city: string;
zipcode?: string;
};
}
const person: Person = {
name: 'John',
};
const city = person.address?.city;
console.log(city); // Output: undefined
- Non-null Assertion Operator (
!
): The non-null assertion operator is used to tell TypeScript that you are certain that a value will not be null or undefined at a specific point in the code. It is a way to bypass the type checker’s nullability check.
const name: string | undefined = 'John';
const nonNullName: string = name!;
console.log(nonNullName); // Output: John
The !
operator can be risky if misused since it bypasses type safety checks. It should only be used when you are sure that a value is not null or undefined.
54. What does Short-Circuiting mean in TypeScript?
In TypeScript (and other programming languages), short-circuiting refers to the behavior of logical operators (&&
and ||
) to stop evaluating expressions as soon as the result can be determined. It means that if the result of an expression can be determined early, based on the value of the first operand, the second operand is not evaluated at all.
Here are examples of short-circuiting:
- Using
&&
(Logical AND):
function doSomething() {
console.log('doSomething function called.');
return true;
}
const result = false && doSomething();
console.log(result); // Output: false
In this example, since the first operand (false
) in the &&
expression is false
, the second operand (doSomething()
) is not evaluated, and the function doSomething
is not called.
- Using
||
(Logical OR):
function doSomething() {
console.log('doSomething function called.');
return true;
}
const result = true || doSomething();
console.log(result); // Output: true
In this example, since the first operand (true
) in the ||
expression is true
, the second operand (doSomething()
) is not evaluated, and the function doSomething
is not called.
55. List a few rules of private fields in TypeScript.
In TypeScript, private fields have the following rules:
- Private fields can only be accessed within the class where they are defined. They are not accessible from outside the class, including its subclasses.
- Private fields cannot be accessed or modified by external code, ensuring encapsulation.
- Private fields are denoted with the
private
keyword followed by the field name. - Private fields cannot be used in interfaces, as they are implementation details.
- Private fields are not inherited by subclasses. A subclass can define its own private fields.
- Private fields do not participate in structural typing, meaning two classes with identical private fields are not considered the same type.
Here’s an example illustrating these rules:
class MyClass {
private privateVar: number = 10;
private privateMethod() {
console.log('This is a private method.');
}
public accessPrivate() {
console.log(this.privateVar); // Allowed within the class
this.privateMethod(); // Allowed within the class
}
}
const instance = new MyClass();
console.log(instance.privateVar); // Error: Property 'privateVar' is private and only accessible within class 'MyClass'.
instance.privateMethod(); // Error: Property 'privateMethod' is private and only accessible within class 'MyClass'.
instance.accessPrivate(); // Output: 10, This is a private method.
56. What are some use cases of template literal types in TypeScript?
Template Literal Types in TypeScript allow you to create complex types by using template literal syntax. They are especially useful in scenarios where you want to generate types based on existing types or combine strings in a type-safe manner.
Use cases of template literal types include:
- Generating Union Types:
type StatusCode = 200 | 404 | 500;
type ApiResponse<T> = { status: StatusCode; data: T };
type ResponseType<T> = ApiResponse<T> | `${StatusCode} Error`;
In this example, the ResponseType
is a union type combining ApiResponse<T>
with string literals representing error status codes.
- Creating Mapped Types:
type RequestPayload = {
endpoint: string;
method: string;
body: any;
};
type ApiResponse<T> = {
data: T;
statusCode: number;
};
type ApiRequest<T> = {
[K in keyof RequestPayload]: ApiResponse<T>;
};
Here, ApiRequest<T>
creates a mapped type by using keyof RequestPayload
to map each property of RequestPayload
to ApiResponse<T>
.
- Formatting Strings Based on Types:
type EventTypes = 'click' | 'input' | 'submit';
type EventPayloads = {
[T in EventTypes]: { type: T; data: string };
};
In this example, EventPayloads
maps each event type to an object containing the event type and corresponding data.
57. How to check the type of a variable or constant in TypeScript?
In TypeScript, you can use the typeof
operator or the instanceof
operator to check the type of a variable or constant.
- Using
typeof
:
const x = 42;
if (typeof x === 'number') {
console.log('x is a number.');
} else {
console.log('x is not a number.');
}
- Using
instanceof
:
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
const person = new Person('John', 30);
if (person instanceof Person) {
console.log('person is an instance of Person.');
} else {
console.log('person is not an instance of Person.');
}
Both methods are valid ways to check the type of a variable or constant. Use typeof
when you want to check primitive types, and use instanceof
when you want to check for custom class instances.
58. How to add types to an interface from another interface or extend types in TypeScript?
In TypeScript, you can add types to an interface from another interface using the extends
keyword. This allows you to create new interfaces that inherit properties and methods from other interfaces.
Here’s an example of extending interfaces:
interface Person {
name: string;
age: number;
}
interface Employee extends Person {
department: string;
}
const employee: Employee = {
name: 'John Doe',
age: 30,
department: 'HR',
};
In this example, the Employee
interface extends the Person
interface, inheriting its properties name
and age
. The employee
object conforms to the Employee
interface, containing all the properties defined in both Person
and Employee
.
You can also extend multiple interfaces:
interface Shape {
color: string;
}
interface Circle extends Shape {
radius: number;
}
interface Square extends Shape {
sideLength: number;
}
const circle: Circle = {
color: 'red',
radius: 5,
};
const square: Square = {
color: 'blue',
sideLength: 10,
};
In this example, both the Circle
and Square
interfaces extend the Shape
interface, sharing its color
property while adding their own specific properties (radius
for Circle
and sideLength
for Square
).
59. What is the difference between type and interface in TypeScript?
Feature | Type | Interface |
---|---|---|
Use keyword | type | interface |
Extending other types | Supported | Supported |
Merging declarations | Not supported | Supported |
Declaration merging | Not supported | Supported |
Multiple declarations | Not allowed for the same type name | Allowed for the same interface name |
Function signatures | Can define function types | Can define function signatures |
Object literal shapes | Can define object literals | Can define object literals |
Implementing by classes | Not possible | Possible (with optional methods) |
Augmentation | Not possible | Possible (through declaration merging) |
60. What is the difference between interface vs type statements?
Feature | Interface | Type |
---|---|---|
Syntax | interface keyword | type keyword |
Object Shape | Defines the shape of an object | Can define complex types like unions, intersections, and tuples |
Extending other types | Supported | Supported |
Declaration merging | Supported | Not supported |
Merging declarations | Can merge declarations with the same name | Cannot merge declarations |
Implementing by classes | Possible (with optional methods) | Not possible |
Augmentation | Possible (through declaration merging) | Not possible |
Function signatures | Can define function signatures | Can define function types |
Best Used for | Defining object shapes and contracts | Defining complex and utility types |
Convention and Usage | Commonly used for objects and classes | Commonly used for unions and intersections |
61. How does the override keyword work in TypeScript?
The override
keyword in TypeScript is used to explicitly indicate that a method in a derived class is intended to override a method with the same signature in the base class. It helps prevent accidental method hiding when the base class method is meant to be overridden.
Here’s an example of using the override
keyword:
class Animal {
makeSound() {
console.log('Animal makes a sound.');
}
}
class Dog extends Animal {
override makeSound() {
console.log('Dog barks.');
}
}
const dog = new Dog();
dog.makeSound(); // Output: Dog barks.
In this example, the Dog
class extends the Animal
class and overrides the makeSound()
method. The override
keyword ensures that if the method signature in the base class changes, the TypeScript compiler will raise an error in the derived class if the override
keyword is not present.
62. What is Mixin Constructor Type?
A Mixin Constructor Type is a type representing a constructor function of a mixin. It allows you to define a type that can be used to create instances of a class that has mixed in multiple behaviors from different mixin classes.
Here’s an example of a Mixin Constructor Type:
class Printable {
print() {
console.log('Printing...');
}
}
class Loggable {
log() {
console.log('Logging...');
}
}
type Constructor<T = {}> = new (...args: any[]) => T;
function Timestamped<TBase extends Constructor>(Base: TBase) {
return class extends Base {
timestamp = new Date();
printTimestamp() {
console.log(`Timestamp: ${this.timestamp}`);
}
};
}
class Document {
constructor(public content: string) {}
}
const TimestampedDocument = Timestamped(Document);
const doc = new TimestampedDocument('This is a document.');
doc.printTimestamp(); // Output: Timestamp: <current timestamp>
In this example, we have Printable
, Loggable
, and Timestamped
mixins. The Timestamped
mixin uses a Mixin Constructor Type (Constructor<TBase>
) to specify that it takes a constructor as an argument and returns a new constructor with additional properties and methods.
63. What is a dynamic import expression?
A dynamic import expression is a way to import modules in TypeScript and JavaScript asynchronously. It allows you to load modules on-demand, only when they are needed, which can significantly improve performance for large applications.
Here’s an example of a dynamic import expression:
async function fetchData() {
const module = await import('./module');
const data = module.getData();
console.log(data);
}
fetchData(); // Dynamically imports the 'module' and calls its 'getData' function.
In this example, the import()
function is used with the await
keyword to dynamically import the module
asynchronously. The getData
function from the imported module is then called to fetch and process the data.
64. Explain what `never` datatype in TypeScript is?
In TypeScript, the never
type represents a value that will never occur. It is used to indicate that a function will not return any value or that a variable cannot have a value.
Here are some examples of never
:
- Function that never returns:
function throwError(message: string): never {
throw new Error(message);
}
const result = throwError('An error occurred.'); // Function throws an error and never returns a value.
- Function with an infinite loop:
function infiniteLoop(): never {
while (true) {}
}
const loopResult = infiniteLoop(); // Function runs indefinitely and never returns a value.
- Exhaustive switch statement:
function exhaustiveCheck(value: string): never {
throw new Error(`Unexpected value: ${value}`);
}
function processValue(value: 'A' | 'B' | 'C') {
switch (value) {
case 'A':
// Do something for 'A'
break;
case 'B':
// Do something for 'B'
break;
case 'C':
// Do something for 'C'
break;
default:
exhaustiveCheck(value); // Compiler knows this line will never be reached.
}
}
In this example, the exhaustiveCheck
function is used in the default case of a switch statement to indicate that the default case should never be reached. The TypeScript compiler can infer that if none of the explicit cases match, the exhaustiveCheck
function will be called, and therefore the switch statement is exhaustive.
65. Explain what Currying is in TypeScript?
Currying is a functional programming technique where a function that takes multiple arguments is transformed into a series of functions, each taking a single argument. The returned function from each step can be called with the next argument until all the arguments are provided.
Here’s an example of currying in TypeScript:
// Non-curried function
function add(a: number, b: number): number {
return a + b;
}
console.log(add(2, 3)); // Output: 5
// Curried function
function curriedAdd(a: number): (b: number) => number {
return function (b: number) {
return a + b;
};
}
const add2 = curriedAdd(2);
console.log(add2(3)); // Output: 5
In this example, we have a non-curried function add
, which takes two arguments and returns their sum. The curried version, curriedAdd
, takes one argument a
and returns a function that takes another argument b
and returns the sum of a
and b
.
By calling curriedAdd(2)
, we get a new function add2
, which is pre-configured to add 2 to any number passed as an argument. This allows for more flexible and reusable function composition.
66. Why is the `infer` keyword needed in TypeScript?
The infer
keyword in TypeScript is used in conditional types to allow the inference of a type parameter within a generic type declaration. It helps capture the type of an argument passed to a generic function or class and use it to define the return type or
other type relationships.
Here’s an example of using the infer
keyword:
type ExtractReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function add(a: number, b: number): number {
return a + b;
}
type Result = ExtractReturnType<typeof add>;
// Result is inferred as 'number' because 'add' returns a number.
In this example, we define a ExtractReturnType
type, which uses conditional types with infer R
to capture the return type of a function T
. When ExtractReturnType
is applied to the add
function, it infers the return type as number
because the add
function returns a number.
67. What is the difference between the `unknown` and `any` types?
The unknown
and any
types in TypeScript are both used to represent values of any type, but they have different behaviors and levels of type safety.
any
: Theany
type allows values of any type and turns off type checking for that variable or expression. It provides maximum flexibility but sacrifices type safety.
let data: any = 10;
data = 'Hello';
data = true;
unknown
: Theunknown
type is similar toany
, allowing values of any type. However, it requires explicit type checking or assertion before using the value. It provides better type safety compared toany
.
let data: unknown = 10;
if (typeof data === 'number') {
const result = data + 5;
}
68. How to exclude a property from a type in TypeScript?
In TypeScript, you can exclude a property from a type by using the Omit
utility type. The Omit
type allows you to create a new type by excluding one or more properties from the original type.
Here’s an example of using Omit
:
interface Person {
name: string;
age: number;
city: string;
}
type PersonWithoutAge = Omit<Person, 'age'>;
// Resulting type: { name: string; city: string; }
const person: PersonWithoutAge = {
name: 'John Doe',
city: 'New York',
};
In this example, we have an Person
interface with properties name
, age
, and city
. We create a new type PersonWithoutAge
by using Omit<Person, 'age'>
, which excludes the age
property from the original Person
interface. The resulting type has only name
and city
properties.
69. Why do we need Index Signature in TypeScript?
In TypeScript, an index signature is used to define the types of properties that are not known at compile-time but can be accessed using a dynamic key, typically a string or a number. It allows you to create objects or maps with dynamic keys and ensure type safety when accessing their values.
Here’s an example of using an index signature:
interface DynamicObject {
[key: string]: any;
}
const person: DynamicObject = {
name: 'John Doe',
age: 30,
};
console.log(person.name); // Output: John Doe
console.log(person.age); // Output: 30
person.city = 'New York';
console.log(person.city); // Output: New York
In this example, the DynamicObject
interface has an index signature [key: string]: any
, which allows the person
object to have properties with any string key and any value type. This allows us to add new properties to the person
object dynamically.
70. How to define a TypeScript class that has an index signature?
To define a TypeScript class that has an index signature, you need to include the index signature in the class definition. The index signature allows you to access properties of the class using a dynamic key.
Here’s an example of a class with an index signature:
class DynamicObject {
[key: string]: any;
constructor() {
this['name'] = 'John Doe';
this['age'] = 30;
}
}
const person = new DynamicObject();
console.log(person.name); // Output: John Doe
console.log(person.age); // Output: 30
person.city = 'New York';
console.log(person.city); // Output: New York
In this example, the DynamicObject
class has an index signature [key: string]: any
, which allows the class instances to have properties with any string key and any value type. We create a new person
instance of the DynamicObject
class and set some properties dynamically using the index signature.
71. What is one thing you would change about TypeScript?
As an AI language model, I don’t have personal opinions or desires, but one common request from the TypeScript community is to improve the handling of circular references in type definitions. Currently, TypeScript struggles to infer certain circular types, and developers often need to use type assertions or explicitly annotate types to resolve circular dependencies.
TypeScript’s team is continually working on improving the language and addressing community feedback. It’s essential to keep track of the language’s development and submit issues or feature requests on the TypeScript GitHub repository to contribute to its improvement.
72. Explain when to use the `declare` keyword in TypeScript.
The declare
keyword in TypeScript is used to tell the compiler that a variable, function, class, or module has been defined elsewhere and will be provided at runtime by an external source, such as a JavaScript library or a global variable.
Here’s an example of using the declare
keyword:
// Declare a global variable that will be available at runtime
declare var myGlobalVar: string;
// Declare a global function that will be available at runtime
declare function myGlobalFunction(name: string): void;
// Declare a module that will be available at runtime
declare module 'my-module' {
export function doSomething(): void;
export const value: number;
}
In this example, we use the declare
keyword to inform TypeScript that myGlobalVar
, myGlobalFunction
, and the my-module
module will be available at runtime from external sources. This allows TypeScript to compile without throwing errors for undefined variables or functions.
73. Is it possible to generate TypeScript declaration files from a JavaScript library?
Yes, it is possible to generate TypeScript declaration files (also known as .d.ts
files) from a JavaScript library that lacks type information. TypeScript provides a tool called “dts-gen” (TypeScript Declaration File Generator) to automatically generate declaration files for existing JavaScript libraries.
Here’s how to use dts-gen to generate declaration files:
- Install dts-gen globally (if not already installed):
npm install -g dts-gen
- Navigate to the root directory of your JavaScript library.
- Run dts-gen with the following command:
dts-gen -o ./
dts-gen will analyze your JavaScript library and attempt to generate corresponding .d.ts
files. Note that the generated declaration files may not be perfect and may require manual adjustments to ensure type safety.
74. What are Ambients in TypeScript and when to use them?
In TypeScript, “Ambients” refer to declarations that describe types and values defined outside of the TypeScript environment. Ambients are typically used to provide type information for external JavaScript libraries or global variables that are not natively written in TypeScript.
Ambient declarations are declared using the declare
keyword, and they allow TypeScript to understand the types and APIs of these external entities during development without requiring the actual implementation.
When to use Ambients:
- When using external JavaScript libraries without existing TypeScript type definitions.
- When working with global variables or functions that are not natively defined in TypeScript.
For example, if you are using the jQuery
library, which is written in JavaScript, you can use an ambient declaration to provide TypeScript with the necessary type information:
declare var jQuery: (selector: string) => any;
By declaring the jQuery
variable with a function signature, TypeScript can provide type checking and autocompletion for code that uses the jQuery
library.
75. Explain the difference between `declare enum` and `declare const enum` in TypeScript.
In TypeScript, both declare enum
and declare const enum
are used to declare enums that exist at runtime but do not have a specific implementation in TypeScript. However, there are some key differences between the two:
declare enum
: When you usedeclare enum
, the enum values will be available at runtime. Additionally, the generated JavaScript output will include the enum object with property assignments for each enum member.
declare enum Direction {
Up,
Down,
Left,
Right,
}
const direction: Direction = Direction.Up;
declare const enum
: On the other hand, when you usedeclare const enum
, the enum values will be inlined directly at compile-time. There will be no JavaScript object generated for the enum, and the enum members’ values will be replaced by their numeric literals in the compiled JavaScript code.
declare const enum Direction {
Up,
Down,
Left,
Right,
}
const direction: Direction = Direction.Up;
In the second example, the declare const enum
will not generate a JavaScript object for Direction
and will directly replace Direction.Up
with 0
in the compiled output.
76. How can the `never` datatype be useful?
The never
datatype in TypeScript is useful in several scenarios:
- Function that never returns:
function throwError(message: string): never {
throw new Error(message);
}
const result = throwError('An error occurred.'); // Function throws an error and never returns a value.
- Function with an infinite loop:
function infiniteLoop(): never {
while (true) {}
}
const loopResult = infiniteLoop(); // Function runs indefinitely and never returns a value.
- Exhaustive switch statement:
function exhaustiveCheck(value: string): never {
throw new Error(`Unexpected value: ${value}`);
}
function processValue(value: 'A' | 'B' | 'C') {
switch (value) {
case 'A':
// Do something for 'A'
break;
case 'B':
// Do something for 'B'
break;
case 'C':
// Do something for 'C'
break;
default:
exhaustiveCheck(value); // Compiler knows this line will never be reached.
}
}
In this example, the exhaustiveCheck
function is used in the default case of a switch statement to indicate that the default case should never be reached. The TypeScript compiler can infer that if none of the explicit cases match, the exhaustiveCheck
function will be called, and therefore the switch statement is exhaustive.
77. What is the need for the `–incremental` flag in TypeScript? give code examples
The --incremental
flag in TypeScript is used to enable incremental compilation. Incremental compilation is a feature that caches information about the project’s structure between compilations, making subsequent builds faster by only recompiling the changed parts of the project.
When you use the --incremental
flag, TypeScript stores information about the project in the node_modules/.cache
directory by default.
Here’s how to use the --incremental
flag:
tsc --incremental
When the --incremental
flag is used, TypeScript will generate and update the necessary files in the cache directory to speed up subsequent compilations.
78. What are the differences between the `private` keyword and private fields in TypeScript?
In TypeScript, the private
keyword and private fields are both used to encapsulate data and restrict access to certain class members, but they have some differences:
private
keyword: When you declare a class member asprivate
, it is only accessible within the class where it is defined. It cannot be accessed or modified from outside the class, including its subclasses.
class MyClass {
private privateVar: number = 10;
private privateMethod() {
console.log('This is a private method.');
}
public accessPrivate() {
console.log(this.privateVar); // Allowed within the class
this.privateMethod(); // Allowed within the class
}
}
const instance = new MyClass();
console.log(instance.privateVar); // Error: Property 'privateVar' is private and only accessible within class 'MyClass'.
instance.privateMethod(); // Error: Property 'privateMethod' is private and only accessible within class 'MyClass'.
instance.accessPrivate(); // Output: 10, This is a
private method.
- Private Fields: Private fields are a feature introduced in TypeScript 3.8 that allows you to define private class members using the
#
symbol. Private fields provide true encapsulation, and unlikeprivate
members, they are not accessible even from within the class.
class MyClass {
#privateVar: number = 10;
#privateMethod() {
console.log('This is a private method.');
}
public accessPrivate() {
console.log(this.#privateVar); // Allowed within the class
this.#privateMethod(); // Allowed within the class
}
}
const instance = new MyClass();
console.log(instance.#privateVar); // Error: Private identifiers are not accessible outside class 'MyClass'.
instance.#privateMethod(); // Error: Private identifiers are not accessible outside class 'MyClass'.
instance.accessPrivate(); // Output: 10, This is a private method.
The key difference between private
and private fields is that private fields provide stronger encapsulation, and their names are truly private and cannot be accessed even from within the class, whereas private
members are still accessible within the class.
79. What is the benefit of import assertions features in TypeScript?
The import assertions feature in TypeScript allows you to explicitly specify the shape or type of the imported module or value. It provides additional type safety and enables better tooling support for imported values.
There are two types of import assertions in TypeScript:
- Asserting the existence of a property:
import { someValue } from './module' assert { someValue: number };
Here, we are asserting that the imported someValue
from the module
has the type number
.
- Asserting the type of the entire module:
import * as myModule from './module' assert { type: 'json' };
In this case, we are asserting that the entire myModule
imported from the module
is a JSON-like object.
The benefits of import assertions are:
- Improved type safety: By explicitly specifying the types or shape of imported values, TypeScript can provide more accurate type checking and prevent potential bugs due to incorrect usage.
- Better tooling support: Import assertions provide valuable information to IDEs and code editors, enabling enhanced autocompletion, type inference, and documentation for imported modules.
80. How to make a union type from type alias or interface properties in TypeScript?
To create a union type from type alias or interface properties in TypeScript, you can use the keyof
operator in conjunction with the typeof
operator.
Here’s an example:
interface Person {
name: string;
age: number;
city: string;
}
type PersonProperties = keyof Person;
// Resulting type: 'name' | 'age' | 'city'
function printProperty(property: PersonProperties, person: Person) {
console.log(person[property]);
}
const person: Person = {
name: 'John Doe',
age: 30,
city: 'New York',
};
printProperty('name', person); // Output: John Doe
printProperty('age', person); // Output: 30
printProperty('city', person); // Output: New York
In this example, we define an Person
interface with properties name
, age
, and city
. We then create a type alias PersonProperties
using keyof Person
, which results in a union type of all property names of the Person
interface.
Advanced Questions
81. What does the tsconfig
option lib
do?
The tsconfig.json
file is used to configure the TypeScript compiler. The lib
option allows you to specify the set of JavaScript libraries that are available for your TypeScript code to use. By default, TypeScript includes a standard set of libraries based on the ECMAScript version you are targeting, but you can explicitly include or exclude certain libraries using the lib
option.
For example, if you want to target ES6 and use the Promise
object, you can include the "es6"
library in your tsconfig.json
like this:
{
"compilerOptions": {
"target": "es6",
"lib": ["es6"]
}
}
83. Are strongly-typed functions as parameters possible in TypeScript?
Yes, TypeScript allows you to define strongly-typed functions as parameters. You can use the arrow function syntax to define the function type.
Example:
// Define a function type that takes two numbers and returns a number
type MathOperation = (a: number, b: number) => number;
// Function that uses the MathOperation type as a parameter
function performOperation(operation: MathOperation, num1: number, num2: number): number {
return operation(num1, num2);
}
// Example usage:
const add: MathOperation = (a, b) => a + b;
const subtract: MathOperation = (a, b) => a - b;
const result1 = performOperation(add, 5, 3); // Result: 8
const result2 = performOperation(subtract, 10, 4); // Result: 6
86. How to make an array with a specific length or array elements in TypeScript?
To create an array with a specific length or pre-populate it with specific elements, you can use the Array
constructor and fill
method in TypeScript.
Example 1: Creating an array with a specific length
const length = 5;
const arrWithLength = new Array(length); // This creates an array of length 5, but the elements are all undefined
console.log(arrWithLength); // Output: [ undefined, undefined, undefined, undefined, undefined ]
Example 2: Creating an array with pre-populated elements
const arrWithElements = new Array(3).fill(0); // This creates an array of length 3 with all elements initialized to 0
console.log(arrWithElements); // Output: [ 0, 0, 0 ]
87. Is there a way to check for both null and undefined in TypeScript?
Yes, there are multiple ways to check for both null
and undefined
in TypeScript.
- Using a union type with
null
andundefined
:
function checkValue(value: string | null | undefined) {
if (value === null || value === undefined) {
console.log("Value is null or undefined");
} else {
console.log("Value is:", value);
}
}
checkValue("Hello"); // Output: Value is: Hello
checkValue(null); // Output: Value is null or undefined
checkValue(undefined); // Output: Value is null or undefined
- Using the
== null
comparison:
function checkValue(value: string | null | undefined) {
if (value == null) {
console.log("Value is null or undefined");
} else {
console.log("Value is:", value);
}
}
Both approaches will correctly handle values that are either null
or undefined
.
89. How would you overload a class constructor in TypeScript?
In TypeScript, you can use constructor overloads to define multiple constructor signatures for a class. This allows you to provide different ways to create instances of the class with varying parameters.
Example:
class Person {
name: string;
age: number;
constructor(name: string, age: number);
constructor(name: string) {
this.name = name;
this.age = 0;
}
// Additional constructor overload
constructor(name: string, age?: number) {
this.name = name;
this.age = age || 0;
}
}
// Creating instances using different constructor overloads
const person1 = new Person("Alice");
console.log(person1); // Output: Person { name: 'Alice', age: 0 }
const person2 = new Person("Bob", 30);
console.log(person2); // Output: Person { name: 'Bob', age: 30 }
In this example, we have defined two constructor overloads for the Person
class—one that takes only the name as a parameter and initializes the age to 0, and the other that takes both name and age as parameters.
90. What does the const assertion mean in TypeScript?
The const
assertion is a feature in TypeScript that allows you to indicate to the compiler that the variable should have a literal type. When a variable is declared with a const
assertion, TypeScript treats it as a constant with a specific, unchangeable value.
Example:
let myNumber = 42; // TypeScript infers myNumber as a number type
let myConstNumber = 42 as const; // TypeScript infers myConstNumber as a literal type 42
// Trying to modify the const assertion will result in an error
// myConstNumber = 43; // Error: Cannot assign to '42' because it is a constant.
In the example above, myConstNumber
is inferred as a literal type 42
, which means it can only have the value 42
, and any attempt to change its value will result in a compilation error.
91. In a?.b.c, if a.b is null, then a.b.c will evaluate to undefined, right?
Yes, that’s correct. In TypeScript (and JavaScript), if you use the optional chaining operator (?.
), the expression will short-circuit and evaluate to undefined
if any intermediate property is null
or undefined
.
Example:
const obj = {
a: null // Set 'a' to null
};
// Without optional chaining:
console.log(obj.a.b.c); // Error: Cannot read property 'b' of null
// With optional chaining:
console.log(obj.a?.b.c); // Output: undefined
In the first case without optional chaining, attempting to access obj.a.b.c
results in an error since obj.a
is null
. However, when using optional chaining obj.a?.b.c
, the expression short-circuits at obj.a
and evaluates to undefined
.
92. What is the difference between “interface” and “type” in TypeScript?
Interface | Type |
---|---|
Primarily used for defining object shapes and contracts. | Used for creating aliases for complex types or unions. |
Can be extended by other interfaces. | Cannot be extended or combined with other types. |
Can be implemented by classes to provide a contract. | Cannot be implemented by classes. |
Allows declaration merging, where multiple interfaces | Does not support declaration merging. |
with the same name can be merged into a single interface. | |
Has better support for declaration merging and | More suitable for defining complex types and unions. |
augmentation when working with libraries. |
93. How is TypeScript different from ES6?
TypeScript is a superset of ECMAScript 6 (ES6) and includes all the features of ES6 while adding static typing and other advanced features on top. Here are the main differences:
- Static Typing: TypeScript introduces static typing, enabling developers to specify the data types of variables, function parameters, and return values. This helps catch type-related errors during development.
- Optional Typing: TypeScript allows developers to mark variables or function parameters as optional by using
?
after the variable name. This is not present in ES6. - Interfaces and Type Annotations: TypeScript provides interfaces and type annotations to define complex types and enforce specific structures on objects. This is not available in ES6.
- Type Inference: TypeScript’s compiler infers types whenever possible, reducing the need for explicit type annotations while maintaining strong typing.
- Access Modifiers: TypeScript supports access modifiers like
public
,private
, andprotected
, providing more control over class members’ visibility. - Strict Null Checks: TypeScript has strict null checks that help prevent null and undefined errors during runtime.
- Enums: TypeScript has enums, which allow developers to define a set of named constants, making code more expressive.
- Decorators: TypeScript introduces decorators, which are used to modify class declarations or properties at design time. This is useful in various scenarios, including building frameworks or code transformation.
94. What are the different ways to define a variable in TypeScript?
In TypeScript, you can define variables using different keywords:
- Using
let
: Used to declare variables that can be reassigned.
let message: string = "Hello, TypeScript!";
- Using
const
: Used to declare constants with a fixed value.
const PI: number = 3.14;
- Using
var
: The older way to declare variables. Generally avoided in favor oflet
andconst
.
var count: number = 10;
95. What are the different data types available in TypeScript?
In TypeScript, there are several built-in data types:
- Boolean: Represents true/false values.
let isDone: boolean = false;
- Number: Represents numeric values (both integers and floating-point numbers).
let age: number = 30;
let price: number = 19.99;
- String: Represents textual data.
let name: string = "John";
- Array: Represents a collection of elements of the same type.
let numbers: number[] = [1, 2, 3, 4];
let fruits: Array<string> = ["apple", "banana", "orange"];
- Tuple: Represents an array with fixed-length and mixed data types.
let person: [string, number] = ["John", 30];
- Enum: Represents a set of named constants.
enum Color {
Red,
Green,
Blue,
}
let backgroundColor: Color = Color.Red;
- Any: Represents a dynamic or unknown type, allowing variables to hold values of any type.
let dynamicValue: any = "Hello";
dynamicValue = 42; // Valid
- Void: Represents the absence of any type, often used for functions that return nothing.
function logMessage(message: string): void {
console.log(message);
}
- Null and Undefined: Represents null and undefined values, respectively.
let someValue: null = null;
let otherValue: undefined = undefined;
96. What is a class in TypeScript? How do you define it?
In TypeScript, a class is a blueprint for creating objects with a specific structure and behavior. It encapsulates data (properties) and operations (methods) related to the objects. Here’s how you define a class:
class Person {
// Properties
name: string;
age: number;
// Constructor - used to initialize the class properties
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
// Method
sayHello(): void {
console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
}
}
// Creating an instance of the class
const person1 = new Person("John", 30);
person1.sayHello(); // Output: Hello, my name is John and I'm 30 years old.
In the example above, we define a Person
class with properties name
and age
, a constructor to initialize these properties, and a sayHello
method to log a greeting.
97. What is a namespace in TypeScript? How can it be used?
A namespace in TypeScript is a way to organize code by encapsulating variables, functions, classes, and interfaces under a unique identifier. It helps avoid naming collisions in large applications. Namespaces are also known as internal modules.
Example:
namespace MathUtils {
export function add(a: number, b: number): number {
return a + b;
}
export function subtract(a: number, b: number): number {
return a - b;
}
}
const sum = MathUtils.add(5, 3); // Using the function from the namespace
console.log(sum); // Output: 8
In this example, we define a namespace MathUtils
and export two functions, add
and subtract
. We can access these functions using the MathUtils
namespace.
98. What are decorators in TypeScript?
Decorators are a TypeScript feature used to modify the behavior of classes, methods, or properties at design time. They are denoted by the @
symbol and can be used to add metadata, apply mixins, or transform class properties or methods.
Example:
// Decorator function
function uppercase(target: any, propertyKey: string) {
let value = target[propertyKey];
// Redefine the property to return the value in uppercase
Object.defineProperty(target, propertyKey, {
get