Introduction to Generics: Writing Reusable TypeScript Code
Learn how to use Generics in TypeScript to create flexible, reusable functions that maintain strict type safety across your entire application.

Previously in this course, we explored Mastering Discriminated Unions for Type-Safe API Responses to handle complex API states. While unions allow us to model specific outcomes, we often find ourselves writing the same logic repeatedly just to handle slightly different data shapes. This lesson introduces Generics, the ultimate tool for achieving code reusability without sacrificing the strict type safety you’ve come to rely on.
The Problem: Why Generics Matter
Imagine you need a function that returns the first element of an array. If you only deal with numbers, you might write:
TYPESCRIPTfunction getFirstNumber(items: number[]): number { return items[0]; }
But what if you suddenly need to do the same for strings, or for your Task interface? You would end up duplicating this logic, leading to "code bloat." While you could use any to bypass the issue, you lose all the benefits of TypeScript’s validation, as Defining Function Return Types in TypeScript: A Practical Guide emphasizes the importance of predictable, explicit types.
Generics solve this by allowing you to define a "type placeholder" that is filled in when the function is actually called.
Creating Your First Generic Function
Think of a generic as a parameter for types. Instead of hardcoding number or string, we define a variable—usually named T (for Type)—that represents whatever type the user passes in.
TYPESCRIPTfunction getFirstElement<T>(items: T[]): T { return items[0]; } // Usage const firstTask = getFirstElement<Task>(allTasks); const firstNumber = getFirstElement<number>([1, 2, 3]);
In the code above:
<T>declares the generic type parameter.items: T[]tells TypeScript the function expects an array of whateverTis.: Ttells TypeScript the return value will match that same type.
By passing <Task> or <number> at the call site, you are essentially "configuring" the function to work specifically for that data structure while keeping the logic shared.
Advancing the Task API Client
In our running project, we often receive different types of data from the server. Instead of writing a custom function for every response shape, we can use a generic wrapper. Let's create a identity-style helper that logs a response and returns it:
TYPESCRIPTfunction logAndReturn<T>(data: T): T { console.log("Data received:", data); return data; } // Usage in our API client const task = logAndReturn<Task>(apiResponse);
This ensures that task is correctly typed as Task without us having to write separate logging functions for Task, User, or Category interfaces.
Hands-on Exercise
Create a generic function called wrapInArray that takes a single argument of type T and returns an array containing that element.
- Define the function using the
<T>syntax. - Call the function with a
string. - Call the function with a
Taskobject. - Verify that the output types are
string[]andTask[]respectively.
Common Pitfalls
- Over-using Generics: Don't use them if a simple union or a specific type is sufficient. If a function only ever handles
stringornumber, don't make it generic; just use a union type. - Cryptic Naming: While
Tis the industry standard for the first generic, use descriptive names likeTResponseorTDataif your function has multiple generic parameters. - Ignoring Constraints: Sometimes a generic is too flexible. If you try to access
.idon a genericT, TypeScript will complain because it doesn't know ifThas anid. We will cover how to solve this in the next lesson on constraining generics.
FAQ
Q: Are generics just a fancy name for any?
A: Absolutely not. any tells TypeScript to stop checking types entirely. Generics preserve type information, allowing the compiler to verify that what you put in is what you get out.
Q: Can I use more than one generic?
A: Yes! You can define multiple parameters like <T, U> to map inputs to different outputs.
Q: Do generics add runtime overhead? A: No. Generics are a compile-time construct. They are erased during the transpilation process, meaning they have zero impact on your application's bundle size or performance.
Recap
Generics allow you to write reusable functions that maintain strict types. By using a placeholder like <T>, you create flexible logic that adapts to any data structure while ensuring the compiler continues to protect your code from runtime errors.
Up next: Generics with Interfaces — where we apply these concepts to our API models for even greater consistency.
Work with me

Next.js Full-Stack Web App Development
A fast, SEO-ready full-stack web app built with Next.js 16 — from idea to deployed product, by an engineer who ships to production.

Next.js Website & Landing Page Development
A blazing-fast, SEO-optimized website or landing page in Next.js — the kind that loads instantly and ranks. Design-to-code, done right.


