There’s a tendency in the TypeScript community to throw generics everywhere just to look "complete" or sophisticated. But overusing them often just makes your code harder to read.
My rule of thumb is simple: only add a type parameter if it actually removes the need for type casting in multiple places. If you're only doing it to make the documentation look prettier, don't bother.
Real problems arise when building components like Select<T> or Table<T>. You
need to ensure the data being consumed remains "honest." Without generics, you
often end up using any or creating extra props that don't actually sync with
your original data. For example, an onChange function might accidentally
receive a string when the app is actually managing a full Project object.
This is where generics actually shine—they ensure the data coming in and going
out stays consistent.
That said, letting T run wild is risky. Instead of leaving it totally
unrestricted, it’s better to constrain the type based on capability rather than
the specific domain entity. If a component only needs an id and a label,
don't force it to accept a massive interface from your billing module. Just
limit it to what the UI actually requires.
I often see people using explicit type arguments, like Select<Project>. But if
the type of your options data is defined correctly, TypeScript should be able
to infer it automatically. If you find yourself manually typing it out every
time, it’s a signal that your underlying data types are too weak or too broad.
And don't use any as a shortcut. Using any inside a design system component
is like passing technical debt down to everyone who uses that component. It's
much better to use T and let the system handle the inference than to hide
any inside a render function.
Be careful, too, when stacking generics with patterns like CVA or polymorphism. Your files can quickly bloat to hundreds of lines just to manage types. You have to prioritize complexity: get the visual variants sorted first, then deal with the data types. If a component suddenly needs three or four type parameters, it’s a sign that the component API needs to be broken up.
The key question to ask yourself is: "If I remove this type parameter, will I be forced to do type casting elsewhere?" If the answer is no, then that generic is just decoration. Just delete it and keep your code concise.