March Flash Sale

TypeScript Interview Questions and Answers for 2024

We have curated the most important typescript interview questions and answers for the year 2024. This article is one best preparation source for typescript coding interview questions as well as conceptual questions. In this Article, we have covered the most important typescript interview questions for experienced as well as beginner programmers. The interviewers typically ask these questions while asking about frameworks, such as react and angular typescript interview questions. The article categorizes all the interview questions on typescript among three categories: basic, intermediate, and advanced questions.

  • 4.7 Rating
  • 70 Question(s)
  • 45 Mins of Read
  • 5907 Reader(s)

Beginner

The values we assign to a variable in our program have a specific type, which is represented by types in TypeScript. In TypeScript we have support for a wide range of value types, including strings, Boolean, numbers, in addition to other types like enum, any, never, unknown and many more.

Declaring types explicitly in TypeScript is advantageous for a number of reasons. Types make code more readable and clearer, and they can also help your compiler identify issues that result from improper type assignments. Additionally, Types provide as an additional layer of documentation that might be useful when working in a team context.

To explicitly declare a type in TypeScript, you can append the ‘:’ after the variable name, following the Type of the assigned value. For example,

let stringVariable : string = "This is a string"; 
let numVariable : number = 2; 
let booleanVariable : boolean = true;  

Object-oriented programming (OOP) is a computer programming style that organises software design around objects (or data), instead of functions and logic. An object is a data field with characteristics and behaviour.

Large, sophisticated, and actively updated or maintained programmes enjoy many benefits by using Object oriented programming style, for instance, large desktop applications, manufacturing system simulation software, and many more. Additionally, it makes the software easy to collaborate and distributed among small teams.

The OOP is based on four main principles; Encapsulation, Abstraction, Inheritance, and Polymorphism. This brings many benefits to the software in terms of reusability, scalability, and efficiency.

Yes, Typescript supports all the four principles Encapsulation, Abstraction, Inheritance, and Polymorphism of Object-oriented programming paradigm.

Encapsulation: Encapsulation is a technique for organising code and an essential element of object-oriented programming. According to this principle, an object contains all the critical information, with only a small subset of it being exposed outside.  

Inheritance: Inheritance simply means that the Classes can reuse code from other classes. This enables developers to reuse common programming logic while still preserving a distinctive hierarchy. An inheritance chain and object model can be easily created with TypeScript.

Abstraction: Objects conceal any extraneous implementation code and only expose internal mechanisms that are necessary for the use of other objects. The derived class can typically extend the functionality of the base class. This approach makes it simpler for developers to introduce new features or make future modifications.

Polymorphism: When many classes from the same parent override the same functionality, this is known as polymorphism. Each newly created child class adds to the functionality of the parent class by implementing its own special properties or methods or by customising the base class's properties or methods to suit the needs of the child class.

Encapsulation is a technique for organising code and a key element of object-oriented programming. According to this principle, an object contains all the critical information, with only a small subset of it being exposed outside.  

In other words, each object's implementation and state are kept secretly inside a defined class. This class is not accessible to other objects, nor do they have the power to modify it. Only a limited set of object members with public access are available outside the class. Thus, it creates an encapsulation for the private data.
For instance, let us create a Worker class, all the class members are public by default in TypeScript. But we can provide access modifiers (public, private, protected) to prevent other entity from accessing or modifying them outside the parent class scope.

class Worker { 
 name: string; 
 private _salary: number; 
 
 constructor(name: string, salary: number) { 
  this.name = name; 
  this._salary = salary; 
 } 
 getSalary() : number { 
 return this._salary; 
 } 
} 
 
const john = new Worker("John"50000); 
console.log(john._salary) // Property '_salary' is private and only accessible within class 'Worker'. 
console.log(john.getSalary()) //50000 

We can also use getters and setters methods to achieve better encapsulation in Typescript. This data hiding provides greater program security and avoids unintended data corruption.

Functional programming means using functions to the best effect for creating clean and maintainable software. It is a declarative programming style, that puts more emphasis on “what to solve” in contrast to “how to solve”.

Functional programming highly depends upon the concept of Pure functions. A pure function is one whose outcomes depend only on the input variables and whose operation has no side effects to the outside world, aside from the return value.

Another important idea behind functional programming is immutability, i.e., avoid modifying the data outside the function, this is a way of avoiding side effects. In other words, only the function's return value should reflect the work that was done, and function input arguments should be copied, not changed. 

Beyond the pure function ideal, functional programming depends on first class functions and higher order functions in actual coding practise.  

  • A first-class function is one that is handled as an entity capable of standing on its own and receiving independent treatment. Functional programming utilizes functions as variables, parameters, and return values 
  • A higher-order function manipulates a function, it takes a function as an argument or returns a function.  

You can join us and find the best online computer Programming courses to start your dream career. 

Objects is used in programming to store a collection of keys and values pairs that resemble dictionaries. In TypeScript, an object type is defined by simply writing the object property keys and their corresponding types inside curly brackets or code blocks {}. For example, the function greet takes a parameter person of object type; objects are the primary means by which data is grouped and passed around in TypeScript.

function greet(person: { name: string; knowsHindi: boolean }) { 
 if (person.knowsHindi) return "Namaste" + person.name; 
 return "Hello " + person.name; 
} 

We can also use interface and type alias to define the object contract separately from the object instance. For example:

interface Person { name: string; knowsHindi: boolean } 
// or 
type Person = { name: string; knowsHindi: boolean }  

As the name suggests the optional property of an object type does not compel the object to include them in its declaration. By adding a “?” as a suffix by following the name of the property, an object type can have zero or more optional property members. 

For example, we can declare ab object “horse” with name, and color. Let us assign the color property to be optional.

const horse: {name: string, color?: string} =  
 {name: "Robin"}  

Using Type Alias

type Horse = {name: string 
  color?: string} 

Using Interface

interface Horse {name: string 
   color?: string} 

Typescript can infer the types of a variable but often it is unable to infer the precise type of a complex or unknown value of the variable and instead suggests the more general type "any."

The “any” type in TypeScript is the superset of all types and hence can be used for all acceptable values of a variable. Any type is frequently used when dealing with third-party applications where we anticipate some variables but are unsure of their precise types. Any data type can assist in opt-in and opt-out of type checking during compilation.

For example, we can assign all these values to the someVariable because it is of type any

let someVariable: any; 
 
someVariable = "string"; 
someVariable = 2; 
someVariable = true;  

A directory is the root directory of a TypeScript project if it has tsconfig.json file present in the directory as a direct child. The tsconfig.json file contains the information about the root file and the necessary compiler options for compilation of the project.

Instead, JavaScript projects can utilise a jsconfig.json file (like tsconfig.json for Typescrip) which performs the same functions but has several JavaScript-related compiler options enabled by default.

We can use tsconfig.json by invoking tsc with no input files in the command line terminal. Doing so, will cause the compiler to look for the tsconfig.json file starting from the current directory and continuing up the parent directory chain. 

tsc 

Tsconfig.json files are ignored when input files are invoked with tsc command on the command line terminal.  

tsc index.ts 

In Typescript, we can pass a particular texts or numbers as a type to a variable, for example:

let greeting: "hello" = "hello";  

The use of string literal types alone is limited. You can, however, combine them to form unions. When you specify a union of potential string values for a variable using the string literal type, the typescript compiler will only permit those string values to be assigned to the variable. If someone tries to assign a value to a variable that is not defined by the string literal type, TypeScript throws a compile-time error. 

let someVariable : "a" | "b" | "c"; 
  
someVariable = "a"; 
console.log(someVariable); // a 
someVariable = "d"// Error: Type '"d"' is not assignable to type '""a" | "b" | "c""'

TypeScript is a superset of JavaScript and supports all JavaScript features. Any JavaScript code can be quickly converted to TypeScript by simply changing the file extension from .js to .ts. Developers like typescript because it helps them write faster and more error-free code along with other powerful features due to its statically typed nature.

Here are some advantages of using Typescript: 

  • Compared to JavaScript's dynamic typing, TypeScript's static typing makes it more structured and simpler to read. Code written with TypeScript is more dependable and easier to edit than JavaScript. 
  • JavaScript allows you to access all the properties of an object, including the undefined ones. Which may result in bugs that are challenging to eliminate. Whereas, while working with TypeScript, your IDE will alert you in real time that you are attempting to access a property that is not specified and does not exist. 
  • An extensive type of system provided by TypeScript allows for better code auto-completion throughout the entire project. TypeScript autocomplete eliminates the need for ongoing code checks across several files while coding. 
  • Typescript works flawlessly with big data, whereas working with big data is a little complex in JavaScript.  
  • Cross-platform compatibility, for both client and server-side projects.

This is a frequently asked question in TypeScript interview questions.  

Typescript includes all the data types available in JavaScript and has some more types to improve code quality and provide statically typed benefits to the language. In TypeScript, the types can be classified into two categories, namely, built-in types and user-defined types. 

Built-in types available in the typescript include: 

  • String 
  • Number 
  • Boolean 
  • Null 
  • Void 
  • Any 
  • Undefined 
  • Symbol 

User-defined types available in the typescript include: 

  • Array 
  • Enum 
  • Class 
  • Interface 
  • Tuple 

In typescript, sometimes the typescript compiler infers the type of a variable according to its location, this type of inference is termed contextual typing.

For example: In the code below, TypeScript infers the type of the function expression on the right the type is using the type of the window.onmousedown function on the left. Using this, typescript also infers the type of parameter of the function expression. Thus in the example, the event has the type ‘MouseEvent.’

Moreover, the third line of the code gives a typescript error, because the MouseEvent type does not have a dance property in it. Whereas the second line parses without any error and tells the horizontal coordinate of the mouse pointer relative to the screen.

window.onmousedown = function (event) { 
 console.log(event.screenX); 
 console.log(event.dance); 
Property 'dance' does not exist on type 'MouseEvent'. 
}; 

Expect to come across this popular question in TypeScript coding questions.  

An interface defines a syntactical contract by specifying the type of properties and methods that an object can contain. In other words, you can predefine an object’s structure by creating an interface, this is also known as “duck typing.”

Interfaces can only contain the type of declaration of the members, and it is the responsibility of the deriving class to define all the contained methods and properties. In case the deriving class misses a member that is present in the interface or contains an extra method or property, the typescript will throw a typescript error.

The interface is typically used to predefine a standard structure or contract that all deriving classes must conform to. This contract or structure can be used for classes within as well as outside the current program.

For example:

interface User{ 
name: string; 
userId: number; 
} 
 
Function getUserId(user: User) { 
console.log(`user id : ${user.userId}`); 
} 
 
let john: User = { 
name: "John Doe", 
salary: 1321 
} 
 
getUserId(john); // "user id : 1321" 

TypeScript also contains the Rest Parameter, a unique kind of parameter that lets you pass an unknown number of arguments to a function. To use the rest parameter we invoke it by using an ellipsis (…) before the last parameter you are dealing with. This (...) syntax indicates that the function can accept one or more arguments and all the arguments that are passed after the rest symbol are going to be stored in an array. It is important to remember that only the final parameter can be the rest parameter, so each function can only have one rest parameter.

The rest parameter allows functions to run with a varying number of arguments, which is extremely helpful if you are unsure about the total number of parameters you want a particular function to accept.

For example:

function greet(greeting: string, ...names: string[]) {
 return greeting + " " + names.join(", ") + "!";
}
greet("Nice to meet you", "John", "Aron"); // returns "Nice to meet you John, Aron!"
greet("Nice to meet you", "John"); // returns "Nice to meet you John!"

To obtain a list of property keys in JavaScript, we frequently use Object.keys. The keyof operator in TypeScript works like the Object.keys. Despite being similar, Object.keys returns values whereas keyof operator only works at the type level and returns a literal union type.

In other words, the keyof operator takes an object type and outputs a union of its keys as a string or numeric literal.

For example, let us create an Student type with name, studentId, and class properties. When we use the keyof operator on the Student type, it return a union of Student’s property names as type litrals (i.e. "name" | "studentId | class" )

type Student = { 
 name: string; 
 studentId: number; 
 class: number 
 }  
type studentTypeKeys = keyof Student; // "name" | "studentId | class"

A must-know for anyone heading into a TypeScript interview, this question is frequently asked in Type Script interview questions.  

Typescript can infer the types of a variable, but often it is unable to infer the precise type of a complex or unknown variable and instead suggests the more general type "any." In these circumstances, you typically know the variable's more precise type rather than its inferred type. In such cases, you can use type assertion to tell the TypeScript compiler the exact type of the variable. 

Type assertion in TypeScript functions similarly to typecasting in other languages, although without the type checking or data restructuring possible in languages like C# and Java.  

In essence, type assertion is a lighter kind of typecasting that asks the compiler to treat a variable as a particular type but does not compel it to fit that shape if it is in a different form. Type assertion is utilized by the compiler only; it has no effect on runtime. 

There are two approaches to type assert any variable: 

  • Using ‘as’ syntax

let word: unknown = "hello"; 
let len: number = (word as string).length;  
  • Using angle-bracket ‘<>’ syntax: 
let word: unknown = "hello"; 
let len: number = (<string>word).length;  

It's no surprise that this one pops up often in TypeScript coding interview questions.  

The unknown type in TypeScript is the opposite of the type any, while any type is a superset of all the other types and can be assigned in the place of every type, the unknown type is the least assignable type available.

In other words, we can assign a value of every type to a variable of the unknown type, whereas the value of the unknown type of variable is not assignable to another variable of any other type, except the unknown type and any type. Therefore, we cannot conduct any operations on a variable of an unknown type without first asserting or narrowing it to a more precise type.

For example, let us create an unknown type of variable foo and assign a string value to it. The compiler generates an error if we attempt to assign that unknown variable to another variable bar of the string type.

let foo: unknown = "Typescript!!"; 
let bar: string = foo; // Type 'unknown' is not assignable to type 'string'  

However, we can fix the above error by narrowing down the variable foo of unknown type to a more precise type by using typeof checks or comparison checks or type guards.

let foo: unknown = "Typescript!!"; 
let bar: string = foo as string;  

Similar to the ‘any’ type in typescript, a common use case of the ‘unknown’ type is when you do not know the exact type of the variable while writing a program. We can assign any value to both any type and an unknown type. However, for an unknown type, a type of check or type assertion is necessary to perform any operation on the variable. 

// any type 
function runCallback(callback: any): void { 
 callback(); 
} 
// unknown type 
function runCallback(callback: unknown): void { 
 if (typeof callback === 'function') { 
 callback(); 
 } 
}  

By default, TypeScript is statically typed, and it adds so many advantages to TypeScript over JavaScript. This is very useful since your compiler will automatically check your data types during the compilation process and enforces the value to retain the same type. Albeit it is also possible to disable this feature and instruct your compiler to ignore the type of a certain variable.

In TypeScript, we use "any" data type to achieve optional static typing for a variable. This will not cause any runtime errors because your compiler will not verify any variables that are expressly declared to be this data type.

Moreover, when a variable's type is not explicitly specified and the compiler is unable to infer it from the context, TypeScript considers the variable to be of "any" data type.

For instance, when writing some program, there may be times when we want to assign a value to a variable but are unsure of its type. In such a situation, we want the compiler to skip type-checking and accept the value as it is. Since Typescript has "any" data type, we can store any type of value and forego type checking.

// json may come from a third-party API 
const studentData: string = ` {"name": "John Doe", "studentId": 623}`; 
 
// parse JSON to build student object 
const student: any = JSON.parse(studentData); 
 
console.log(student); // {"name": "John Doe", "studentId": 623} 

TypeScript has a number of globally available utility types that simplify common type transformations. Omit Type is one of the most useful out of these TypeScript utitlity type. The Omit type generates a new Type by ommiting the set of Keys properties from all the properties of your Type. It is more practical when you simply want to remove a few attributes from an object while keeping others.

For example, let’s create a type of alias SuperHero that contains some user properties.

type SuperHero = { 
 name: string 
 powers: string[] 
 age: number 
 humanName: string 
} 

We can use Omit Type to create another type alias NormalHuman which is a subset of the SuperHero type except a few properties.

type NormalHuman = Omit<User, "powers" | "humanName"> 
 
// type NormalHuman = { 
// name: string; 
// age: number; 
// }  

We write the readonly modifier before the property name to mark immutable object properties. The readonly modifier prevents change in the value of the marked property from outside of the object

Example:

interface User { 
 readonly userId: number; 
 name: string; 
 age: number; 
} 
const user1: User = {userId: 1, name: “John Doe”, age: 20} 
user1.age = 21; 
console.log(user1.age) // 21 
user1.userId = ‘10’ // Cannot assign to 'userId' because it is a read-only property. 

However, we can update the userId property inside the object method. For example, the updateUserId method in the User object.

interface User { 
 readonly userId: number; 
 name: string; 
 age: number; 
 updateUserId: () => void; 
}  
const user2: User = { 
 userId: 1, 
 name: 'John Doe', 
 age: 20, 
 updateUserId: function() { 
 this.userId = 2; 
 }, 
}; 
user1.updateUserId() 
console.log(user2.userId) // 2 

A common question in interview questions on TypeScript, don't miss this one.  

We must keep in mind the following two fundamental distinctions between dynamic typing and static typing:

First, type checking occurs at runtime for dynamically typed languages, whereas it occurs at build time for statically typed languages. As a result, scripts created in dynamically typed languages, such as JavaScript, can compile even when they have errors that will prohibit the script from functioning properly (if at all). Whereas an application developed in a statically-typed language, such as Java, will not compile until the mistakes are corrected.

The second difference is that dynamically typed language does not require you to declare the data types of your variables prior to using them, unlike statically typed languages that compel you to explicitly declare the types. 

The fundamental benefit of static typing is that the compiler performs all types of checking; as a result, many trivial errors are discovered relatively early on.

Optional chaining in TypeScript allows you to access properties and methods buried deep inside an object without having to verify the validity of each reference in the chain.

In TypeScript, we have a question mark followed by a period (?.) as the typescript operator for optional chaining.

Before accessing any of the chain's offspring, TypeScript examines each reference in the chain and performs the null or undefined checks. In case the typescript fails the null or undefined check and returns undefined for the entire chain, the execution is instantly terminated.

const user = { 
 userInfo: { 
 name: 'Bill' 
 } 
} 
 
// without optional chaining 
const name = user && user.userInfo && user.userInfo.name || undefined; 
 
// with optional chaining 
const name = user?.userInfo?.name;  

TypeScript provides a lot of advantages, however there are also some disadvantages associated to it, such as: 

  • Excessively complex type system: The type system is a fantastic tool in many ways, but it can occasionally be a little too complicated to use correctly. But rather than being a drawback specific to TypeScript, this is a consequence of TypeScript's complete interoperability with JavaScript, which opens the door to more problems. 
  • The fact that TypeScript requires compilation while JavaScript does not is another cause of problem. However, the majority of JavaScript applications today need a build step, and TypeScript can also be added to it. 
  • TypeScript dedicates a significant amount of time to incorporating the code. 
  • There should be a definition document when using an outside library, however occasionally it is not always available.

The use of types in JavaScript or frontend development in general is not limited to TypeScript. There are other options available that also bring static types to the JavaScript, such as:  

  1. Flow: Flow is likewise a superset of JavaScript, although it is not classified as a separate language. Similar to TypeScript, Flow provides type annotations, but that is about it, and nothing is different from JavaScript in terms of syntax. 
  2. JSDoc: A live standard for JavaScript code documentation. It enables you to annotate types in addition to using text to describe what each line of code does. Although JSDoc is supported by development environments, it is far from ideal. With JSDoc, proper typing is challenging and requires a lot more code than TypeScript. 
  3. Other strictly typed languages: Nowadays, many languages with strict type system and can be compiled to frontend code have been developed. For example languages like Kotlin, Rust, Go, etc. can compile to a functional frontend code. 

One of the most frequently posed TypeScript interview questions, be ready for it.  

Although type aliases and interfaces can generally be used interchangeably, there are two significant differences.  

  • In contrast to an interface, which can always be extended, a type alias is combined using type intersection. Having said that, intersections can indirectly extend themselves after the initial definition.

type BirdType = { 
 canFly: true 
}; 
interface BirdInterface { 
 canFly: true 
} 
type Owl = { nocturnal: true } & BirdType; 
 
interface Chicken extends BirdInterface { 
 colourful: false; 
 flies: false; 
} 
// Because TypeScript is a structural type system, it is also possible to intermix them. 
type Robin = { nocturnal: false } & BirdInterface; 
 
interface Peacock extends BirdType { 
 colourful: true; 
 flies: false; 
}  
  • Two interfaces with the same name combine to become one interface with the same name (also known as Interface Merging), but two types cannot be combined because a type cannot be altered once it has been formed.  
interface BirdInterface { 
 canFly: true; 
} 
 
interface BirdInterface { 
 numOfWings: 2; 
} 
 
// the compiler will merge it into one single interface 
interface BirdInterface { 
 canFly: true 
 numOfWings: 2; 
}  

Moreover, In Typescript an object's type structure can be described using either Interfaces or Type Aliases, however, the official recommendation is to use interface for object definition. 

A tuple is essentially an array with a fixed element size and well-known data types. In other words, a static, clearly defined array would be represented as a tuple. 

Thus, a Tuple has following properties:  

  • The array has a set number of elements. 
  • The tuple elements don't have to be of the same type. 
  • Each element's type is known. 

The TypeScript feature that enables you to limit an array to a particular number of elements and of the exact types is responsible to create tuple. In typeScript, Tuple has a signature of [Type] that is a little different from an array Type[]. 

const array: string[] = ["Bob""Steve""Robin"]; 
const tuple: [stringstringstring] = ["Bob""Steve""Robin"]; 
 
// tuple can also contain elements of different types 
const tuple2: [numberstringboolean] = [1"Steve"true];  

When compared to arrays in JavaScript, tuples in TypeScript is very similar. The same fundamental principles and methodologies apply, such as iterations, filtering, reducing, pushing, pulling, and so on. The key distinction between JS and TS syntax is the limitation on the type and number of data that can be included in an array or tuple.

Classes are a popular abstraction used in object-oriented programming (OOP) languages to define data structures known as objects. These objects might have a starting state and perform actions that are specific to that instance of the object. 

The JavaScript classes are fully supported by TypeScript, and additional capabilities like member visibility, abstract classes, generic classes, and a few more are also added on top of it.

To define a class in TypeScript we majorly follow the same JavaScript syntax with one caveat of defining the type for every class properties and methods.

In TypeScript we must define the type of the properties before using them in the constructor method. For example

class Worker { 
 name: string; 
 salary: number; 
 
 constructor(name: string, salary: number) { 
  this.name = name; 
  this.salary = salary; 
 } 
 payRaise() : void { 
 this.salary += 10000; 
 } 
}  

The worker class can then be used by creating a instance of Worker.

let john = new Worker("John"60000); 
 
console.log(john.salary); // 60000 
john.promote(); 
console.log(john.salary); // 70000 

We can add access modifier as a prefix to the constructor parameters, to bypass the boiler plate type definition before using them in constructor. For example 

class Worker { 
 constructor(public name: stringpublic salary: number) { 
  this.name = name; 
  this.salary = salary; 
 } 
}  

In contrast to well-known object-oriented programming languages like C# and Java, TypeScript does not provide static classes.

Languages like C# and Java require static classes because in these languages all code, including data and functions, must be inside a class and is therefore unable to exist separately. Static classes allow these functions to be used without having to associate them with any objects.

However, in TypeScript, you can create any data and functions as simple objects without first defining them in an enclosing class. Therefore, static classes are not required for TypeScript. In TypeScript, a singleton class is just a straightforward object.

Typically, TypeScript assumes "any" type when we don't specify a type for a variable. For instance, TypeScript compiler runs the code below on the presumption that the parameter "x" is of any type. The code will function without an error as long as the caller passes a string.

function parse(x) { 
console.log(x.split(' ')); 
} 
parse("Hello readers"); // ["Hello", "readers"]

But as soon as we provide a number or some another type other than a string, that does not posses a split() method, the code collapses. For instance,  

function parse(x) { 
console.log(x.split(' ')); // x.split is not a function 
} 
parse(10);  

In TypeScript, you can set the noImplicitAny compiler option to true in the tsconfig.json file. Every time the TypeScript compiler assumes a variable is of any type, it is compelled to raise an error. This stops us from unintentionally making mistakes of the same kind.

function parse(x) { 
console.log(x.split(' ')); //x.split is not a function 
  
parse(10) 

Intermediate

This question is a regular feature in TyepScript questions, be ready to tackle it.  

Generics allows programmers to pass types as parameters to another type, function, class, or other structures.

Making a generic component increases code flexibility, makes components reusable, and eliminates duplication by enabling the component to accept and enforce the types that are passed in when the component is used.

In TypeScript generics are written by writing the type variable inside the angle brackets in the format <T>, where T is a special kind of variable that denotes type. The type variable keeps track of the type that the user gives and works with that particular type only.

We can also use multiple types in the same generic structure, like <T, C, P>.

Example:

function addToCart<T>(product: T, cart: T[]): T[] { 
 return cart.push(product) 
}  

This is a frequently asked interview question on TypeScript.  

When TypeScript code is converted to JavaScript, all types and annotations are erased from the source code.

When JavaScript is being executed, the types you employ in TypeScript cannot be inspected at run-time. The types are only accessible during the compilation and/or transpiration process.

Given that JavaScript is still dynamically typed, it would be useless for V8 or other engines with internal optimization systems to include type-related comments in the code.

For instance, assume this is your TypeScript code:

let name: string = 'Robin';  

After compilation TypeScript compiler emits the following code in a JavaScript file.

let name = 'Robin';

This holds true not only for basic types like number or string but also for any custom types you might develop. Furthermore, it indicates that some of your code is just discarded and is not getting used after being compiled into JavaScript. Therefore, the code given to the user is shorter, and all those interface lines are simply eliminated.  

Expect to come across this popular question in TypeScript interview questions.  

A module is a technique for creating groups for related entities, such as entities like variables, interfaces, classes, etc., in any combination.

Modules might be compared to containers that hold all the components required to complete a task. We can easily import/export modules to readily share code among projects.

The main reason modules are utilized their feature of inaccessibility, which prohibits the contained entities from being accessible from outside the module. Each entity in a module can only be executed from within its own scope, not from the global scope. This gives you a mechanism to restrict access to important variables and functions.

A must-know for anyone heading into a TypeScript interview, this question is frequently asked in TypeScript interview questions.  

Getters and setters are unique methods that enable you to assign various levels of access to private variables based on the requirements of the code. These modifiers are significant if you are adding encapsulation to the class. 

  • The getter method is also known as an accessor because it returns the current value of the property but does not have the power to edit it.  
  • The setter method is also known as a mutator because it lets the programmer modify the value of the variable while hiding the current value from getting accessed from outside of the class. 
class Student{ 
 private _studentId: number; 
 private _firstName: string; 
 private _lastName: string; 

 get studentId() { 
 return this._studentId; 
 } 
 
 set studentId(id: number) { 
 if (id <= 0 || id % 1 !== 0) { 
  throw new Error('The student ID is invalid'); 
 } 
 this._studentId = id; 
 } 
 
 getFullName(): string { 
 return `${this._firstName} ${this._lastName}`; 
 } 
} 

In the code, the _firstname, _lastname, and _studentId are private property and hence cannot be accessed directly outside the scope of the student class. The get and set keywords before the studentId() method define the getter and setter, respectively, for the studentId. The getter and setters do not have a private access modifier and hence can be called outside the scope of the Student class. 

let raju = new Student() 
raju._studentId = 12 // Property '_studentId' is private and only accessible within class 'Student'. 
 
raju.studentId = 12 
let id = raju.studentId; 
console.log(id) // 12 

It's no surprise that this one pops up often in TypeScript questions.  

Enums allow us to create a collection of named constants. It is one of the few non-JavaScript type-level extensions available in TypeScript. We can consider enums as a simpler approach to providing more relevant names to constant values of a typescript program. 

To create an enum in TypeScript, we use the keyword enum, followed by its name and the members. TypeScript provides both numeric as well as string-based approaches to define enums.

Example for Numeric approach:

enum Fruits{ 
APPLE, 
BANANA, 
GRAPES, 
ORANGE 
} 
let fruit: Fruits = Fruits.Banana;  

By default, the enums begin to add numbering to the contained constants from 0. However, this default numbering behavior can be overridden by explicitly assigning the values to members inside an enum. 

Example for string-based approach: 

enum Fruits{ 
APPLE : ‘APPLE’ 
BANANA : ‘BANANA’ 
GRAPES : ‘GRAPES’ 
ORANGE : ‘ORANGE’ 
} 
let fruit: Fruits = Fruits.BANANA;  

After compilation, the TypeScript compiler converts enums into plain JavaScript objects. Thus, as a result, it makes enums a more favorable choice compared to using multiple independent const variables, because it adds type safety and makes the code more readable.

The TypeScript language is divided internally into three layers namely Language, TypeScript Compiler, and TypeScript Language Services. These layers are further broken down into components.

  • Language: It is written in TypeScript and contains elements of the TypeScript language. It incorporates annotations for type, syntax, and keywords. 
  • The TypeScript compiler (TSC): A TypeScript program is converted into JavaScript code via the TypeScript compiler (TSC). It also parses and type-checks our TypeScript code prior to converting it into JavaScript code. 
  • The TypeScript Language Services: Provides information that enables editors and other tools to offer more powerful help features like automated refactoring and IntelliSense. The core-compiler pipeline gains an additional layer of abstraction as a result. It provides features like statement completion, colorization, signature assistance, and code formatting and outlining. 

A common question in interview questions on TypeScript, don't miss this one.  

Although the null and void data types in TypeScript initially appear to be quite similar, they are kept distinct for strong reasons. TypeScript has these two data types for their unique purposes, such as 

Void 

The void data type indicates the absence of a type on a variable, it acts as a reverse of “any” data type. It is particularly helpful in functions that don't return a value. However, declaring variables as void is not especially useful since, if —strictNullChecks is not used, you can only assign undefined or null values to void-type variables.

A function in JavaScript that doesn't explicitly return anything defaults to returning undefined. TypeScript infers that these functions have a void return type.

const greet = (name: string) : void => { 
 console.log(`Hello ${name}`} 
}  

Never 

The "never" type denotes values that never arise. It can be used with functions that never return or with those that consistently throw exceptions.

Moreover, when a type guard is applied for narrowing, TypeScript additionally infers the value is "never" data type if that value can never be true,

const example = (val: string | number) => { 
 if (typeof val === 'string'return val.substr(-2); 
 if (typeof val === 'number'return val.toLocaleString(); 
 else {console.log(val) // val is inferred as "never" 
 } 
} 
let f = () => { 
 throw new Error("Should never get here"); // infers as () => never 
} 
 
let g = function() {  
 throw new Error("Should never get here"); // infers as () => never 
} 
 
function h() {  
 throw new Error("Should never get here"); // infers as () => void 
} 

The three functions are inferring different return types because TypeScript interprets function declarations and function expressions with arrow functions differently. 

When a function is marked as throw-only, it receives the type never for expressions and void for declarations, respectively. Thus the difference arises because f and g are function expressions, where h is a function declaration. 

For instance, let us consider the code below,

function doIf(value: boolean, 
  whenTrue: () => number,  
  whenFalse: () => number): number { 
  return value ? whenTrue() : whenFalse(); 
} 
let x = doIf(2 > 3, 
 () => { throw new Error("haven't implemented backwards-day logic yet"); }, 
 () => 14);

When we think a function should not be called or should only be invoked in error situations, it is typical to write a throwing function. But the call to doIf() will be declined if the type of function expression is void.

This example makes it quite evident that function expressions that only throw should actually return never and not void. And frankly, given that these functions are never, this should be our underlying assumption because, in a correctly-typed program, a value of never data type cannot be observed.

Now the question is why don't all throwing functions never return data type? The quick answer is that doing so turned out to be a big mess. There are many programs mostly the code predating the abstract keyword is written like this.

class Base { 
 overrideMe() { 
 throw new Error("You forgot to override me!"); 
 } 
} 
 
class Derived extends Base { 
 overrideMe() { 
 // Code that actually returns here 
 } 
}  

However, a function that returns void cannot be used in place of a function that returns never. As a result, Derived cannot provide any non-never implementation of that function if Base.overrideMe() returns never. 

Function declarations that always throw are extremely uncommon, although function expressions that always throw frequently appear as a kind of placeholder for Debug.fail().

Declarations are static, whereas expressions frequently get aliased or ignored. In the absence of a return type annotation, it is better to supply void rather than never for a function declaration. Since a function declaration that throws now is actually likely to perform anything valuable tomorrow. 

One of the most frequently posed TypeScript interview questions, be ready for it.  

A variable's type can occasionally change depending on its input. Conditional types change a variable's type in accordance with a preset condition, bridging the type gap between the input and output. An essential TypeScript feature called conditional type helps in the creation of type-safe framework-like code, such as API boundaries.

In TypeScript, conditional types resemble the ternary operators we have in JavaScript. As the name implies, a conditional type selects one of two potential types for the variable based on a condition provided as a type relationship test.

The conditional types are written like this.

condition ? X : Y

Here the type Type X is used if the condition is true whereas type Y is applied if the condition fails. For example

interface Animal { 
 live(): void; 
} 
interface Dog extends Animal { 
 woof(): void; 
} 
type example = Dog extends Animal ? number : string 
console.log(typeof example) // number

A staple in TypeScript interview questions, be prepared to answer this one.  

Distributive conditional types are conditional types in which the checked type is a union-type parameter. When conditional types act on a generic type, they become distributive if the type parameter is of union type. In other words, if we pass a union type as the checked type, then the conditional type will be applied to each member of that union.

Distributive conditional types are automatically distributed over union types after instantiation. For example

type getType<Type> = Type extends U ? X : Y; 
type newType = getType<A | B | C> 

Here, the newType is resolved as

type newType = (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)

Distributivity is typically the desired behaviour. Albeit, you can use square brackets to enclose the extends keyword on each side to prevent that effect. For example:

type ToArray<Type> = Type extends any ? Type[] : never; 
  
type StrArrOrNumArr = ToArray<string | number>; // resolves as type StrArrOrNumArr = string[] | number[] 
 
 
// add square brackets to prevent Distributivity 
type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never; 
  
type StrOrNumArr = ToArrayNonDist<string | number>; // resolves as type StrArrOrNumArr = (string | number)[] 

You can organize your code in TypeScript by using namespaces. Namespaces in TypeScript were formerly known as internal modules, they are based on an earlier edition of the ECMAScript modules. Around September 2013, internal modules were dropped from the ECMAScript specification, however, TypeScript retained the concept under a different name.

Namespaces give programmers the ability to establish independent groups that can be used to store a single or a collection of related functionality, such as properties, classes, types, and interfaces.

namespace DatabaseEntity { 
 class User { 
 constructor(public name: string) {} 
 } 
 
 const user1 = new User("Jon"); 
}  

The absolve code works fine, but if you try to use the User class outside the DatatbaseEntity the TypeScript compiler will give you an error.

Property 'User' does not exist on type 'typeof DatabaseEntity'

You must export the User class in order for it to be accessible outside of your namespace if you wanted to utilize it elsewhere.

namespace DatabaseEntity { 
 export class User { 
 constructor(public name: string) {} 
 } 
 
 const user1 = new User("Jon"); 
} 
 
const user2 = new DatabaseEntity.User("Jane"); 

The three JSX modes that are by default bundled with TypeScript are preserve, react, and react-native. 

  • Preserve: The JSX output of your code can be customized using these modes. The preserve mode's goal is to keep the JSX output within your compiled code so that another compiler can directly operate on it.
  • React-native: The compiler will create a file with a .jsx file extension while this mode is active so that it can be further modified before usage. Similar functionality is provided by the react-native mode, however, the emitted output file will have the .js file extension. 
  • React: React mode functions a little differently. Because it omits the React.createElement modifier, the result is devoid of raw JSX code. When utilising this mode, the output file will have the .js file extension. 

Yes we can use a JSX file in a TypeScript project, however, your TypeScript file must be saved with the a.tsx extension. On a default level, TypeScript comes with three JSX modes preserve, react, and react-native namely.

But there are a few important things to keep in mind while working with JSX and TypeScript in your project. Since JSX has an embeddable XML-like syntax, it must be compiled into valid JS. Thus, to ensure that y our code will now go through more compilation steps, which could affect the performance of your program.

On the other hand, TypeScript offers a number of powerful tools, such as type verification, embedding, and direct JSX compilation, for an enhanced working experience with JSX.

We cannot inherit or extend from more than one class in TypeScript, but Mixins allow us to circumvent this limitation. Mixins encourage reusability and assist you in avoiding the limitations imposed by multiple inheritances.

Mixins produce partial classes that we may combine to create a single class that contains all of the methods and properties from the partial classes. Moreover, Mixins provide the ability to postpone the declaration and binding of methods until runtime, even while attributes and instantiation arguments are defined at compile time.

To construct a mixin, we will use two TypeScript features: interface class extension and declaration merging. TypeScript uses interface class extension to extend multiple classes. And declaration merging is the method used by TypeScript to combine two or more declarations with the same name.

For example, let us create a base class (to which the mixins will be applied) and two contractor classes (to which the base class will extend).

// constructor classes 
class CanFly { 
 fly() { 
 console.log(“flying”) 
} 
} 
  
class CanWalk { 
 walk() { 
 console.log(“walking”) 
} 
} 
  
// base class 
class Bird { 
 x = 0; 
 y = 0; 
} 

Create an interface that merges the expected classes with the same name as your base class (Bird). Due to declaration merging, the Bird class will be merged with the Bird interface.

interface Bird extends CanFly, CanWalk {}

Next, create a function to join two or more class declarations.

function applyMixins(derivedCtor: any, constructors: any[]) { 
 constructors.forEach((baseCtor) => { 
 Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => { 
 Object.defineProperty( 
 derivedCtor.prototype, 
 name, 
 Object.getOwnPropertyDescriptor(baseCtor.prototype, name)  || Object.create(null) 
 ); 
 }); 
 }); 
} 
applyMixins(Bird, [CanFly, CanWalk]);

Here, the applyMixins() function iterates through the CanFly and CanWalk classes, then loops through their list of properties and adds those properties to the Bird class.

Now the Bird instance can directly access the walk() and fly() methods from the CanSwim and CanFly classes, respectively. For example: 

 let bird = new Sprite(); 
bird.fly(); // flying  

A Type Declaration or Type Definition file is a TypeScript file with the.d.ts file extension. As the name implies, a type declaration file only includes type declarations and not actual source code (program logic). These files are not intended to be included in the compilation itself; rather, they are primarily intended to help with the development process.  

Using these declaration files with TypeScript comes with the following two drawbacks: 

  • When you upgrade TypeScript, you also need to deal with modifications to TypeScript's built-in declaration files, which can be challenging given how frequently the DOM APIs change. 
  • It is challenging to modify these files to suit your goals and the requirements of your project's dependencies. For example, you might be compelled to use the DOM APIs if your project dependencies use it.

In TypeScript, the Function is a global type. Along with the other attributes found on all function values, it has properties like bind, call, and apply. It also has the unique quality of always being able to call values of type Function, and these calls can return a value of “any” data type.

function doSomething(f: Function) { 
 return f(123); 
}  

However this is an untyped function call and is typically advised to avoid because of the unsafe “any” return type. 

In general, if you need to accept any function but are not planning to call it, the type () => void is a safer approach than the Function data type. 

function doSomething(f: () => void) { 
 return f(123); 
}

doSomething(() => { 
console.log(‘function called”) 
}) 

TypeScript has a number of globally available utility types that simplify common type transformations. Here are a few of TypeScript's most useful utility types.

Pick<Type, Keys>

The Pick utility type generates a new type by selecting the set of Keys attributes from Type, where Keys may be a single string literal or a union of string literals. The keys of the Type must be the value of Keys for the TypeScript compiler to not raise an error. This utility class is extremely helpful when you wish to make objects lighter by selecting particular properties from objects with many properties.

Omit<Type, Keys>

The Omit utility type is the opposite of Pick because it specifies a collection of properties to be omitted rather than a collection of properties to keep. It is more practical when you simply want to remove a few attributes from an object while keeping others.

Partial<Type>

The Partial utility type creates a type by setting all Type properties optional. When composing an object's update logic, it can be really helpful.

Required<Type>

Unlike Partial, the Required utility type accomplishes the opposite. It creates a type with all the properties of the Type set to mandatory. It can be applied to make sure a type doesn't have any optional properties.

Readonly<Type>

With the Readonly utility type, you can create a type with all of Type's properties set to read-only. Its variable and properties will issue a TypeScript warning if new values are assigned to them.

One of the most frequent causes of unanticipated runtime problems in programming is null pointers. By requiring stringent null checks, TypeScript greatly assists you in avoiding them. 

Here are the two main methods that you can use to enforce strict null checks in TypeScript: 

  • By passing the —strictNullChecks flag to the TypeScript (tsc) compiler. 
  • By updating the value of the strictNullChecks field in the tsconfig.json configuration file to true 

The code in TypeScript ignores null and undefined values when the flag —strictNullChecks is set to false. When this flag is true, null, and undefined both have their unique types, respectively. Hence, if you try to utilize them in a situation where a concrete value is anticipated, the compiler will throw a type error.

Intersection types work similarly to Union types in TypeScript, it empowers you to combine the type of two or more unique types by using the ‘&’ operator. Hence it can be used to combine diverse types in your project to create a new type with all the properties you require. For example:

type Combined = { a: number } & { b: string }; 

In case, both combining types have a property of same name but different type. Then, that property will have an intersection of the two properties in the resultant type.

type Conflicting = { a: number } & { a: string }; 
// Conflicting = { a : number & string } 

When you intersect types, the order of the types is not to be concerned. For example, the typeBothAB and the typeBothBA have all the properties of the two types typeA and typeB i.e.typeBothAB and the typeBothBA have the same type definition. 

type typeBothAB = typeA & typeB; 
type typeBothBA = typeA & typeB; 

The Record type is one of the many powerful utility types available in TypeScript for the transformation of types. When attempting to build more sophisticated types of data, the Record Type is an excellent approach to guarantee consistency. It enforces key-value pairs and allows you to define custom interfaces.

Record<Keys, Type>

The Record utility type creates an object type from property keys in Keys whose corresponding values are of Type. 

For example, let’s define an interface DogInfo with age and number properties. We can use the Record utility type to define the type of the dogs object with the dog names as key and the respective dog information object as its value.

interface DogInfo { 
 age: number; 
 breed: string; 
} 
  
type DogName = "pluto" | "scooby" | "rocky"; 
  
const dogs: Record<DogName, DogInfo> = { 
 pluto: { age: 10, breed: "Labrador" }, 
 scooby: { age: 5, breed: "German Shepherd" }, 
 rocky: { age: 16, breed: "Pitbull" }, 
};

Advanced

This question is a regular feature in TypeScript advanced interview questions, be ready to tackle it.  

Unlike JavaScript, we can create function overloading in a Typescript program. In function overloading, a program can have numerous functions with the same name but different parameters and return types. However, the total number of parameters for all the functions must be the same.

In function overloading, we write two or more overload signatures and one implementation signature.

An overload signature defines the parameter and return types of the function without the function body.

We add an implementation signature at the end. The implementation signature also has the parameter types and return type, but also a body that implements the function. The implementation signature type must be generic enough to include the overload signatures.

// Overload signature 
function doSomething(numA: number, numB: number): number; 
function doSomething(stringA: string, stringB: string): string[]; 
function doSomething(boolA: boolean, boolB: boolean): boolean[]; 
// Implementation signature 
function doSomething(a: unknown, b: unknown): unknown { 
 if(typeof a === ‘number’) return a + b; 
 if (type of a === ‘string’) return [a, b]; 
 return a === b 
} 
doSomething(1, 2) // 3 
doSomething(‘book’, ‘bag’) // [‘book’, ‘bag’] 
doSomething(false, true) // false 
doSomething(1, false) //Error

This is a frequently asked question in TypeScript interview questions.  

Similar to interfaces, abstract classes define an object's contract but prevent direct object instantiation. An abstract class, as opposed to interfaces, can provide implementation details for one or more of its members.

In Typescript, we use the abstract modifier to define an abstract class. Typically, an abstract class has one or more abstract property declarations or methods.

In the implementation class that extends the abstract class, all the abstract members of the class must be defined. Moreover, in the constructor of the implementation class, the super() method must be called.

In the example, the abstract class Animal has one property and two methods. The talk() method is abstract, whereas the move() method has an implementation. All the classes that extend the Animal class have to provide their specific implementation for the write() method, e.g. Cat class and Dog class have their own talk() method. Whereas the non-abstract method move() of the abstract class Animal does not need to be implemented again for every class.

abstract class Animal { 
 name: string; 
 constructor(name: string) { 
 this.name = name; 
 } 
 abstract talk(): void; 
 
 move(): void { 
 console.log(`${this.name} is moving`); 
 } 
} 
 
class Dog extends Animal { 
 constructor(name: string) { 
 super(name); 
 } 
 talk(): void { 
 console.log('Bark!!'); 
 } 
} 
 
class Cat extends Animal { 
 constructor(name: string) { 
 super(name); 
 } 
 talk(): void { 
 console.log('Meow!!'); 
 } 
} 
 
const pluto = new Dog('Pluto'); 
pluto.move(); // "Pluto is moving" 
pluto.talk(); // "Bark!!" 
 
const tom = new Cat('Tom'); 
tom.move(); // "Tom is moving" 
tom.talk(); // "Meow!!" 

Expect to come across this popular question in TypeScript questions.  

In TypeScript, constructor overloads work similarly to function overloading.  

To create overloading for class constructors in TypeScript, you first define all the overloading constructors in layers. Immediately followed by the original constructor definition under the overloading constructors.

For example, let's create a class called Car with properties name having string type and yearMade having number type. The Car class also has a constructor method to initialize the value for the name and yearMade property fields,

class Car { 
 name: string; 
 yearMade: number; 
 
 constructor(name: string, yearMade: number) { 
 this.name = name; 
 this.yearMade = yearMade; 
 } 
} 

Next to overload the class constructor with only yearMade parameter, we define the new constructor directly above the original constructor. 

Moreover, all the constructors in an overloading class can have different parameters of different types, however, the total number of permanent parameters must remain the same. In other words, the Car class can have a minimum of one parameter thus, for the constructor that takes more than one parameter, we shall make the other parameters optional. Moreover, the type for all the permanent parameters must be generic enough, hence use the type “any” for the parameters in the original constructor.

class Car { 
 name: string; 
 yearMade: number; 
 
 // constructor overload here 
 constructor(yearMade: number); 
 constructor(nameOrYear: any, yearMade?: any) { 
 this.name = nameOrYear; 
 this.yearMade = yearMade; 
 } 
} 

A must-know for anyone heading into a TypeScript interview, this question is frequently asked in TypeScript interview.  

Inheritance is an important concept in object-oriented programming, specifically for polymorphism and code reusability. There are two main keywords that TypeScript uses for inheritance – extends and implements.

When a class extends to another class, all the members from the parent class are transferred to the child class. Any of its parent's existing properties and methods can be overridden, and new ones can be added.

When a class implements another class or interface, the class must implement all the members from the implemented class or interface. The implements keyword serves as an agreement that the implemented class has to follow. TypeScript's job is to make sure that the class maintains the exact shape similar to the class or interface it is implementing.

Here is a simple example to explain both.

class User { 
 name: string; 
 userId: number; 
greet: () => void 
} 
 
// Max will inherit the name and userId from the User class 
class Max extends User {} 
 
// this will result in an error as Rahul doesn't have all the properties that the User class has 
class Rahul implements User {} 
 
// This is valid as Rahul satisfies the contract (i.e. contains all the member properties and methods) specified by the User class 
class Rahul implements User { 
 name = 'Rahul'; 
 userId = 19724 
 greet = function () { 
 console.log(`Hello ${this.name}`) 
 } 
}

It's no surprise that this one pops up often in TypeScript interview questions.  

A decorator is a special declaration used to supplement existing code with annotations and metadata to your program syntax. They are used in a declarative manner and require enabling the experimentalDecorators either on the command line or in your tsconfig.json compiler function in order to utilize them.

A class declaration, method, accessor, property, or argument can all be decorated with the decorator declaration type. Decorators take the form @expression, where this sign is written as a prefix to decorators when an expression should evaluate a function. When the decorated declaration is called at runtime, the information from the decorated declaration and this function will both be called.

When we put multiple decorators to a single declaration syntax, their evaluation is similar to the composition of functions present in mathematics. For instance, in the code below, the @first and @second decorators create a composite function similar to (f ∘ g)(x)is equivalent to f(g(x)) in mathematics.

The compiler evaluates these decorators in the following order: 

  1. Each decorator expression first gets analyzed from top to bottom 
  2. After that, the results are invoked as functions going from bottom to top.
function first() { 
 console.log("first"); 
 return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { 
 console.log("first(): called"); 
 }; 
} 
  
function second() { 
 console.log("second"); 
 return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { 
 console.log("second(): called"); 
 }; 
} 
  
class Foo { 
 @first() 
 @second() 
 method() {} 
} 

A common question in interview questions on TypeScript, don't miss this one.  

Triple-slash Directives are single-line comments that include an XML element tag in them. Similar to double-slash comments in JavaScript and TypeScript, Triple-slash directives can be used to write comments although they are a lot more powerful and can also provide instructions for your compiler. 

Triple-slash directives only execute when they are at the top of the enclosed file. You can write comments before them, including additional triple-slash directives. However, if they come after any other kind of statement or declaration, your compiler will consider them as standard comments and ignore them.

/// <reference path="..." /> : Adding this to the beginning of your TypeScript code informs your compiler that there are file dependencies in the program. The compilation process will subsequently incorporate these extra files thanks to your compiler.

/// <reference types="..." /> : This is also similar to the path triple-slash directive, but instead of file dependencies, it informs the compiler about the package dependencies in the program.

/// <reference lib="..." /> : This also works similarly to the above two but is used for including the built-in lib file in your program. 

One of the most frequently posed TypeScript interview questions, be ready for it.  

Declarative merging in Typescript refers to the process by which the compiler creates a single definition out of several declarations with the same name. To improve the efficiency of your code, the TypeScript compiler combines (or merges) these declarations into a single definition. 

This merged definition will retain the characteristics of the original definitions from which this was constructed. All entities, including interfaces, namespaces, enums, and other objects, can be combined, excluding the classes.

For instance, here is a Typescript code with multiple interfaces with the same name

interface Animal {  
 doSomething(something: Fly): Fly  
 
interface Animal {  
 doSomething(something: Jump): Jump 
 
interface Animal {  
 doSomething(something: Run): Run  
}  

The typescript compiler will merge the three definitions from the above code into a single definition like this:

interface Animal { 
 doSomething(something: Fly): Fly  
 doSomething(something:Jump): Jump 
 doSomething(something: Run): Run 
}  

A staple in TypeScript interview questions, be prepared to answer this one.  

In TypeScript, the declare keyword is utilized as the prefix for the Ambient declaration of variables and methods. The declare keyword tells the TypeScript compiler that the concerned entity exists in the global scope and can be used inside your code. 

It works similarly to an import keyword, ambient declarations inform the compiler about the source code that is stored elsewhere in a different file. In order to use third-party libraries like JavaScript, jQuery, Node, etc., we generally utilize Ambient declarations in TypeScript. The declare keyword reduces the likelihood of errors in our TypeScript code by directly integrating these packages. The typical location for ambient declarations is a type declaration file with the '.d.ts' extension.

We can use the declare prefix to declare: 

  • variable (const, let, var). 
  • type or an interface 
  • class 
  • enum 
  • function 
  • module 
  • namespace,

For instance, you want to use an API script in your TypeScript code. First, you must provide access to the API link on your HTML page.

<script async src="https://www.someAPI.com/gtag/js?id=TAG_ID"></script> 
<script> 
 window.dataLayer = window.dataLayer || []; 
 function gtag(){dataLayer.push(arguments)}; 
 gtag('js'new Date()); 
 
 gtag('config''TAG_ID'); 
</script>  

In your HTML, the dataLayer array is declared. However, your TypeScript programme does not know about it. If you wish to utilize the dataLayer array, you must declare it in a .d.ts file.

declare const dataLayer: any[]; 

Similarly, to use any other external code, we must declare it first and inform the TypeScript Compiler. For example: 

declare module '*.png' { 
 const src: string; 
 export default src; 
} 
declare function greet(hello: string): void; 

We should be cognizant that the ambient declarations occasionally lead to problems because your compiler will produce an error if the source changes. Thus, to prevent such errors if the location of your external source code changes, you must update all of your ambient declarations.

TypeScript definition Manager or TSD is a file package manager for TypeScript that allows you to easily download and install definition files to use in TypeScript projects. TSD makes it very easy to find and install TypeScript definition files of many public APIs, libraries, etc. from the free and open-source DefinitelyTyped repo. 

TSD is quite helpful because it enables you to use type definition files directly in your TypeScript code. For instance, you could use the following syntax to add some jQuery code to your.ts file:

$(document).ready(function() { //Your jQuery code });

Your compiler will inform you that it cannot locate the name "$", due to the fact that this type is a part of jQuery. You may use TSD to locate and download the jQuery Type Definition file, which you can then include in your.ts file, and your compiler will have everything it requires. 

It's no surprise that this one pops up often in Angular TypeScript interview questions.  

Your code can benefit from comprehensive inspections and more precise tooling thanks to TypeScript's strictness settings. The more you tweak your parameters, the more rigorously TypeScript evaluates your code. There are a number of type-checking flags available in TypeScript that can be turned on or off. The two most crucial to remember are:

  • noImplicitAny: TypeScript assigns any type whenever we don't define a specific type in our code. If this flag is set to true, variables whose type is implicitly assumed to be any will produce an error. This flag is mainly useful because using the type any negates the purpose of adopting TypeScript over JavaScript in the first place.

We add the following code in our tsconfig.json, to mark the noImplicitAny flag true in our TypeScript project.

{ 
 "compilerOptions" : { 
 "noImplicitAny" : true 
 } 
} 
  • strictNullChecksThis flag makes the handling of null and undefined data more explicit and rigorously verifies that we are handling all the null and undefined variables in our TypeScript code. When the strictNullChecks is false, the typescript compiler ignores the null and undefined values, this can throw unexpected errors at runtime. Whereas when the strictNullChecks is true, the typescript compiler handles the null and undefined as their own unique types. Thus, it will show a type error if you try to use them where a concrete value is expected. 

Use the following code in your tsconfig.json to mark strictNullChecks as true.

{ 
 "compilerOptions" : { 
 "strictNullChecks" : true 
 } 
}  
{ 
 "compilerOptions": { 
 "outDir""./built", 
 "allowJS"true, 
 "target""es5" 
 }, 
 "include": ["./src/**/*"] 
  
}  

The code snippet is taken from the tsconfig.json file, which is found at the root of every TypeScript project. It is used to administer the project's settings, including which files to include and what kinds of checking we want to conduct, among other things. 

  • compilerOptions - This comprises the majority of TypeScript's configuration choices and gives us control over how the language should operate internally. Additionally, the code snippet also illustrates some of the various settings that are available inside it. 
  • "outDir": "./built" - When this key is provided, the compiler will push the output file (production code files) into the directory listed in this key's value. This piece of code tells the computer to "emit all of the output files in the built folder." The project will be organized as follows. 
├── built 
│ └── index.js // output .js file 
├── index.ts // input .ts file 
└── tsconfig.json  

Unless otherwise provided, the compiler will produce output.js files in the same directory as the input.ts files, such as:  

src 
├── index.js 
└── index.ts  
  • "allowJS": true - This boolean code allows us to also use JavaScript files as our input files along with .ts files in our typescript project. Since here the boolean is true, this key allows .js files to be imported inside our project instead of just .ts and .tsx files. The following code will cause an error if this key is set to "false":  
// greetings.js 
export const morningGreeting = "Good Morning"  
// index.ts 
import { morningGreeting} from "./greetings" 
console.log(morningGreeting) 
  • "target": "es5" - This line of code directs the translation of the program written in more recent JavaScript syntax into the syntax of an earlier version, such as ECMAScript 5 ("es5"). 
  • "include": ["./src/**/*"] This expression essentially means to read in any files it understands from the src directory. Here the “include” key simply defines a list of filenames or regex patterns.

In TypeScript, decorators are declarations that can be attached as a prefix to class declarations, methods, accessors, properties, or parameter declarations. They are written as a "@expression," and evaluate some function that gets invoked during the runtime. 

In the question, a class decorator is declared just above a class declaration. This decorator will use the class constructor as its target and is usually good to observe, modify or replace the class declaration. 

Although Decorator Factories are functions that return another function and do not actually implement the decorator, the returned function oversees doing so and should assume the responsibility of a wrapper function.

In the question, we have a class decorator `someDecorator`, by using a decorator factory, we can write it as:

const someDecorator = function (canSwim: boolean) { 
 return (target: Function) => { 
 // some function logic 
 console.log(`The animal ${canSwim ? ‘can’ : ‘cannot’} swim`) 
 } 
}

The decorator ‘someDecorator’ takes one parameter and returns another function that implements the decorator itself. The value of the boolean parameter (either true or false) can now be passed to a Person class as follows:  

const someDecorator = function (canSwim: boolean) { 
 return (target: Function) => { 
 // some function logic 
 console.log(`The animal ${canSwim ? 'can' : 'cannot'} swim`) 
 } 
} 
 
@someDecorator(true) 
class Animal {}  
// calculate.ts 
declare module CalculateModule { 
 export class Maths { 
 doSubtract(numA: number, numB: number): number; 
 } 
} 
 
// main.ts 
var maths = new CalculateModule.Maths(); 
maths.doSubtract(125) 

In the given program files, we are using the `Ambient` declaration, which informs the compiler that the actual piece of code exists elsewhere at a different file location. To write an Ambient declaration we use the following syntax:

declare module moduleName { 
// module data 
}  

Thus, the code sample provided to us in the question is coming from the module "CalculateModule," which exports the "Maths" class. The “Maths” class contains “doSubtract()” function which accepts two parameters numA and numB of the number type and returns a number.

This Ambient declaration is now being used in the Main.ts file when we construct a new instance maths of the Maths class. Maths.doSubtract(12, 5) will return 7 as 12 - 5 = 7 as the result of main.ts.

{ 
 "typeAcquisition": { 
 "enable"false 
 } 
}  

The above code shows TypeScript's Type Acquisition functionality, which is only required for JavaScript projects. In the configuration, we are deactivating the type acquisition for JavaScript projects by setting the 'enable' value to 'false'.

Typically, we must explicitly include the types in our TypeScript projects. However, the TypeScript tooling downloads all the types of modules we use in the background for JavaScript projects. The other "typeAcquisition" properties available in TypeScript are as follows: 

  • include - Using this, we define an array containing all the types we want to utilize in our project from the DefinitelyTypes. For instance:
{ 
 "typeAcquisition": { 
 "include": ["jquery""jest"] 
 } 
}  
  • exclude - Here, this provides the option to turn off type-acquisition for a specific module or an array of modules in JavaScript projects. For instance:
{ 
 "typeAcquisition": { 
 "exclude": ["mocha"] 
 } 
}  
  • disableFilenameBasedTypeAcquisition - This is a boolean that controls whether TypeScript should use the filenames available in the project to infer what types should be added. This is how we can incorporate it:
{ 
 "typeAcquisition": { 
 "disableFilenameBasedTypeAcquisition"true 
 } 
} 

In version 4.7 of TypeScript, it introduced the triple-slash directives to contain the resolution-mode attribute. This is how it appears syntactically:

/// <reference types="pkg" resolution-mode="require" /> 
// or 
/// <reference types="pkg" resolution-mode="import" /> 

Previously, the mode of containing files and the syntax we employed determined how imports were resolved in the project when utilizing Node's default ECMAScript resolution. However, using "resolution-mode," we are now able to refer to the types of CommonJS modules from an ECMAScript module or vice-versa.

Additionally, you may use "import type" to define an import assertion like: 

// Resolve `pkg` as if we were importing with a `require()` 
import type { TypeFromRequire } from "pkg" assert { 
 "resolution-mode""require" 
}; 
 
// Resolve `pkg` as if we were importing with an `import` 
import type { TypeFromImport } from "pkg" assert { 
 "resolution-mode""import" 
}; 
 
export interface MergedType extends TypeFromRequire, TypeFromImport {}  
interface Form<T> { 
 values: T; 
 errors: any; 
} 
 
const contactForm: Form<{name: string; email: string}> = { 
 values: { 
 name: "John", 
 email: "john@email.com" 
 }, 
 errors: { 
 emailAddress: "Invalid email address." 
 } 
}  

The code given to us in the question employs a generic Form interface and uses this interface as the type of the contactForm variable. However, the errors property in the contactForm refers to a field with the wrong name. It should be “email” as defined in the Form interface, instead of the current “emailAddress” property. Hence here is the fixed program:

interface Form<T> { 
 values: T; 
 errors: any; 
} 
 
const contactForm: Form<{name: string; email: string}> = { 
 values: { 
 name: "John", 
 email: "john@email.com" 
 }, 
 errors: { 
 email: "Invalid email address." 
 } 
} 

Because the errors property's type is set to any on the Form interface, we do not get a type error. There will be no type-checking as the consequence. We utilize the type 'any' whenever we do not want a specific value to result in type-checking issues.  

The Map TypeScript file in your TypeScript project converts the human-unreadable trans-compiled JavaScript back to readable TypeScript format. This is useful when debugging is required during production because the source Map will be used. 

In Typescript, most commonly there are two kinds of .map files, namely Source Map (.js.map) and Declaration Map (.d.ts.map). 

Source Map (.js.map):  

Each section of your generated JavaScript code is linked to the exact line and column of the corresponding Typescript file using JSON-formatted mapping definitions found in source map (.js.map) files.

When source maps are enabled, Visual Studio Code and Chrome DevTools will display your Typescript code during debugging rather than the generated complex and intricate JavaScript code. 

Declaration Map (.d.ts.map).:  

Declaration source maps (.d.ts.map) files include mapping definitions that connect each of the type declarations created in.d.ts files back to your original source file (.ts). The mapping definitions in these files are in JSON format.

This is helpful in code navigation when you have split a big project into small multiple projects using project references. You can utilize code editor tools like "Go to Definition" and "Rename" to traverse and edit code across subprojects in a transparent manner. 

When dealing with class members, TypeScript utilizes a variety of access modifiers. The access modifier makes the class members more secure and protects them from unauthorized use. It can also be used to manage a class's data members' visibility.  

There are three distinct access modifiers in TypeScript: 

  • public: A class that is public can be accessed by all of its members, including its child classes and class instances. 
  • private: Private denotes that a class's members can only access one another. 
  • protected: When a class is marked as protected, all of its members and the members of its children classes can access it, but the class instance cannot. 

When there is no access modifier before a class member in the code, TypeScript automatically applies the public access modifier to all members of the class. This might cause problems with compliance processes, thus you should explicitly define the access behavior whenever possible.

Furthermore, after your TypeScript code has been compiled, class modifiers have absolutely no impact on your final JavaScript code. Since these modifiers are only present in typescript and not taken into account when the compiler generates the final JS. 

It is common practice to modify an existing type by making each of its properties optional. Given how frequently this occurs in JavaScript, TypeScript offers a feature called mapped types that enables you to define new types based on pre-existing ones. In a similar fashion, the new type converts each property of the old type into a mapped type. For instance, you may make all properties read-only or optional.

A mapped type is a generic type that builds types by iterating through keys using a union of PropertyKeys, which are commonly constructed using "keyof" keyword.

For instance, OptionsFags<Type> is “Mapped type,” it is a generic type that takes a type of parameter and iterates over its members to declare the same member's name properties of the boolean type.

type OptionsFlags<Type> = { 
 [Property in keyof Type]: boolean; 
}; 
 
type FeatureFlags = { 
 darkMode: () => void; 
 newUserProfile: () => void; 
}; 
  
type FeatureOptions = OptionsFlags<FeatureFlags>;  

Here, the FeatureOptions type copies and define properties of boolean type for all the corresponding member names of FeatureFlags.   

type FeatureOptions = { 
 darkMode: boolean; 
 newUserProfile: boolean; 
} 

To ensure that TypeScript and JavaScript support performs commendably out of the box, TypeScript includes a number of declaration files (.d.ts files). These declaration files contain information on the various JavaScript APIs as well as the DOM APIs that are common across all browsers. You can adjust the lib setting in the tsconfig.json to specify which declaration files your project needs, albeit there are some reasonable defaults based on your target.

Similar to @types/ support, TypeScript includes a feature that lets you override a particular built-in library. When deciding which lib files to include, TypeScript will look for a scoped @typescript/lib-* package in node modules. Next, you may use your package manager to install a specific package to substitute for a specific one.