Why Typing is a Superpower, Not a Burden
\n\nImagine: you're writing code, everything works, you're happy. And then — bam! — a red error in the console: Cannot read property 'name' of undefined. Sound familiar? Welcome to the world of JavaScript without types. But there's good news: modern JavaScript (and especially TypeScript) offers a powerful tool — Generics. This isn't just "another feature," but a real way to make code cleaner, safer, and more understandable.
In this article, we'll break down what typing in JavaScript is, how generics work, and why you should use them in every project. No dry theory — only live examples and practical tips.
\n\nWhat are Generics and Why Do You Need Them?
\n\nGenerics (generic types) are a mechanism that allows you to write functions, classes, and interfaces that work with different data types while maintaining strict typing. Simply put: you create "universal" code that automatically adapts to the data type it works with.
\n\nWithout generics, you'd write something like:
\n\nfunction identity(arg: any): any {\n return arg;\n}\n\nThe problem? any kills all typing. You lose type information, and the compiler won't be able to warn you about errors. With Generics, it's different:
function identity<T>(arg: T): T {\n return arg;\n}\n\n// Usage\nlet num = identity<number>(42); // type number\nlet str = identity<string>("Hello"); // type string\n\nMagic? No, just smart typing. The type T is a placeholder that gets replaced with a real type when the function is called.
Generics vs any: The Battle for Safety
\n\nMany beginners ask: "Why bother with generics when you can just use any?" The answer is simple: type safety isn't about "making it look nice," but about preventing bugs at runtime.
Look at this example without Generics:
\n\nfunction getLength(data: any): number {\n return data.length; // if data is a number, there will be an error\n}\n\ngetLength(42); // undefined at runtime\n\n\nWith generics, you explicitly specify that the function works with arrays or strings:
\n\nfunction getLength<T extends { length: number }>(data: T): number {\n return data.length;\n}\n\ngetLength([1,2,3]); // 3\ngetLength("hello"); // 5\n// getLength(42); // Compilation error — number doesn't have length\n\n\nSee the difference? The error is caught at the code-writing stage, not in production. That's the essence of advanced typing.
\n\nPractical Examples of Generics in JavaScript
\n\nLet's look at a few real-world scenarios where generics truly save the day.
\n\n1. Generic Functions for Working with Arrays
\n\nSuppose you're writing a utility to extract the first element of an array:
\n\nfunction first<T>(arr: T[]): T | undefined {\n return arr[0];\n}\n\nconst numbers = [10, 20, 30];\nconst firstNum = first(numbers); // type number | undefined\n\nconst strings = ["a", "b", "c"];\nconst firstStr = first(strings); // type string | undefined\n\n\nWithout generics, you'd have to write two separate functions or use any. With JavaScript typing through Generics — one function works for all types.
2. Generic Classes for Storing Data
\n\nImagine you're creating a simple wrapper for storing a value:
\n\nclass Box<T> {\n private content: T;\n\n constructor(initial: T) {\n this.content = initial;\n }\n\n getValue(): T {\n return this.content;\n }\n\n setValue(value: T): void {\n this.content = value;\n }\n}\n\n// Usage\nconst numberBox