-
Section 1: Getting Started
-
Section 2: Basic Types
-
Section 3: Functions and Objects
-
Section 4: Advanced Types
-
Section 5: Object-Oriented TypeScript
-
Section 6: Working with Modules
-
Section 7: Tooling and Practice
-
Section 8: Type-Level Programming
-
Section 9: TypeScript with Backends
-
Section 10: Testing Typed Code
-
Section 11: Data Validation with Types
-
Section 12: Practical Projects
-
Section 13: Compiler Internals
-
Section 14: Configuration Deep Dive
-
Section 15: Enums, Symbols, and Special Types
-
Section 16: Working with Async Code
-
Section 17: TypeScript and the DOM
-
Section 18: Advanced Generics Practice
-
Section 19: Working with Third-Party Types
-
Section 20: Monorepo and Large-Scale Practices
-
Section 21: Common Pitfalls and Best Practices
-
Section 22: Interview Practice
-
Section 23: Handbook: Narrowing In Depth
-
Section 24: Handbook: Object Types In Depth
-
Section 25: Handbook: Classes In Depth
-
Section 26: Handbook: Modules In Depth
-
Section 27: Handbook: Declaration Files In Depth
-
Section 28: JSX and Namespaces
-
Section 29: Compiler Configuration Reference
-
Section 30: More Practice Exercises
-
Section 31: Handbook: Everyday Types Deep Dive
-
Section 32: Utility Types Full Reference
-
Section 33: Decorators Reference
-
Section 34: Mixins and Advanced OOP Patterns
-
Section 35: Iterators and Generators Typing
-
Section 36: More Type-Level Programming Practice
-
Section 37: TypeScript Ecosystem Tools
-
Section 38: TypeScript with Testing Frameworks
-
Section 39: TypeScript for Library Authors
-
Section 40: More Interview Practice
-
Section 41: Handbook: Functions In Depth
-
Section 42: Handbook: Type Manipulation Deep Dive
-
Section 43: More Real-World Patterns
-
Section 44: TypeScript Release Notes Highlights
-
Section 45: Final Practice and Review
130: Common TypeScript Interview Questions on Generics
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
EventMapthat maps event names to their payload types (e.g.,'userLogin': { userId: string }and'error': { message: string, code: number }). - Create an
EventEmitterclass that uses a genericT 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.
There are no comments for now.