Skip to content
Hoodie for your thoughts: take the Developer Skills Survey for a chance to win free merch Start survey
69% of tech leaders are preparing their teams for GenAI. Uncover more insights in the AI Skills Report. Read now
Adapt your hiring strategy for an AI-powered future. Uncover more insights in our latest whitepaper. Read now
Programming Languages

7 TypeScript Interview Questions Every Developer Should Know

Written By April Bohnert | June 21, 2023

Abstract, futuristic image generated by AI

In the wide world of JavaScript development, TypeScript has made a name for itself by improving upon the scalability, maintainability, and reliability of JavaScript. Now, more than 10 years after its release, it’s everywhere — from top-notch tech companies to the tiniest startups. TypeScript has been gaining in popularity over the last few years, and it seems like the upward trajectory is only going to continue. In our 2023 Developer Skills Report, we saw demand for TypeScript assessments explode by 2,788 percent in 2022, growing faster both in terms of popularity and demand than any other programming language.

So, what does this mean for developers? Opportunity. There’s a surge of demand for TypeScript developers, and if you can show that you have a solid understanding of this versatile superset of JavaScript, it can open many doors. But, as with all things, to seize these opportunities, you need to be prepared. This blog post will help you understand the kind of TypeScript interview questions that might come your way and help you prepare for and answer them with confidence. 

What is TypeScript?

TypeScript, as its name suggests, is all about types. At its core, TypeScript is a statically typed superset of JavaScript that compiles to plain JavaScript. It was developed by Microsoft in 2012 with the aim to make JavaScript development more efficient and less error-prone.

Being a superset means that any valid JavaScript code is also valid TypeScript code. However, TypeScript adds a powerful feature not present in standard JavaScript: static types. With TypeScript, you can declare types for your variables, function parameters, and object properties. This provides a level of certainty about the kind of data your code will be dealing with, which can be a huge boon for debugging and catching errors at compile-time, rather than at run-time.

What a TypeScript Interview Looks Like

Understanding what a TypeScript coding interview may entail is the first step toward acing it. Just like any other technical interview, a TypeScript interview will test your understanding of concepts, your problem-solving skills, and your ability to apply those skills to real-world scenarios.

Typically, TypeScript coding interviews will focus on the following areas:

  • Understanding of TypeScript Basics: You will be expected to know the basics, such as how to define and use types, interfaces, enums, and generics.
  • TypeScript Features: Questions may probe your knowledge of TypeScript’s unique features like static types, type inference, and type guards.
  • Integration with JavaScript Ecosystem: TypeScript plays well with the JavaScript ecosystem, so you may be asked about integrating TypeScript with popular JavaScript frameworks like Angular, React, or Vue.js.
  • Real-World Problem Solving: Practical coding exercises where you’ll need to solve a problem or build a small feature using TypeScript.
  • Best Practices: You should also be aware of best practices when it comes to using TypeScript — things like when and how to use “any” or “unknown” types, how to avoid null and undefined errors, and how to use optional chaining and nullish coalescing.

But who might be interested in your TypeScript skills? Many technical roles require TypeScript expertise, including front-end developers, full-stack developers, back-end developers, and web application developers. These roles span companies of all sizes and industries of all types, providing experienced TypeScript developers with a number of opportunities to flex their skills.

1. TypeScript Function Overloading

Function overloading is an important aspect of TypeScript that offers the flexibility to create functions with the same name but different arguments or return types. This is a common practice in many strongly typed languages.

Task: Write a TypeScript function named process that accepts either a string or an array of strings. If the argument is a string, return it in uppercase; if it’s an array, return the array length.

Sample Code:

function process(input: string): string;

function process(input: string[]): number;

function process(input: any): any {

    if (typeof input === "string") {

        return input.toUpperCase();

    } else {

        return input.length;

    }

}

console.log(process("TypeScript")); // returns "TYPESCRIPT"

console.log(process(["JavaScript", "TypeScript"])); // returns 2

Explanation: The process function uses TypeScript’s type checking (typeof input === “string”) to determine the type of the input. If it’s a string, it uses the toUpperCase() method to return the uppercase version of the string. If the input is an array (meaning it’s not a string), it simply returns the length of the array.

2. Implementing an Interface

In TypeScript, interfaces define a contract for classes and help in achieving strong typing. The class that implements an interface must provide an implementation for all its members.

Task: Create an interface IVehicle with properties make, model, and year. Then, create a class Car that implements IVehicle. The Car class should have a constructor to initialize the properties and a method getDetails that returns a string containing all the details of the car.

Sample Code:

interface IVehicle {

    make: string;

    model: string;

    year: number;

}

class Car implements IVehicle {

    constructor(public make: string, public model: string, public year: number) {}

    getDetails(): string {

        return `${this.make} ${this.model} ${this.year}`;

    }

}

const myCar = new Car("Toyota", "Corolla", 2018);

console.log(myCar.getDetails()); // returns "Toyota Corolla 2018"

Explanation: The Car class constructor uses TypeScript’s shorthand for assigning parameters to class properties (public make: string). The getDetails method uses template literals (${this.make} ${this.model} ${this.year}) to format a string with the car details.

Explore verified tech roles & skills

Backed by machine learning and skills intelligence.

Explore all roles

3. Deep Equality Check

TypeScript, like JavaScript, treats objects as reference types, which means that two objects are considered equal only if they reference the exact same memory location. However, in many practical scenarios, we need to check if two objects have the same properties and values, regardless of whether they reference the same memory location or not. This is known as a deep equality check.

Task: Write a TypeScript function deepEqual that compares two objects for deep equality. The function should return true if the objects are deeply equal and false otherwise.

Sample Code:

type Value = number | string | boolean | null | Value[] | { [key: string]: Value };

function deepEqual(a: Value, b: Value): boolean {

    if (a === b) {

        return true;

    }

    if (typeof a === "object" && a !== null && typeof b === "object" && b !== null) {

        if (Object.keys(a).length !== Object.keys(b).length) {

            return false;

        }

        for (const key in a) {

            if (!b.hasOwnProperty(key) || !deepEqual(a[key], b[key])) {

                return false;
            }
        }

        return true;
    }

    return false;

}


console.log(deepEqual({ name: "John", age: 30 }, { name: "John", age: 30 })); // returns true

console.log(deepEqual({ name: "John", age: 30 }, { name: "John", age: 31 })); // returns false

Explanation: The deepEqual function uses recursion to perform a deep comparison of two objects. It first checks if the objects are the same using the strict equality operator (===). If not, it checks if both are objects (excluding null), then it compares the number of their properties (Object.keys(a).length !== Object.keys(b).length). If they have the same number of properties, it iteratively checks each property using the hasOwnProperty method and the deepEqual function itself.

4. Implementing a Decorator

Decorators provide a way to add both annotations and a meta-programming syntax for class declarations and members. They’re a stage 2 proposal for JavaScript and are widely used in TypeScript.

Task: Implement a class decorator @sealed that seals both the constructor and its prototype.

Sample Code:

function sealed(constructor: Function) {

    Object.seal(constructor);

    Object

.seal(constructor.prototype);

}

@sealed

class Greeter {

    greeting: string;

    constructor(message: string) {

        this.greeting = message;

    }

    greet() {

        return "Hello, " + this.greeting;

    }

}

Explanation: The @sealed decorator uses Object.seal, a JavaScript method that prevents new properties from being added to an object and marks all existing properties as non-configurable. This decorator can be used to ensure that a class and its prototype cannot be tampered with, which can be particularly useful when you need to guarantee that a class maintains its original behavior and structure.

5. Implement a Generic Class

In TypeScript, working with classes often involves working with a variety of types. Generics offer a way to create classes that can work with a variety of types while still retaining type safety.

Task: Implement a generic Queue class. The class should have methods: enqueue which adds an element to the end, and dequeue which removes an element from the front. The class should also have a size property.

Sample Code:

class Queue<T> {

    private storage: T[] = [];

    enqueue(item: T): void {

        this.storage.push(item);

    }


    dequeue(): T | undefined {

        return this.storage.shift();

    }

    get size(): number {

        return this.storage.length;

    }

}

let queue = new Queue<number>();

queue.enqueue(1);

queue.enqueue(2);

console.log(queue.size); // outputs 2

console.log(queue.dequeue()); // outputs 1

Explanation: This generic Queue class is built to handle elements of any type T. The enqueue method uses Array.prototype.push to add items at the end, while dequeue uses Array.prototype.shift to remove items from the front. The size getter property provides the current size of the queue.

6. Type Guards

As TypeScript is a statically typed superset of JavaScript, it allows for type checking at compile time. But, in many scenarios, we need to check types during runtime. This is where TypeScript’s type guards come in handy. They are a way to provide extra type information based on runtime values.

Task: Write a function isNumber that acts as a type guard for number.

Sample Code:

function isNumber(value: any): value is number {

    return typeof value === "number";

}

function process(value: number | string) {

    if (isNumber(value)) {

        return value.toFixed(2); // available because 'value' is guaranteed to be a number here

    } else {

        return value.trim();

    }

}

Explanation: The isNumber function is a user-defined type guard that checks if a value is a number. Inside process, we use this type guard to narrow the type of value from number | string to number within the if block. This allows us to use the toFixed method, which is only available on numbers.

7. Implement a Mixin

Mixins are a design concept in TypeScript that allows a developer to create reusable class factories. With mixins, we can create complex classes by combining simpler base classes. This question explores your knowledge of this advanced TypeScript concept.

Task: Implement a Disposable mixin. This should include a dispose method and an isDisposed property.

Sample Code:

class Disposable {

    isDisposed: boolean;

    dispose() {

        this.isDisposed = true;

    }

}


class SmartObject implements Disposable {

    constructor() {

        setInterval(() => console.log(this.isDisposed), 500);

    }


    interact() {

        this.dispose();

    }


    isDisposed: boolean = false;

    dispose: () => void;

}


applyMixins(SmartObject, [Disposable]);

let smartObj = new SmartObject();

setTimeout(() => smartObj.interact(), 1000);

function applyMixins(derivedCtor: any, baseCtors: any[]) {

    baseCtors.forEach(baseCtor => {

        Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {

            derivedCtor.prototype[name] = baseCtor.prototype[name];

        });

    });

}

Explanation: The applyMixins function copies the methods from the Disposable class into the SmartObject class at runtime, which allows SmartObject to access the isDisposed property and dispose method.

 

This article was written with the help of AI. Can you tell which parts?