Question
TypeScript Dictionary Objects: How to Type Key-Value Objects
Question
I have JavaScript code that uses plain objects as dictionaries. For example, a people object stores personal details, keyed by email address.
var people = {
// email: personal data
};
people[email] = data; // add or update
var data = people[email]; // read
delete people[email]; // remove
Is it possible to describe this pattern in TypeScript, or do I need to use an array instead?
Short Answer
By the end of this page, you will understand how to type dictionary-style objects in TypeScript using index signatures and Record. You will also learn when to use an object, when an array is the wrong choice, and what common mistakes to avoid when storing values by dynamic string keys such as email addresses.
Concept
In JavaScript, objects are often used as dictionaries: collections of key-value pairs where you look up a value by a dynamic key.
In your example, the key is an email address and the value is some person-related data.
TypeScript supports this pattern directly. You do not need to use an array for this. In fact, an array would usually be the wrong tool unless your keys are numeric indexes.
The main TypeScript feature for this is an index signature.
interface PeopleDictionary {
[email: string]: PersonData;
}
This means:
- the object can have any number of properties
- each property key is a
string - each property value must be of type
PersonData
This is useful because it lets TypeScript check that:
- you only store the right kind of value
- you access the dictionary in a type-safe way
- your code communicates intent clearly
A more modern shorthand is Record<string, PersonData>:
type PeopleDictionary = Record<string, PersonData>;
Both approaches describe a dictionary-like object.
Mental Model
Think of a dictionary object like a set of mailboxes.
- The label on each mailbox is the key, such as an email address.
- The content inside the mailbox is the value, such as a person's data.
TypeScript lets you say:
- what kind of labels are allowed
- what kind of content each mailbox can contain
So instead of saying “this is just some object,” you say:
“This is an object where every string key points to a
PersonDatavalue.”
That makes the structure predictable and safer to use.
Syntax and Examples
The most common ways to type a dictionary in TypeScript are index signatures and Record.
Using an index signature
interface PersonData {
name: string;
age: number;
}
interface PeopleDictionary {
[email: string]: PersonData;
}
const people: PeopleDictionary = {};
people["alice@example.com"] = { name: "Alice", age: 30 };
people["bob@example.com"] = { name: "Bob", age: 25 };
const person = people["alice@example.com"];
console.log(person.name); // Alice
delete people["bob@example.com"];
What this means
peopleis an object- any string can be used as a key
- every value must match
Step by Step Execution
Consider this example:
interface PersonData {
name: string;
age: number;
}
const people: Record<string, PersonData> = {};
people["alice@example.com"] = { name: "Alice", age: 30 };
people["bob@example.com"] = { name: "Bob", age: 25 };
const person = people["alice@example.com"];
if (person) {
console.log(person.name);
}
delete people["bob@example.com"];
Here is what happens step by step:
-
interface PersonDatadefines the shape of each stored value.- Every person must have a
name - Every person must have an
age
- Every person must have a
-
const people: Record<string, PersonData> = {};
Real World Use Cases
Dictionary-style objects appear everywhere in real applications.
Common use cases
- Users by email
- store user profiles keyed by email address
- Products by ID
- quickly look up product data without looping through a list
- Feature flags by name
- store
trueorfalsevalues keyed by flag name
- store
- Translations by language key
- map keys like
"welcome_message"to translated text
- map keys like
- Cached API responses
- store responses by URL or resource ID
- Session data
- track session information by token or session ID
Example: feature flags
const flags: Record<string, boolean> = {
darkMode: true,
betaDashboard: false
};
if (flags["darkMode"]) {
console.log("Enable dark theme");
}
Real Codebase Usage
In real projects, developers often use typed dictionaries for fast lookups, caching, validation results, and grouped data.
Common patterns
1. Lookup tables
Instead of searching an array repeatedly, code converts data into an object keyed by ID.
interface User {
id: string;
name: string;
}
const usersById: Record<string, User> = {};
This makes access simple:
const user = usersById[userId];
2. Guard clauses for missing entries
const user = usersById[userId];
if (!user) {
throw new Error("User not found");
}
console.log(user.name);
This is common in services, controllers, and business logic.
3. Validation error collections
const : <, > = {};
errors. = ;
errors. = ;
Common Mistakes
Here are some common mistakes beginners make when typing dictionary objects.
1. Using an array for string keys
Broken code:
const people: PersonData[] = [];
people["alice@example.com"] = { name: "Alice", age: 30 };
Why this is a problem:
- arrays are meant for numeric indexes
- string keys on arrays are confusing and not the intended pattern
Use this instead:
const people: Record<string, PersonData> = {};
2. Using Object as the type
Broken code:
const people: Object = {};
people["alice@example.com"] = { name: "Alice", age: 30 };
Why this is a problem:
Objectis too vague
Comparisons
Here is how dictionary objects compare to related options.
| Option | Best for | Key type | Notes |
|---|---|---|---|
Record<string, T> | Simple key-value objects | string | Clean and readable for dictionaries |
| Index signature | Custom dictionary types | string, number, symbol | More flexible when defining interfaces |
Array T[] | Ordered lists | number | Not suitable for email or named keys |
Map<K, V> | Advanced key-value storage | any type | Useful when keys are not just strings or when map-specific methods are needed |
Record vs index signature
Cheat Sheet
Quick syntax
Index signature
interface Dictionary {
[key: string]: ValueType;
}
Record
type Dictionary = Record<string, ValueType>;
Example
interface PersonData {
name: string;
age: number;
}
const people: Record<string, PersonData> = {};
people["alice@example.com"] = { name: "Alice", age: 30 };
const person = people["alice@example.com"];
delete people["alice@example.com"];
Use a dictionary when
- keys are strings
- you need fast lookup by key
- values all have the same general type
FAQ
Can I use an object as a dictionary in TypeScript?
Yes. This is a common pattern. Use an index signature or Record<string, T> to type it safely.
Do I need an array instead of an object?
No. Use an array only when your data is a list accessed by numeric index. For email or ID keys, an object dictionary is usually better.
What is the TypeScript type for dynamic string keys?
A common choice is:
Record<string, T>
or
interface MyDictionary {
[key: string]: T;
}
What happens if the key does not exist?
At runtime, the result is undefined. Check that the value exists before using its properties.
Should I use Record or an interface with an index signature?
Both work well. Record is shorter. An interface can be clearer when you want a named custom type.
Is Map better than an object in TypeScript?
Sometimes. Use Map when you need special map methods or non-string keys. For simple string-key dictionaries, an object is often enough.
Mini Project
Description
Build a small email-based contact lookup. This project demonstrates how to use a typed dictionary to store, retrieve, update, and delete person records by email address. It reflects a common real-world pattern used in user directories, caches, and in-memory lookup tables.
Goal
Create a TypeScript dictionary that stores contacts by email and provides basic add, read, update, and delete operations.
Requirements
- Define a type for contact data with at least a name and phone number.
- Create a dictionary object keyed by email address.
- Add at least three contacts to the dictionary.
- Read and print one contact by email.
- Delete one contact and show that it no longer exists.
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.
Angular @ViewChild Returning Undefined: Lifecycle, Child Components, and Fixes
Learn why Angular @ViewChild can be undefined, when it becomes available, and how to access child components correctly using lifecycle hooks.