Records & Dynamic Keys

The Course
•
5 min read

Sometimes you need a value by name rather than by position. Record<Keys, Value> is a built-in helper type for an object whose specified keys have the given value type. The angle brackets supply information to a reusable type; Day 5 will explain how to write such generics yourself.

Record<"planned" | "done", string> requires both of those keys with string values. A union of literal keys is a closed vocabulary, useful for complete label tables. Missing a key is a type error. Indexing with a key from that same union gives a string.

Record<string, number> describes an open dictionary. It cannot guarantee that an arbitrary runtime key exists. With this course’s unchecked-index protection, a lookup can return undefined. Supply a default or branch on absence before arithmetic.

Prefer a small literal-key union when the possible keys are known. Use an open dictionary when names genuinely arrive dynamically. These examples use controlled keys. Validating arbitrary external keys is a separate boundary task, not something a Record annotation does for you. Do not read a type like Record<string, number> as permission to trust external data.

The type helper produces no object at runtime. You still create a plain JavaScript object, and the same rules for property access apply. Types make the intended role visible.

Predict, check, then run

Before running, write down the output and explain which information the checker knows. Click Check types first, then Check & run.

Predict, then runTypeScript
type Status = "planned" | "done";

const labels: Record<Status, string> = { planned: "Next", done: "Complete" };

function statusLabel(status: Status): string {
  return labels[status];
}

console.log(statusLabel("done"));

const minutes: Record<string, number> = { Types: 25 };

console.log(minutes["Unknown"] ?? 0);

Enable JavaScript to edit and type-check this example.

Check your prediction
Complete
0

Read a real compiler error

A closed record promises an entry for every named key, even if this run only reads Mon.

Find and fix the type errorTypeScript
type Day = "Mon" | "Tue";

const minutes: Record<Day, number> = { Mon: 10 };

console.log(minutes.Mon);

Enable JavaScript to edit and type-check this example.

Check your diagnosis

Add a numeric Tue entry. If the real model allows absent days, use a different contract and explicitly handle missing data; do not weaken a complete-table contract accidentally.

Closed keys and dynamic keys need different expectations

Record<Status,string> requires one label for every known status. Record<string,number> cannot promise that every possible runtime string has a value; a dynamic lookup may be undefined under the course settings.

Choose a closed key union for a complete table and a dynamic dictionary for genuinely open input. Do not widen a finite vocabulary to string just to silence a missing-key error: that removes useful coverage checks.

Closed keys and dynamic keys need different expectations: traceTypeScript
type Status = "planned" | "done";

const labels: Record<Status, string> = { planned: "Next", done: "Complete" };

console.log(labels.planned);
console.log(labels.done);

Enable JavaScript to edit and type-check this example.

Check your prediction
Next
Complete

Every valid Status has a label. Adding another status forces a deliberate table update, which is useful maintenance evidence rather than an inconvenience to bypass.

Closed keys and dynamic keys need different expectations: contrastTypeScript
const totals: Record<string, number> = { Types: 0 };

console.log(totals["Types"] ?? 99);
console.log(totals["Missing"] ?? 99);
console.log(totals["Types"] || 99);

Enable JavaScript to edit and type-check this example.

Check your prediction
0
99
99

Nullish fallback preserves a real zero. The || expression replaces it. A dictionary being numeric does not justify treating every absent or falsy lookup the same way.

Try the rule on new inputs

Add a new closed status and observe the missing-label error. For the dynamic dictionary, test an existing zero, an existing positive amount and a missing key. State which lookup needs a fallback.

Your turn

Exercise

Records & Dynamic Keys: practise

Create a Record for weekday keys “Mon” | “Tue” | “Wed” with numeric minutes. Write dayMinutes(day) to look up a valid day. Print 20 for Tue. Include all three keys.

Your workspaceTypeScript
// Define Day, a complete minutes table, and a typed lookup function.

Enable JavaScript to edit and type-check this example.

Hint 1: the idea

A closed key union makes the complete table checkable.

Hint 2: a first step

Declare Day as a literal union and use it as the first argument to Record.

Show one solution
One possible solutionTypeScript
type Day = "Mon" | "Tue" | "Wed";

const minutes: Record<Day, number> = { Mon: 10, Tue: 20, Wed: 0 };

function dayMinutes(day: Day): number {
  return minutes[day];
}

console.log(dayMinutes("Tue"));

Enable JavaScript to edit and type-check this example.

20

Explain the types at the function boundary and the runtime checks, if any. Compare your approach before copying this answer.

Change it and explain it

Remove Wed and inspect the error, then restore it. Change the table to Record<string, number> and explain why arbitrary lookups now need a missing-value policy. Do not “fix” a missing key by claiming that it can never be missing.

Was this article helpful?

Your response is saved on this device.