20 TypeScript Interview Questions and Answers You Should Prepare For

typescript interview questions and answers
Summary:

Use this list of TypeScript interview questions and answers to prepare for your upcoming meeting with a technical recruiter or lead engineer!

Looking to hire a TypeScript developer or hoping to land a job as a TypeScript developer? These TypeScript interview questions and answers can help you put together a comprehensive interview guide.

TypeScript is closely related to JavaScript. You can refer to our JavaScript interview questions guide if you need to review interview questions specifically about JavaScript.

In this guide, we break down the important TypeScript interview questions to know into three groups:`

You can expect to learn about what questions you should ask, what answers to expect, and what to focus on evaluating for each answer. Additionally, we’ll explain why you should ask these questions, what to look for, and what you want to draw out from your candidates.

And, at the end of the article (as well as a few places throughout), we’ll link you to some other helpful interview advice and job hunting guides.

Let’s get started!

Related GuidesJavaScript Interview Questions • Angular Interview Questions • React Interview Questions • Selenium Interview Questions • Java Interview Questions

Basic TypeScript Interview Questions

The following set of questions should test a job candidate’s basic knowledge of TypeScript and some of its core features.

1. What is TypeScript and how does it differ from JavaScript?

This is a fundamental question that interviewers typically ask to gauge the candidate’s familiarity with the language and how comfortable they are with its features. Make sure your candidate can describe TypeScript as a language, its core features, and how it relates to JavaScript.

TypeScript is a superset of JavaScript that compiles to plain JavaScript. Conceptually, the relationship between TypeScript and JavaScript is comparable to that of SASS and CSS. In other words, TypeScript is JavaScript’s ES6 version with some additional features.

TypeScript is an object-oriented and statically typed language, similar to Java and C#, whereas JavaScript is a scripting language closer to Python. The object-oriented nature of TypeScript is complete with features such as classes and interfaces, and its static typing allows for better tooling with type inference at your disposal.

From a code perspective, TypeScript is written in a file with a .ts extension whereas JavaScript is written with a .js extension. Unlike JavaScript, TypeScript code is not understandable by the browsers and can’t be executed directly in the browser or any other platform. The .ts files need to be transpiled using TypeScript’s tsc transpiler to plain JavaScript first, which then gets executed by the target platform.

2. What are the benefits of using TypeScript?

Each programming language has its pros and cons and the reasons behind its applications. You might ask this question to gain insight into the candidate’s experience using TypeScript and how it has affected the developer experience and the project’s workflow.

Instead of just listing out TypeScript’s benefits, make sure to encourage your candidates’ to talk about how a typical project could benefit from TypeScript. This could range from better maintainability in the long run or increased developer productivity because of the features that TypeScript offers.

An immediate advantage of using TypeScript is its tooling. TypeScript is a strongly typed language that uses type inferences. These characteristics open the doors to better tooling and tighter integration with code editors. TypeScript’s strict checks catch your errors early, greatly reducing the chances of typos and other human errors from making their way to production. From an IDE’s perspective, TypeScript provides the opportunity for your IDE to understand your code better allowing it to display better hints, warnings, and errors to the developer. For example, TypeScript’s strict null check throws an error at compile time (and in your IDE) preventing a common JavaScript error of attempting to access a property of an undefined variable at runtime.

A long-run advantage of using TypeScript is its scalability and maintainability. The ability to describe the shape of objects and functions directly in your code makes your codebase easier to understand and more predictable. When used correctly, TypeScript provides a more standardized language resulting in better readability which could save time and effort down the road as the codebase grows.

Read More: Common Non-Technical Interview Questions for Software Developer Jobs

3. What are interfaces in TypeScript?

Similar to other TypeScript features-related questions, this question tests the candidate’s level of knowledge and familiarity with the language’s feature. An ideal answer should also include its usage and the benefits of using interfaces.

Interfaces are TypeScript’s way of defining the syntax of entities. In other words, interfaces are a way to describe data shapes such as objects or an array of objects.

We declare interfaces with the help of the interface keyword, followed by the interface name and its definition. Let’s look at a simple interface for a user object

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

The interface can then be used to set the type of a variable (similar to how you assign primitive types to a variable). A variable with the User type will then conform to the interface’s properties.

let user: User = {
  name: "Bob",
  age: 20, // omitting the `age` property or a assigning a different type instead of a number would throw an error
};

Interfaces help drive consistency in your TypeScript project. Furthermore, interfaces also improve your project’s tooling, providing better autocomplete functionality in your IDEs and ensuring the correct values are being passed into constructors and functions.

4. How do you create a new type using a subset of an interface?

Being able to modify an interface is useful to remove duplicate code and max_Being able to modify an interface is useful to remove duplicate code and maximize the reusability of existing interfaces (where it makes sense). This question covers one of the many features that TypeScript offers to create a new interface from an existing interface.

TypeScript has a utility type called omit that lets you construct a new type by passing a current type/interface and selecting the keys to be excluded from the new type. The example below shows how you create a new type UserPreview based on the User interface, but without the email property.

interface User {
  name: string;
  description: string;
  age: number;
  email: string;
}

// removes the `email` property from the User interface
type UserPreview = Omit<User, "email">;

const userPreview: UserPreview = {
  name: "Bob",
  description: "Awesome guy",
  age: 20,
};

Read More: 31 Questions to Ask at an Interview for Software Development Jobs

5. How do “enums” work in TypeScript?

Enums are a common data structure in most typed languages. Understanding enums and how to use them is essential in organizing code and making code more readable. The candidate should be able to explain enums at a high level, along with their basic features and usage.

Enums or enumerated types are a means of defining a set of named constants. These data structures have a constant length and contain a set of constant values. Enums in TypeScript are commonly used to represent a set number of options for a given value using a set of key/value pairs.

Let’s look at an example of an enum to define a set of user types.

enum UserType {
  Guest = "G",
  Verified = "V",
  Admin = "A",
}

const userType: UserType = UserType.Verified;

Under the hood, TypeScript translates enums into plain JavaScript objects after compilation. This makes the use of enums more favorable compared to using multiple independent const variables. The grouping that enums offer makes your code type-safe and more readable.

6. What are arrow functions in TypeScript?

Arrow function is a popular feature of ES6 and TypeScript that introduces an alternate shorter way of defining functions. Although shorter, arrow functions come with their pros and cons, which are important to consider when choosing which method to use.

Arrow functions, also known as lambda functions, provide a short and convenient syntax to declare functions. Arrow functions are often used to create callback functions in TypeScript. Array operations such as mapfilter, and reduce all accept arrow functions as their arguments.

However, arrow functions’ anonymity also has its downsides. If not careful, the shorter arrow function syntax can be more difficult to understand. Furthermore, arrow functions’ nameless nature also makes it impossible to create self-referencing functions (i.e. recursions).

Let’s take a look at a regular function that accepts two numbers and returns its sum.

function addNumbers(x: number, y: number): number {
  return x + y;
}

addNumbers(1, 2); // returns 3

Now let’s convert the function above into an arrow function.

const addNumbers = (x: number, y: number): number => {
  return x + y;
};

addNumbers(1, 2); // returns 3

Read More: Time Management Skills for Developers: Best Tips, Tools, and Strategies

7. What are the differences between var, let, and const?

TypeScript provides three ways to declare a variable, each with its recommended use cases. The ability to differentiate between the three keywords and understanding which to use when plays an important role in writing quality code. As the candidate, make sure that you explain the use case for each of the keywords along with their behavior.

  • var: Declares a function-scoped or global variable with similar behavior and scoping rules to JavaScript’s var variables. var variables don’t require setting its value during its declaration.
  • let: Declares a block-scoped local variable. let variables don’t require setting a value of a variable during its declaration. Block-scoped local variable means that the variable can only be accessed within its containing block, such as a function, an if/else block, or a loop. Furthermore, unlike varlet variables cannot be read or written to before they are declared.
// reading/writing before a `let` variable is declared
console.log("age", age); // Compiler Error: error TS2448: Block-scoped variable 'age' used before its declaration
age = 20; // Compiler Error: error TS2448: Block-scoped variable 'age' used before its declaration

let age: number;

// accessing `let` variable outside it's scope
function user(): void {
  let name: string;
  if (true) {
    let email: string;
    console.log(name); // OK
    console.log(email); // OK
  }
  console.log(name); // OK
  console.log(email); // Compiler Error: Cannot find name 'email'
}
  • const: Declares a block-scoped constant value that cannot be changed after it’s initialized. const variables require an initialization as part of its declaration. This is ideal for variables that don’t change throughout their lifetime.
// reassigning a `const` variable
const age: number = 20;
age = 30; // Compiler Error: Cannot assign to 'age' because it is a constant or read-only property

// declaring a `const` variable without initialization
const name: string; // Compiler Error: const declaration must be initialized

Sidenote: Linters does a great job in catching these in your IDEs and compilers will throw an error if a rule is violated as a result of misusing these keywords.

8. When do you use a return type of never and how does it differ from void?

This is a question that showcases the candidate’s understanding of TypeScript’s types and their use cases in the context of defining return types. The candidate should be able to distinguish between the two types and identify the return type of a function by looking at its contents.

Before diving into the differences between never and void, let’s talk about the behavior of a JavaScript function when nothing is returned explicitly.

Let’s take the function in the example below. It doesn’t explicitly return anything to the caller. However, if you assign it to a variable and log the value of the variable, you will see that the function’s value is undefined.

printName(name: string): void {
  console.log(name);
}

const printer = printName('Will');
console.log(printer); // logs "undefined"

The above snippet is an example of void functions. Functions with no explicit returns are inferred by TypeScript to have a return type of void.

In contrast, never is a type that represents a value that never occurs. For example, a function with an infinite loop or a function that throws an error are functions that have a never return type.

const error = (): never => {
  throw new Error("");
};

In summary, void is used whenever a function doesn’t return anything explicitly whereas never is used whenever a function never returns.

Read More: Problem-Solving Skills for Software Developers: Why & How to Improve

9. What access modifiers are supported by TypeScript?

Access modifiers and encapsulation go hand-in-hand. A question like this is an opportunity to understand the candidate’s knowledge of the concept of encapsulation and TypeScript’s approach to data access restrictions. You may also be looking for real-world applications of access modifiers, the reasoning behind them, and their advantages.

The concept of “encapsulation” is used in object-oriented programming to control the visibility of its properties and methods. TypeScript uses access modifiers to set the visibility of a class’s contents. Because TypeScript gets compiled to JavaScript, logic related to access modifiers is applied during compile time, not at run time.

There are three types of access modifiers in TypeScript: publicprivate, and protected.

  • public: All properties and methods are public by default. Public members of a class are visible and accessible from any location.
  • protected: Protected properties are accessible from within the same class and its subclass. For example, a variable or method with the protected keyword will be accessible from anywhere within its class and within a different class that extends the class containing the variable or method.
  • private: Private properties are only accessible from within the class the property or method is defined.

To use any of these access modifiers, add the publicprotected, or public (if omitted, TypeScript will default to public) in front of the property or method.

class User {
  private username; // only accessible inside the `User` class

  // only accessible inside the `User` class and its subclass
  protected updateUser(): void {}

  // accessible from any location
  public getUser() {}
}

Violating the rules of the access modifier, such as attempting to access a class’s private property from a different class will result in the following error during the compilation process.

Property '<property-name>' is private and only accessible within class '<class-name>'.

In conclusion, access modifiers play an important role in arranging your code. They allow you to expose a set of public APIs and hide the implementation details. You can think of access modifiers as a form of entry gates to your class. Effective use of access modifiers reduces the chance of errors from misusing another class’s contents significantly.

10. What are generics and how to use them in TypeScript?

In addition to the “what”, an interviewer asking this question might also be looking for answers on how generics tie into the code’s flexibility and the reasoning behind using generics over other types. Your candidate should be comfortable discussing what generics are, how it works, and it’s benefits.

Good software engineering practice often encourages reusability and flexibility. The use of generics provides reusability and flexibility by allowing a component to work over a variety of types rather than a single one while preserving its precision (unlike the use of any).

Below is an example of a generic function that lets the caller define the type to be used within the function.

function updateUser<Type>(arg: Type): Type {
  return arg;
}

To call a generic function, you can either pass in the type explicitly within angle brackets or via type argument inference, letting TypeScript infer the type based on the type of the argument passed in.

// explicitly specifying the type
let user = updateUser<string>("Bob");

// type argument inference
let user = updateUser("Bob");

Generics allows us to keep track of the type information throughout the function. This makes the code flexible and reusable without compromising on its type accuracy.

Looking to hire the best remote developers? Explore HireAI to see how you can:

⚡️ Get instant candidate matches without searching
⚡️ Identify top applicants from our network of 300,000+ devs with no manual screening
⚡️ Hire 4x faster with vetted candidates (qualified and interview-ready)

Try HireAI and hire top developers now →

Intermediate TypeScript Interview Questions

The following set of questions should test the candidate’s intermediate knowledge of TypeScript and some of its widely used features.

1. When should you use the unknown type?

Knowing the differences between unknown and any and when to use which is important working with TypeScript. unknown is usually the preferred option because of its safer typing. Your candidate should be able to explain what the unknown type is used for and how to use it.

unknown is a special type that is similar to any. Like any, a common use case of the unknown type is when you don’t know the exact type upfront. unknown variables accept any value. However, when trying to operate on an unknown variable, TypeScript requires a type check or a type assertion. This difference makes unknown a

Let’s look at two examples to highlight the differences between any and unknown types.

The snippet below shows a valid TypeScript code where you could pass in any value as the callback parameter, and the invokeCallback method will try to call it. This function doesn’t throw any compile-time error, however, it could lead to a run time error when you pass in a variable that’s not callable as the callback parameter.

invokeCallback(callback: any): void {
  callback();
}

The unknown equivalent of the above example requires you to add a type check before calling the callback function. The additional type check prevents the run time error above from happening.

invokeCallback(callback: unknown): void {
  if (typeof callback === 'function') {
    callback();
  }
}

To summarize, you can assign anything to both an any and an unknown type. However, unknown types require a type check or type assertion to perform any operation on the variable.

2. What is noImplicitAny and what is its purpose?

This is a TypeScript configuration question that would give you insight into the level of familiarity the candidate has when configuring a TypeScript project. Although this is only one property from TypeScript’s list of configurable properties, noImplicitAny plays an important role in larger projects.

noImplicitAny is a property in a TypeScript project’s configuration file (tsconfig.json) that modifies how TypeScript’s compiler handles your project’s implicit any types. The noImplicitAny flag can be set to either true or false and can always be changed later. There is no correct answer as to what this value should be as each project is different. To be able to decide on what the flag should be set to, it is important to understand the differences between when it’s turned on and when it’s off.

When the noImplicitAny flag is false (by default), the compiler doesn’t infer the variable type based on how it’s used. Instead, the compiler defaults the type to any.

On the other hand, when the noImplicitAny flag is true, the compiler will attempt to infer the type, throwing a compile-time error if the compiler isn’t able to infer the type.

Setting the noImplicitAny flag to either true or false doesn’t prevent you from setting a variable’s type to any.

Read More: 10 Ways to Improve Leadership Skills (& Advance Your Developer Career)

3. What are conditional types in TypeScript?

Sometimes, a variable’s type is dependent on its input. Conditional types help bridge the input and output’s type – transforming a variable’s type based on a set condition. Conditional type is an important TypeScript feature that helps write type-safe framework-like code such as API boundaries. Although the candidate might not use this feature directly every day, they might be using them indirectly when using code from other frameworks or libraries.

Conditional types in TypeScript are similar to ternary operators. As the name suggests, it assigns a type to the variable based on a condition.

The general expression for defining a conditional type in TypeScript is as follows:

A extends B ? X : Y

Type X in the snippet above is applied if the variable fulfills the condition whereas type Y is applied if the variable doesn’t. In the example above, type X is assigned if A extends B, while type Y is assigned if A doesn’t extend B.

4. What is the difference between union and intersection types?

Union and intersection types are considered advanced types in TypeScript. Understanding this is important when creating types in developer’s TypeScript projects to reduce duplication of types. The candidate should ideally be able to explain what union and intersection types are, their differences, and their use cases.

Unions and intersection types let you compose and combine existing types instead of creating them from scratch. Both union and intersection come with their unique characteristics which make them ideal for different use cases.

union type is described as a type that can be one of several types. Union type uses the | (vertical bar) symbol to separate the list of types that will be used in the new type. Let’s take a look at an example:

interface B {
  name: string,
  email: string
}

interface C {
  name: string,
  age: number
}

type A = B | C;

A in the snippet above can either be of type B or C. Based on B and Cs interfaces, A can either contain name and email (B) or name and age (C), but not email and age.

When accessing type A, TypeScript only lets you access properties that exist in both B and C (name) as those are the only ones that TypeScript can be certain of their existence.

Intersection on the other hand, is described as a type that combines multiple types into one – combining all the properties of each type to create a new type. Intersection uses the & symbol to separate the list of types that will be combined. Let’s look at an example:

interface B {
  name: string,
  email: string
}

interface C {
  name: string,
  age: number
}

type A = B & C;

A in the snippet above will contain all the properties from both B and C (nameemail, and age).

Read More: Interpersonal Skills: What Developers Need to Know (& How to Improve)

5. What is the difference between extends and implements in TypeScript?

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. This question is a chance to test your candidate’s knowledge of the concept of inheritance and how to do that in TypeScript.

Implements and extends have different uses. Let’s go through each individual and take a look at an example to compare the two.

When a class extends another class, the child class will inherit all the properties and methods of the class it extends. You can override any of the existing properties and methods of its parent and add new ones.

When a class implements another class or interface, the class has to implement all the methods and properties from the implemented class or interface. The implements keyword acts as a contract that the class has to follow, and TypeScript will make sure that the class is of the same shape as the class or interface it implements.

Let’s look at an example of a class and how another class can extend and implement it.

class User {
  name: string;
  age: number;
}

// John will contain name and age from the User class
class John extends User {}

// this will result in an error as Bob doesn't have all the properties that
// the User class has
class Bob implements User {}

// This is valid as Mike satisfies the "contract" specified by the 
// User class
class Mike implements User {
  name = 'Mike';
  age = 25
}

Advanced TypeScript Interview Questions

The following set of advanced questions for Typescript interviews should test your candidates’ deep knowledge of TS and some of its more expert-level concepts and features.

1. Explain how optional chaining works in TypeScript.

Optional chaining is a TypeScript feature that allows safer and easier accessing of values inside deep objects. Understanding how and when to use this feature will help developers write more concise code (where it makes sense). A candidate should be able to explain what optional chaining is and also elaborate on its use cases and pros and cons.

Optional chaining allows you to access properties and methods located deep within an object without having to check the validity of each reference in the chain.

Optional chaining uses the question mark followed by a period (?.) as its operator. TypeScript evaluates each reference in the chain and performs a null or undefined check before accessing its children. TypeScript immediately stops the execution when it fails the null or undefined check and returns undefined for the entire chain.

The code snippet below is an example of accessing a property with and without using optional chaining.

const user = {
  personalInfo: {
    name: 'John'
  }
}

// without optional chaining
const name = user && user.personalInfo && user.personalInfo.name || undefined;

// with optional chaining
const name = user?.personalInfo?.name;

2. What are abstract classes?

Abstract class is another feature of TypeScript that ties into the inheritance concept in object-oriented programming (OOP). You may ask this question to get an insight into the candidate’s understanding of OOP and its more advanced features in TypeScript.

Abstract classes specify a contract for the objects without the ability to instantiate them directly. However, an abstract class may also provide implementation details for its members.

An abstract class contains one or more abstract members. Any classes that extend the abstract class will then have to provide an implementation for the superclass’s abstract members.

Let’s look at an example of how an abstract class is written in TypeScript and how another class can extend it. In the example below, both Car and Bike extend the Vehicle class, however, they each have a different implementation of the drive() method.

abstract class Vehicle {

  abstract drive(): void;
  
  startEngine(): void {
    console.log('Engine starting...');
  }
}

class Car extends Vehicle {
  drive(): void {
    console.log('Driving in a car');
  }
}

class Bike extends Vehicle {
  drive(): void {
    console.log('Driving on a bike');
  }
}

Read More: 8 Great Benefits of Working From Home (A Guide for Remote Developers)

3. What are type assertions in TypeScript?

Type assertions are a way to tell TypeScript the type of a variable. As types are widely used in TypeScript applications, you often run into scenarios where you want to specify a more specific type to a variable. A candidate should be able to explain not only what type assertions are, but also their syntax and the reasons behind why you might use them.

Type assertion allows you to explicitly set the type of a value and tell the compiler not to infer it. This is useful when you know the type of an object more specifically than its current type or current inferred type. In such cases, you can use type assertions to tell TypeScript the current type of the variable.

TypeScript provides two syntaxes for type assertions – as and <>.

// using the `as` keyword
const name: string = person.name as string;

// using `<>`
const name: string = <string>person.name;

Since type assertions are only a way to tell the TypeScript compiler about the type of a variable, there is no runtime penalty of doing this in your TypeScript projects.

4. What is the difference between type inference and contextual typing?

Type inference is a key feature in TypeScript that helps write type-safe code that is both less error-prone and easier to read. You might ask this question to gauge your candidate’s understanding of both TypeScript’s type inference and their overall familiarity with TypeScript’s type system.

TypeScript can infer the type of a variable usually based on the variable’s initialization or declaration. This process is known as type inference.

The snippet below is an example of type inference where TypeScript infers the type of name as “string” based on the value assigned to it.

let name = 'john'

Contextual typing is a subset of type inference where TypeScript uses the location or context of a variable to infer its type. You can think about this as type inference in the opposite direction.

In the snippet below, TpyeScript uses information from the onmousedown function to infer the type of the mouseEvent parameter.

window.onmousedown = function(mouseEvent) {
   console.log(mouseEvent.button);   // OK
   console.log(mouseEvent.person);   // Error
};

Read More: How to Negotiate Your Salary as a Remote Developer

5. How does function overloads work in TypeScript?

Function overloading helps make functions more flexible and reusable by allowing the same function to behave a different way based on the set of input passed in. In addition to the how, you might be looking for answers on how function overloads tie into the code reusability aspect and any other benefits it provides.

Function overload is when the same function name is used multiple times with a different set of arguments – the number of arguments, types, or return types.

Let’s look at an example of how a print function can accept multiple types as its parameter by using function overloading.

print(message: string): void;
print(message: string[]): void;

print(message: unknown): void {
  if (typeof message === 'string') {
    console.log(message);
  } else if (Array.isArray(message)) {
    message.forEach((individualMessage) => {
      console.log(individualMessage);
    });
  } else {
    throw new Error('unable to print');
  }
}

Based on the code snippet above, we can now call print passing in either a single message string or an array of message strings.

print('Single message');
// Console Output:
// Single message

print(['First message', 'Second message']);
// Console Output
// First message
// Second message

Apart from the reusability of the function, function overloading also comes with autocomplete support. When calling a function (depending on your IDE), you will be provided with a list of all possible overloads that you can choose from for your specific use case, creating a better development experience.

Conclusion

Whether you’re a hiring manager looking for the ideal candidate or a developer preparing for an interview, we hope these TypeScript interview questions help you through the process.

Keep in mind that technical skills and knowledge are only one part of the hiring process. Past experiences and soft skills are equally important to make sure you hire the best candidate (or land the right job).

Good luck on your upcoming TypeScript interview! Here are a few other helpful guides to read:

You can also explore HireAI to skip the line and:

⚡️ Get instant candidate matches without searching
⚡️ Identify top applicants from our network of 250,000+ devs with no manual screening
⚡️ Hire 4x faster with vetted candidates (qualified and interview-ready)

Try HireAI and hire top developers now →

Written by
William Juan