Generics in JavaScript: Why They're Needed and How to Use Them
In the world of modern JavaScript and Node.js development, the concept of Generics is increasingly common. Although native JavaScript does not support strict typing, tools such as TypeScript and JSDoc allow you to implement powerful mechanisms of generic programming. In this article, we will break down why Generics are needed, how they work in the context of Node.js, and how to use them correctly.
What are Generics and why are they needed?
Generics are a mechanism that allows you to create components (functions, classes, interfaces) that work with different data types while maintaining strict typing. Instead of writing the same function for numbers, strings, and arrays, you write one generic piece of code.
Main advantages of Generics:
- Type safety — type errors are caught at the compilation/checking stage, not at runtime.
- Code reuse — one function works with any data type.
- Readability — code becomes self-documenting.
- IDE integration — autocompletion and type hints work correctly.
In Node.js, Generics are especially useful when working with databases, API requests, data collections, and utilities.
Generics in TypeScript (the main way)
TypeScript is a superset of JavaScript that adds static typing. It is in TypeScript that Generics are implemented most fully. Let's look at the basic syntax.
Simple generic function
// Without Generics — you have to use anyfunction identity(arg: any): any { return arg;}
// With Generics — the type is preservedfunction identity<T>(arg: T): T { return arg;}
// Usageconst num = identity<number>(42); // type numberconst str = identity<string>("hello"); // type stringAngle brackets <T> — this is the declaration of a type parameter. T is a conventional name; it can be anything (often T, U, V are used).
Generics with arrays
One of the most common cases is working with arrays:
// Function that returns the first element of an arrayfunction getFirst<T>(arr: T[]): T | undefined { return arr[0];}
const firstNumber = getFirst([1, 2, 3]); // numberconst firstString = getFirst(["a", "b", "c"]); // stringMultiple types
You can use several type parameters:
function pair<T, U>(first: T, second: U): [T, U] { return [first, second];}
const result = pair(42, "answer"); // type [number, string]Constraints in Generics
Sometimes you need to limit which types can be used. For this, constraints are used with the keyword extends.
// Function that only works with objects that have a length propertyfunction getLength<T extends { length: number }>(arg: T): number { return arg.length;}
getLength("hello"); // 5 — string has lengthgetLength([1, 2, 3]); // 3 — array has length// getLength(42); // Error! number does not have lengthThis is especially useful in Node.js when working with buffers, strings, and arrays — anything that has a length property.
Generics in classes and interfaces
Generics work great not only with functions but also with classes and interfaces.
Generic class
Example of a simple stack in Node.js:
class Stack<T> { private items: T[] = [];
push(item: T): void { this.items.push(item); }
pop(): T | undefined { return this.items.pop