Test, ODI, or T20?
Same shape — innings, overs, runs. Different format.
An innings is an innings. It has a format, a number of overs, a running score. Whether it's a five-day Test, a fifty-over ODI, or a twenty-over T20, the shape is the same. Only the format — that one label that hangs off the top of the scorecard — changes.
Innings<T> says exactly that. The shape
is fixed; the format slot is a parameter you fill in. Build an
Innings<'Test'>, or an
Innings<'T20'>, and TypeScript carries
the format around with the data — every place that reads
.format knows which one it got.
Pick a format. Watch the same generic interface lock in three different innings shapes.
One shape, three formats — that's a generic.
<T> is a type parameter — a slot the caller fills in. The interface body uses T the way a function uses an argument. The shape is reusable, the format stays exact.
type Format = 'Test' | 'ODI' | 'T20'; interface Innings<T extends Format> { format: T; overs: number; score: { runs: number; wickets: number }; } const first: Innings<'T20'> = { format: 'T20', overs: 20, score: { runs: 186, wickets: 4 }, };
Generics keep shapes honest.
You used one Innings<T> interface to build three different innings. The format wasn't a string-typed comment — it was tracked by TypeScript as 'Test', 'ODI', or 'T20' for the lifetime of that value.
Use a generic when the same shape applies to many specific types — a container, a wrapper, a record. The shape becomes a function over types: pass in the type, get back the specific version.