Top 40 TypeScript Interview Questions and Answers
Dec 07, 2024 11 Min Read 331 Views
(Last Updated)
In the fast-evolving world of web development, TypeScript has become the go-to language for building scalable and robust applications. Preparing for interviews with the immense amount of competitiveness for these jobs is not easy, you need a resource that takes you through all the questions and covers the most important concepts simply.
Hence, I have drafted this comprehensive guide of must-know TypeScript Interview Questions and Answers that covers everything from the basics to advanced concepts, ensuring you’re well-equipped to impress your interviewer and ace your next opportunity. Let’s get started!
Table of contents
- Top TypeScript Interview Questions and Answers (Section-Wise)
- Beginner Questions
- Intermediate Questions
- Advanced Questions
- Expert-Level Questions
- Takeaways…
Top TypeScript Interview Questions and Answers (Section-Wise)
I have divided all these important TypeScript interview questions and answers into various sections for your ease of learning, I recommend covering the beginner level questions as a must and then going through all the sections one by one so that you can gain a well-rounded knowledge of how these interviews are undertaken and how much and what you should prepare.
Beginner Questions
1) What is TypeScript?
Answer:
TypeScript is a programming language developed by Microsoft as a superset of JavaScript. It introduces static typing to JavaScript, allowing developers to define types for variables, functions, and objects. This helps catch errors at compile time rather than runtime, improving code quality and maintainability.
Key features include:
- Static Type Checking: Detect type-related bugs during development.
- ES6+ Support: Write modern JavaScript features, even in environments that don’t support them.
- Advanced Features: Interfaces, generics, enums, decorators, and more.
TypeScript compiles to JavaScript, ensuring it works seamlessly in any JavaScript runtime, including browsers and Node.js.
2) How do you install TypeScript?
Answer:
TypeScript can be installed globally or locally using NPM (Node Package Manager).
Global Installation:
npm install -g typescript
This makes the tsc (TypeScript compiler) command available system-wide.
Local Installation:
npm install --save-dev typescript
This installs TypeScript within a project.
Verify Installation:
tsc --version
Once installed, .ts files can be compiled using:
tsc file.ts
3) What is tsconfig.json?
Answer:
tsconfig.json is a configuration file for a TypeScript project. It specifies compiler options, source files, and behaviors for the TypeScript compiler (tsc).
Example:
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"strict": true,
"outDir": "./dist"
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
Explanation of important fields:
- compilerOptions: Defines compiler behaviors (e.g., JavaScript target version, module system).
- include: Specifies files or folders to include in compilation.
- exclude: Excludes files or folders like node_modules.
Using tsconfig.json ensures consistent compilation settings across the project.
4) What are type annotations?
Answer:
Type annotations are a way to explicitly specify types for variables, function parameters, and return values in TypeScript. They make code more predictable and prevent type-related errors.
Example:
let username: string = "Alice";
let age: number = 25;
function greet(name: string): string {
return `Hello, ${name}`;
}
Benefits:
- Compile-Time Checking: Type errors are caught during development.
- Self-Documenting Code: Code becomes easier to understand for other developers.
- Enhanced Tooling: IDEs provide better autocompletion and error detection.
5) What are interfaces?
Answer:
Interfaces define the shape of an object, ensuring that an object adheres to a specific structure. They can also describe function types or classes.
Example:
interface User {
id: number;
name: string;
email?: string; // Optional property
}
const user: User = { id: 1, name: "John" };
Features of interfaces:
Optional Properties: Mark properties with ? if they are not mandatory.
Readonly Properties: Prevent modification of certain fields.
interface Point {
readonly x: number;
readonly y: number;
}
Extending Interfaces: Combine multiple interfaces.
interface Admin extends User {
role: string;
}
Interfaces enforce consistent data structures, which is crucial for maintaining scalable and reliable codebases.
6) What are enums in TypeScript?
Answer:
Enums in TypeScript are used to define a collection of named constants. They make code more readable and reduce the risk of invalid values by providing a predefined set of possible values. TypeScript supports numeric and string enums:
Numeric enums: Assign sequential numbers starting from 0 (or a custom starting value).
enum Status {
Active, // 0
Inactive, // 1
Pending // 2
}
let currentStatus: Status = Status.Active; // Resolves to 0
console.log(Status[0]); // "Active" (Reverse mapping)
String enums: Assign custom string constants without reverse mapping.
enum Directions {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT"
}
console.log(Directions.Up); // "UP"
Use Case: Enums are great for modeling state, options, or categories (e.g., HTTP status codes, user roles).
7) What is the difference between let, const, and var?
Answer:
let:
- Block-scoped, meaning it is accessible only within the {} block where it is defined.
- Prevents issues like re-declaration.
- Use for variables that are expected to change.
let count = 10;
if (true) {
let count = 20; // Block-specific variable
console.log(count); // 20
}
console.log(count); // 10
const:
- Block-scoped, but variables are immutable.
- The value cannot be reassigned after initialization, though object properties can still change.
const pi = 3.14;
// pi = 3.15; // Error: Assignment to constant variable
const user = { name: "Alice" };
user.name = "Bob"; // Allowed
var:
- Function-scoped, meaning it’s available across the entire function, regardless of block scope.
- Hoisted to the top of its scope, often leading to bugs.
if (true) {
var message = "Hello";
}
console.log(message); // Accessible outside block
8) What is the purpose of union types?
Answer:
Union types allow variables to hold multiple types. They improve flexibility while maintaining type safety, especially for scenarios like user input, APIs, or third-party libraries.
Example:
let id: number | string; // Accepts both numbers and strings
id = 101; // Valid
id = "abc"; // Also valid
Type narrowing allows you to implement logic based on the current type:
function display(value: string | number) {
if (typeof value === "string") {
console.log(`String: ${value.toUpperCase()}`);
} else {
console.log(`Number: ${value.toFixed(2)}`);
}
}
Use Case: Union types are common in APIs that return different result types or functions with variable input (e.g., handling both null and valid data).
9) Explain optional parameters in functions.
Answer:
Optional parameters allow you to make certain parameters optional in a function. They are defined using the ? syntax, enabling flexibility when calling the function without passing every argument.
Example:
function greet(name: string, greeting?: string): string {
return `${greeting || "Hello"}, ${name}`;
}
console.log(greet("Alice")); // Hello, Alice
console.log(greet("Alice", "Hi")); // Hi, Alice
Rules:
- Optional parameters must come after required parameters.
- If not provided, their value is undefined by default.
Use Case: Optional parameters are helpful in scenarios where a function may have defaults or variable arguments (e.g., logging levels or optional settings).
10) What are type assertions?
Answer:
Type assertions are a way to override TypeScript’s inferred type system. They allow developers to manually specify a type when the compiler cannot determine it accurately.
Syntax:
Using as:
let value: unknown = "TypeScript";
let length: number = (value as string).length; // Treats value as a string
Using angle brackets (not in React):
let length: number = (<string>value).length;
Key Points:
- Not runtime casting: Type assertions do not change the actual runtime type; they only affect compile-time type checking.
- Use cautiously when you’re certain of the type to avoid runtime errors.
Use Case: Commonly used with any or unknown types, such as when working with untyped libraries, dynamic data, or DOM manipulation:
const input = document.getElementById("username") as HTMLInputElement;
console.log(input.value); // Assumes 'input' is of type HTMLInputElement
Intermediate Questions
11) How does TypeScript support inheritance?
Answer:
TypeScript supports inheritance through the extends keyword, which allows one class (subclass) to inherit properties and methods from another class (superclass). This enables code reuse, polymorphism, and extending base functionality.
TypeScript also supports class-based inheritance, where methods and properties are shared across instances of the subclass, reducing redundancy.
Example:
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
move() {
console.log(`${this.name} is moving`);
}
}
class Dog extends Animal {
constructor(name: string) {
super(name); // Calls the parent class constructor
}
bark() {
console.log(`${this.name} barks!`);
}
}
const myDog = new Dog("Rex");
myDog.move(); // Rex is moving
myDog.bark(); // Rex barks!
Key Concepts:
- Inheritance allows subclass instances to access methods and properties of the superclass.
- Polymorphism: Subclasses can override methods from parent classes.
12) What are generics?
Answer:
Generics are a powerful feature in TypeScript that allows developers to write flexible, reusable code that works with any data type. Rather than defining a function or class for specific data types, generics allow you to write components that work with any type while maintaining type safety.
Generics are defined using angle brackets (<T>) and provide type parameters that are replaced with actual types during function calls or class instantiations.
Example:
function identity<T>(arg: T): T {
return arg; // T will be replaced by the actual type at call time
}
console.log(identity<number>(42)); // Returns 42
console.log(identity<string>("Hello")); // Returns "Hello"
Use Case: Useful for collections, APIs, or utilities that need to support multiple data types while ensuring type consistency.
13) What is the difference between type aliases and interfaces?
Answer:
Type Aliases: Type aliases define custom types for primitives, objects, and unions. They cannot be merged or extended once defined. They’re ideal for union types, intersections, or complex object types.
Example:
type Point = { x: number; y: number };
type ID = number | string;
Interfaces: Interfaces define the structure of an object and can be extended or merged with other interfaces. They are more flexible and better suited for defining contracts for objects, classes, and functions.
Example:
interface Point { x: number; y: number; }
Key Difference:
- Extensibility: Interfaces can be extended or merged; type aliases cannot.
- Use Case: Use interfaces when defining object structures and type aliases for unions or type combinations.
14) What are namespaces?
Answer:
Namespaces provide a way to organize code by grouping related functionalities under a single name. They are primarily used to avoid global namespace pollution and can help manage large applications by logically grouping types, functions, and variables.
TypeScript namespaces use the namespace keyword, and internal members are encapsulated, reducing the risk of naming conflicts.
Example:
namespace Utilities {
export function log(message: string): void {
console.log(message);
}
}
Utilities.log("Hello, World!");
Key Concepts:
- Encapsulation: Namespaces prevent code from polluting the global scope by keeping related code together.
- Exporting Members: Members within a namespace need to be marked as export to be accessible outside the namespace.
15) What is the role of decorators?
Answer:
- Decorators are special annotations that can modify the behavior of classes, methods, or properties at runtime. They provide a meta-programming feature to add functionality without modifying the original code structure.
- Use Cases:
- Class decorators: Used to modify or extend the functionality of a class.
- Method decorators: Used to modify or extend method behavior.
Example:
function log(target: any, key: string) {
console.log(`${key} method was called`);
}
class User {
@log
greet() {
console.log("Hello!");
}
}
const user = new User();
user.greet(); // Outputs: greet method was called
Decorators are commonly used in frameworks like Angular to enhance class behavior (e.g., @Component, @Injectable).
16) How does TypeScript handle null and undefined?
Answer:
TypeScript includes null and undefined as subtypes of all other types when –strictNullChecks is disabled. This means you can assign null or undefined to variables of any type. However, when –strictNullChecks is enabled (a best practice in modern projects), null and undefined are treated as their own types and cannot be assigned to other types unless explicitly included in a union.
For example:
let value: string | null = "Hello"; // Can be string or null
value = null; // Allowed
value = undefined; // Error if strictNullChecks is enabled
You must explicitly check for null or undefined before accessing a variable’s properties, ensuring safer code:
if (value !== null) {
console.log(value.length); // Safe to access 'length'
}
17) What is a module in TypeScript?
Answer:
A module in TypeScript is a way to organize and encapsulate code into reusable blocks. TypeScript modules use import and export syntax to share code across files and projects.
Example:
// math.ts (exporting a module)
export const add = (a: number, b: number): number => a + b;
// app.ts (importing a module)
import { add } from "./math";
console.log(add(2, 3)); // Outputs 5
Modules support both ES6 (import/export) and CommonJS (require/module.exports). They help prevent global namespace pollution, improve code maintainability, and facilitate tree-shaking in modern build systems like Webpack.
18) What are mixins in TypeScript?
Answer:
Mixins are a way to combine behaviors or properties from multiple classes without using traditional inheritance. They are especially useful when you need to reuse functionalities across multiple unrelated classes.
Implementation:
- Create individual classes containing desired behaviors.
- Use Object.assign to copy methods into a target class.
Example:
class CanEat {
eat() {
console.log("Eating...");
}
}
class CanWalk {
walk() {
console.log("Walking...");
}
}
class Person {}
Object.assign(Person.prototype, CanEat.prototype, CanWalk.prototype);
const p = new Person();
p.eat(); // Eating...
p.walk(); // Walking...
Mixins avoid the complexities of multiple inheritance while allowing code reuse. They are commonly used in UI frameworks like React.
19) Explain mapped types.
Answer:
Mapped types allow you to transform an existing type into a new type by iterating over each property using keyof and applying rules. This is especially useful for creating utility types.
Example:
type Readonly<T> = { readonly [K in keyof T]: T[K] };
type User = { name: string; age: number };
type ReadonlyUser = Readonly<User>; // { readonly name: string; readonly age: number }
Built-in mapped types include:
Partial<T>: Makes all properties optional.
type PartialUser = Partial<User>; // { name?: string; age?: number }
Pick<T, K>: Selects specific properties.
type UserName = Pick<User, "name">; // { name: string }
Mapped types enhance flexibility when working with dynamic object structures.
20) What are conditional types?
Answer:
Conditional types apply logic based on a condition using extends. They allow for type transformations, filtering, or narrowing based on specific rules.
Example:
type IsString<T> = T extends string ? true : false;
type Test1 = IsString<string>; // true
type Test2 = IsString<number>; // false
Conditional types are distributed over unions, processing each member individually:
type ExtractString<T> = T extends string ? T : never;
type Result = ExtractString<string | number>; // string
These types are powerful for advanced use cases like utility types and type inference in complex generics.
Master TypeScript and ace your interviews with GUVI’s TypeScript course. Learn in-demand skills like type safety, interfaces, and advanced features while working on real-world projects. Gain hands-on experience with tools like VS Code, ESLint, and TypeScript Compiler. Stand out with expert guidance, interview prep, and a job-ready portfolio!
Advanced Questions
21) Explain advanced type inference.
Answer:
TypeScript’s advanced type inference allows it to deduce complex types from context without explicit declarations. It works with:
Variable declarations: Types are inferred based on assigned values.
let num = 42; // Inferred as number
let strArray = ["a", "b", "c"]; // Inferred as string[]
Function return types: TypeScript infers the return type based on the function logic.
function multiply(a: number, b: number) {
return a * b; // Return type inferred as number
}
Generics: Inference is based on how the generic type is used.
function wrap<T>(value: T): T {
return value;
}
wrap(10); // T inferred as number
wrap("TypeScript"); // T inferred as string
TypeScript can also infer types in more complex contexts, such as destructuring or conditional types, enabling concise and safer code.
22) What are tuple types?
Answer:
Tuple types in TypeScript represent arrays with fixed lengths and types for each position. They provide a way to enforce a specific structure for data that combines multiple types.
Example:
let tuple: [number, string, boolean] = [42, "Hello", true];
tuple[0] = 100; // Allowed (must be a number)
tuple[1] = false; // Error (must be a string)
Tuples are often used in scenarios like function returns where you want to return multiple values with specific types:
function getUser(): [string, number] {
return ["Alice", 25]; // Tuple with string and number
}
const [name, age] = getUser(); // Destructured tuple
TypeScript 4.0 introduced variadic tuples, allowing flexible lengths with patterns:
type StringAndNumbers = [string, ...number[]];
let data: StringAndNumbers = ["age", 20, 30, 40];
23) What are discriminated unions?
Answer:
Discriminated unions combine union types with a shared literal property (discriminator) to enable type narrowing. The shared property helps TypeScript determine which type is being used in a union.
Example:
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; side: number };
function getArea(shape: Shape): number {
if (shape.kind === "circle") {
return Math.PI * shape.radius ** 2; // TypeScript knows it's a circle
}
return shape.side ** 2; // TypeScript knows it's a square
}
This approach is widely used for handling complex data structures and ensures that type checking is enforced based on specific conditions, reducing runtime errors.
24) How does TypeScript handle overloading?
Answer:
TypeScript allows multiple function signatures (overloads) for the same function, enabling it to accept different parameter types or combinations while maintaining strong type checks.
Example:
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any): any {
return a + b;
}
add(2, 3); // Inferred as number, returns 5
add("Hello, ", "World!"); // Inferred as string, returns "Hello, World!"
The function implementation (add(a: any, b: any)) must handle all overload cases explicitly. This ensures flexibility while maintaining strict type safety for the caller.
25) What is the readonly modifier?
Answer:
The readonly modifier makes properties immutable after their initial assignment. It enforces immutability at compile-time, ensuring that properties cannot be reassigned, providing better predictability and preventing accidental mutations.
Example:
interface Point {
readonly x: number;
readonly y: number;
}
const point: Point = { x: 10, y: 20 };
point.x = 15; // Error: Cannot assign to 'x' because it is a read-only property
readonly can also be applied to arrays and tuples:
const arr: readonly number[] = [1, 2, 3];
arr.push(4); // Error: Push is not allowed on a readonly array
This is particularly useful for defining constants or protecting objects from unintended changes in larger applications.
26) Explain utility types like Partial and Pick.
Answer:
Utility types in TypeScript allow you to create new types by transforming existing ones. They make handling complex types simpler and more flexible.
Partial<T>:
- Converts all properties of a type to optional.
- Use case: When you need to modify only some properties of an object while leaving others unchanged. Example:
interface User {
name: string;
age: number;
email: string;
}
type PartialUser = Partial<User>; // { name?: string; age?: number; email?: string }
const userUpdate: PartialUser = { age: 30 }; // Valid
Pick<T, K>:
- Extracts specific properties from a type.
- Use case: When you want a reduced type with only the fields relevant to a particular context. Example:
type UserName = Pick<User, "name" | "email">; // { name: string; email: string }
const user: UserName = { name: "Alice", email: "[email protected]" };
These utility types are essential for working with dynamic and reusable type definitions in TypeScript.
27) What are intersection types?
Answer:
Intersection types combine multiple types into one. The resulting type must satisfy all combined types.
Example:
type Admin = { isAdmin: boolean };
type User = { name: string };
type AdminUser = Admin & User; // Combines both types
const admin: AdminUser = { isAdmin: true, name: "John" };
Use Cases:
- Combining multiple interfaces or types.
- Extending functionality without modifying the original types.
For instance, in React, intersection types are often used for component props that must merge default props with additional ones.
28) What are recursive types?
Answer:
Recursive types are self-referential types, allowing you to define structures like nested arrays, trees, or JSON objects.
Example:
type NestedArray<T> = T | NestedArray<T[]>; // Recursive definition
let numbers: NestedArray<number> = [1, [2, [3, 4]]];
Use Cases:
- Tree structures (e.g., file directories).
- Parsing and working with deeply nested data structures like JSON.
Example with a tree:
interface TreeNode {
value: string;
children?: TreeNode[]; // Recursive reference
}
const tree: TreeNode = {
value: "root",
children: [
{ value: "child1" },
{ value: "child2", children: [{ value: "grandchild" }] },
],
};
Recursive types are crucial when defining types for hierarchically structured data.
29) What is the Awaited type?
Answer:
The Awaited type, introduced in TypeScript 4.5, resolves the value of a Promise. It is particularly useful for handling asynchronous operations generically in type definitions.
Example:
type Result = Awaited<Promise<string>>; // Resolves to 'string'
async function fetchData(): Promise<number> {
return 42;
}
type DataType = Awaited<ReturnType<typeof fetchData>>; // Resolves to 'number'
Use Case:
When working with functions returning promises, Awaited helps infer the type of the resolved value, especially in utility types like Promise.all or custom asynchronous logic.
30) Explain discriminated conditional types.
Answer:
Discriminated conditional types evaluate each member of a union type separately. They are useful for filtering or transforming union types based on a condition.
Example:
type ExtractString<T> = T extends string ? T : never; // Filters strings
type Test = ExtractString<string | number | boolean>; // Resolves to 'string'
Distributed Over Unions:
When applied to unions, TypeScript evaluates each union member independently:
type Union = string | number;
type Result = Union extends string ? "yes" : "no"; // Evaluated as "yes" | "no"
Use Case:
Discriminated types are especially valuable for transforming complex union types, such as filtering out certain properties or narrowing types dynamically based on conditions.
Discriminated conditional types are a key feature for creating powerful, reusable generic types.
Expert-Level Questions
31) What are template literal types?
Answer:
Template literal types enable creating string-based types by combining literal values with placeholders, similar to JavaScript template literals.
Example:
type Role = "admin" | "user";
type Permission = `can-${Role}`; // Produces: "can-admin" | "can-user"
They are powerful for building specific string patterns, such as routes, CSS class names, or event handlers, while maintaining type safety.
32) What are branded types?
Answer:
Branded types use type intersections to create distinct, opaque types even when their underlying structure is identical.
Example:
type Brand<K, T> = T & { __brand: K };
type UserID = Brand<"UserID", string>;
const userId: UserID = "1234" as UserID; // Valid
const rawId: string = userId; // Error without explicit cast
This ensures type distinction (e.g., between UserID and ProductID), preventing accidental misuse.
33) How are declaration files (.d.ts) used?
Answer:
Declaration files (.d.ts) define TypeScript types for JavaScript libraries or modules without modifying their code. They provide type information for better IDE support, error checking, and autocomplete.
Example:
// lodash.d.ts
declare module "lodash" {
export function chunk<T>(array: T[], size: number): T[][];
}
// Usage
import { chunk } from "lodash";
const result = chunk([1, 2, 3], 2); // Typed correctly
These files are critical for integrating JavaScript libraries into TypeScript projects.
34) What are module augmentation and declaration merging?
Answer:
Module Augmentation: Extends existing module definitions by adding new types or members.
// Extending a module
declare module "lodash" {
export function customFn(value: string): string;
}
import { customFn } from "lodash";
customFn("test"); // New function
Declaration Merging: Combines multiple declarations of the same name (interfaces, namespaces) into a single definition.
interface User {
name: string;
}
interface User {
age: number;
}
const user: User = { name: "John", age: 30 }; // Merged definition
Both features enable modular and extensible type definitions.
35) Explain keyof and indexed access types.
Answer:
keyof Operator: Retrieves keys of a type as a union.
type User = { id: number; name: string };
type UserKeys = keyof User; // "id" | "name"
Indexed Access Types: Access specific property types using square brackets.
type User = { id: number; name: string };
type NameType = User["name"]; // string
These are used together to create dynamic, type-safe code, such as extracting or transforming types based on their structure.
36) What are utility functions like Exclude and Omit?
Answer:
Utility types in TypeScript are predefined types that help manipulate and transform existing types into new ones. Two commonly used utilities are:
Exclude<T, U>: Removes types in U from T. It works well for narrowing down a union type.
Example:
type A = "a" | "b" | "c";
type B = Exclude<A, "a" | "b">; // Result: "c"
Here, the type B contains only “c” because “a” and “b” are excluded from type A.
Omit<T, K>: Removes specific keys K from an object type T. This is useful when you want an object type with some fields removed.
Example:
type User = { id: number; name: string; age: number };
type UserWithoutAge = Omit<User, "age">;
// Result: { id: number; name: string }
Omit works by picking all keys except those specified in K.
Utility types like these simplify type manipulation, reducing boilerplate and improving code readability.
37) How do you implement conditional narrowing in TypeScript?
Answer:
Conditional narrowing refers to runtime checks that allow TypeScript to refine the type of a variable based on the conditions. TypeScript uses type guards and control flow analysis to infer the specific type.
Type Guards:
Using typeof:
function printLength(value: string | number): void {
if (typeof value === "string") {
console.log(value.length); // TypeScript narrows value to string
} else {
console.log(value); // TypeScript narrows value to number
}
}
Using instanceof:
function processDate(value: Date | string): void {
if (value instanceof Date) {
console.log(value.toISOString()); // Narrowed to Date
} else {
console.log(value.toUpperCase()); // Narrowed to string
}
}
Using in Operator:
This is helpful for narrowing custom object types:
type Cat = { meow: () => void };
type Dog = { bark: () => void };
function handleAnimal(animal: Cat | Dog) {
if ("meow" in animal) {
animal.meow(); // Narrowed to Cat
} else {
animal.bark(); // Narrowed to Dog
}
}
These techniques ensure proper handling of different types in union scenarios.
38) What is never in TypeScript?
Answer:
The never type represents a value that never occurs. It is used in cases where:
A function never returns (e.g., it throws an error or has an infinite loop).
Example:
function throwError(message: string): never {
throw new Error(message);
}
Exhaustiveness checks in discriminated unions:
TypeScript uses never to ensure all possible cases are handled:
type Shape = { kind: "circle" } | { kind: "square" };
function processShape(shape: Shape): void {
switch (shape.kind) {
case "circle":
console.log("Processing a circle");
break;
case "square":
console.log("Processing a square");
break;
default:
const exhaustiveCheck: never = shape; // Ensures all cases are handled
break;
}
}
By enforcing the use of never, TypeScript helps catch logical errors at compile time.
39) Explain variance in generics.
Answer:
Variance determines how subtyping relationships between types are preserved when they are used with generics. TypeScript applies variance primarily in the context of function parameters and return types:
Covariance: Subtyping is preserved. A type Child can be assigned to Parent.
Example:
type ReadonlyArray<T> = { readonly [index: number]: T };
const animals: ReadonlyArray<Animal> = [{ name: "Dog" }];
const mammals: ReadonlyArray<Mammal> = animals; // Valid due to covariance
Contravariance: Subtyping is reversed. A type Parent can be assigned to Child when passed as a function parameter.
Example:
type Printer<T> = (value: T) => void;
let animalPrinter: Printer<Animal> = (animal) => console.log(animal);
let mammalPrinter: Printer<Mammal> = animalPrinter; // Valid due to contravariance
Invariance: No subtyping relationship is allowed. For instance, Array<T> is invariant in TypeScript:
const mammals: Array<Mammal> = [];
const animals: Array<Animal> = mammals; // Error due to invariance
Understanding variance helps avoid type mismatches when working with generics in complex systems.
40) What are advanced configuration options in tsconfig.json?
Answer:
The tsconfig.json file contains compiler options to control how TypeScript transpiles and checks code. Advanced options include:
strict: Enables all strict type-checking features, like strictNullChecks, noImplicitAny, and strictBindCallApply.
Example:
{
"compilerOptions": {
"strict": true
}
}
paths and baseUrl: Enable module aliasing for cleaner imports.
Example:
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"@components/*": ["components/*"]
}
}
}
This allows import { Header } from “@components/Header” instead of long relative paths.
target and module: Control the output JavaScript version and module format.
Example:
{
"compilerOptions": {
"target": "ES6",
"module": "CommonJS"
}
}
experimentalDecorators: Enables decorator support for meta-programming.
Example:
{
"compilerOptions": {
"experimentalDecorators": true
}
}
resolveJsonModule: Allows importing JSON files directly.
import config from "./config.json";
These configurations enhance developer productivity, type safety, and project maintainability.
Takeaways…
Mastering TypeScript is more than just understanding types; it’s about leveraging its features to build reliable and maintainable code. These 40 interview questions not only test your technical expertise but also challenge your problem-solving abilities in real-world scenarios.
With this guide, you’re not just preparing for an interview; you’re equipping yourself with skills to stand out in the competitive world of web development. Hence, I hope this guide becomes a great aid in your TypeScript learning journey.
If you have doubts about any of the questions or the article itself, reach out to me in the comments section below.
Did you enjoy this article?