When Structure Comes From Patterns: Understanding Record in TypeScript
Record<K, T> generates object types from a set of keys, turning repeated structures into a single, consistent pattern driven by relationships instead of manual definitions.
Thereâs a point where defining objects manually starts to feel⌠repetitive.
You write something like:
type Config = {
darkMode: boolean
notifications: boolean
autoSave: boolean
}Itâs clear.
It works.
But thereâs something slightly rigid about it.
Because the structure isnât really the interesting part.
Whatâs interesting is the pattern behind it.
All keys share the same type.
All values follow the same rule.
The only thing that changes is the name of the key.
And yet, weâre writing it out one by one.
When Keys Become the Source of Truth
Now imagine those keys arenât just random.
They come from somewhere else.
type Settings = "darkMode" | "notifications" | "autoSave"Now your intent changes.
Youâre no longer thinking:
âThis object has these fieldsâ
Youâre thinking:
âFor each setting, there should be a valueâ
Thatâs a completely different perspective.
The structure is no longer primary.
The keys are.
From Writing Objects to Generating Them
This is where Record starts to make sense.
type Config = Record<Settings, boolean>At first glance, it feels like a shortcut.
But itâs not just shorter.
Itâs expressing something deeper.
Youâre not manually defining an object anymore.
Youâre generating one from a rule.
âFor every key in Settings, assign a boolean valueâThatâs a shift from definition to transformation.
A Different Way to Think About Objects
Without Record, objects feel static.
You write them out, field by field.
With Record, objects become derived.
They emerge from relationships between types.
Instead of saying:
âThis object has these propertiesâ
Youâre saying:
âThis object is built from these keys and this value typeâ
That makes your types more adaptable.
Because if Settings changes, the object updates automatically.
No duplication.
No drift.
What Record Actually Does
Under the surface, Record is very simple.
type Record<K extends keyof any, T> = {
[P in K]: T
}It loops over each key and assigns the same type.
Nothing more.
Nothing less.
So it doesnât add complexity.
It removes repetition.
The Constraint That Defines It
Record is strict.
If you say:
type Config = Record<"a" | "b", number>Then both keys must exist.
const config: Config = {
a: 1
// â missing b
}That strictness is intentional.
Because Record is about completeness.
It assumes that if a key exists in your set, it must be represented.
Where It Becomes Limiting
That same strictness can also be a limitation.
All values must share the same type.
All keys must be present.
Thereâs no room for variation.
Which means Record is not for everything.
It works best when your data follows a uniform pattern.
If different keys require different types, youâre solving a different problem.
A Subtle but Important Distinction
Compare these two:
type A = {
darkMode: boolean
notifications: boolean
}type B = Record<"darkMode" | "notifications", boolean>They look identical.
But they carry different meaning.
The first is a standalone definition.
The second is tied to a source.
And that connection makes your system more consistent over time.
Because one is a snapshot.
The other is a rule.
The Bigger Insight
Record exists because sometimes structure isnât something you invent.
Itâs something you derive.
It recognizes that patterns matter more than repetition.
That relationships between types can define structure more clearly than manual definitions ever could.
Itâs not just about avoiding duplication.
Itâs about expressing intent at a higher level.
Because in many systems, the question isnât:
âWhat does this object look like?â
But:
âWhat rule generates this object?â