Skip to Content
Course content

130: Common TypeScript Interview Questions on Generics

Click on the "Edit" button in the top corner of the screen to edit your slide content.

If you've ever sat through a TypeScript interview, you know that generics are usually where the interviewer decides if you actually understand the type system or if you're just using it as a fancy version of JavaScript. I've seen plenty of senior devs stumble here because they treat generics as a "magic box" that just makes errors go away.

// The "I'll just use a generic" mistake
function getProperty(obj: T, key: string) {
    return obj[key]; 
    // Error: Element implicitly has an 'any' type because 
    // expression of type 'string' can't be used to index type 'T'.
}

const user = { id: 1, name: "Alex" };
const name = getProperty(user, "name"); // Returns 'any', loses all type safety

The problem with unconstrained generics

The code above is a classic. You're thinking, "I want this function to work with any object, so I'll use T." But here's the catch: T is too honest. It tells TypeScript that obj could be anything—a number, a boolean, or a null value. You can't index a number with a string, so TypeScript throws a fit.

Even if you ignore the error with a type cast, you've defeated the purpose of using TypeScript. The return type of getProperty becomes any, and you've effectively turned off the compiler for that entire data flow. That's a red flag in any technical interview.

Fixing it with Generic Constraints and keyof

To fix this, we need to tell TypeScript that T isn't just "anything," but specifically "something that can be indexed." We do this using the extends keyword and the keyof operator.

function getProperty<T, K extends keyof T>(obj: T, key: K) {
    return obj[key];
}

const user = { id: 1, name: "Alex" };
const name = getProperty(user, "name"); // Type is correctly inferred as string
const id = getProperty(user, "id");     // Type is correctly inferred as number

// Now, this will actually throw a compile-time error:
// getProperty(user, "email"); // Error: Argument of type '"email"' is not assignable to '"id" | "name"'

By saying K extends keyof T, we're creating a relationship between the two generics. We're telling the compiler: "Whatever T is, K must be one of the keys that actually exists on that object." This is exactly the kind of answer interviewers are looking for—it shows you understand how to maintain type safety across dynamic inputs.

T vs Any: The most common interview trap

An interviewer will almost certainly ask you: "Why not just use any?"

If you answer "because it's safer," you're giving a junior answer. The professional answer is about type preservation. When you use any, you're telling the compiler to stop tracking the type entirely. When you use a generic T, you're telling the compiler: "I don't know what this type is yet, but I want you to remember it and keep it consistent."

For example, if you have a function that returns the same type it receives, any loses the information of what was passed in. T carries that information through the function call and back to the caller.

Handling complex API responses with Generics

Another high-frequency question involves wrapping API responses. You don't want to write a different interface for every single endpoint. Instead, you create a generic wrapper.

interface ApiResponse<T> {
    data: T;
    status: number;
    message: string;
}

interface User {
    username: string;
    email: string;
}

// We reuse the same wrapper for different data shapes
async function fetchUser(): Promise<ApiResponse<User>> {
    const response = await fetch("/api/user");
    return response.json();
}

async function fetchPosts(): Promise<ApiResponse<Post[]>> {
    const response = await fetch("/api/posts");
    return response.json();
}

This pattern is the industry standard. It keeps your code DRY (Don't Repeat Yourself) while ensuring that response.data is correctly typed as a User in one function and a Post[] in another.




📋 Practical Task

Build a Type-Safe Event Emitter

In a real-world application, event emitters often suffer from "stringly-typed" events where you pass a string for the event name and any for the payload. Your task is to create a type-safe EventEmitter class.

Requirements:

  • Define an interface called EventMap that maps event names to their payload types (e.g., 'userLogin': { userId: string } and 'error': { message: string, code: number }).
  • Create an EventEmitter class that uses a generic T extends Record<string, any> to accept the event map.
  • Implement an on<K extends keyof T>(eventName: K, callback: (payload: T[K]) => void) method.
  • Implement an emit<K extends keyof T>(eventName: K, payload: T[K]) method.

Goal: Ensure that if you try to emit an event with the wrong payload type, or on an event that doesn't exist in the EventMap, TypeScript throws a compile-time error.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.