Question
I am creating a collection of objects in TypeScript like this:
const userTestStatus = {
"0": { id: 0, name: "Available" },
"1": { id: 1, name: "Ready" },
"2": { id: 2, name: "Started" }
};
How should I declare its type correctly?
Can this be done inline, or do I need to create separate type definitions?
I want to replace xxx with a proper type so that TypeScript can warn me about mistakes later, such as writing:
userTestStatus[3].nammme
instead of the correct property name.
Short Answer
By the end of this page, you will understand how to type collections of objects in TypeScript, especially the difference between a true array and an object with numeric keys. You will learn when to use inline types, type aliases, interfaces, arrays, tuples, and Record, and how these choices affect autocomplete and error checking.
Concept
In TypeScript, the first thing to notice is that your example is not actually an array. It is an object whose keys are strings that look like numbers:
const userTestStatus = {
"0": { id: 0, name: "Available" },
"1": { id: 1, name: "Ready" },
"2": { id: 2, name: "Started" }
};
A real array would look like this:
const userTestStatus = [
{ id: 0, name: "Available" },
{ id: 1, name: "Ready" },
{ id: 2, name: "Started" }
];
That difference matters because TypeScript uses different type systems for:
- arrays: ordered lists, accessed by index
- objects: key-value collections, accessed by property name or index signature
If your data is meant to be a list of statuses, an array is usually the best choice.
If your data is meant to be a lookup table keyed by IDs, then an object or Record may be better.
Typing data correctly matters because it gives you:
- autocomplete for property names like
name - compile-time error checking
- clearer intent for other developers
- safer refactoring later
For example, if each item should have an id and a name, TypeScript can catch errors like:
userTestStatus[0].nammme
because nammme is not a valid property.
However, whether TypeScript can catch userTestStatus[3] depends on the type you choose:
Status[]allows any numeric index, though the result may beundefinedat runtime- a tuple can restrict exact positions
- an object type with explicit keys like
0 | 1 | 2can limit allowed keys more strictly
Mental Model
Think of this as choosing the right storage container:
- An array is like a row of boxes in order: box 0, box 1, box 2.
- An object is like a cabinet with labeled drawers: drawer
"0", drawer"1", drawer"2".
Both can store similar data, but they mean different things.
If you want “a list of statuses in order,” use an array. If you want “a dictionary of statuses by key,” use an object.
TypeScript then acts like a label checker:
- it checks whether each box contains the right shape
- it checks whether property names like
nameare spelled correctly - in some cases, it can even check whether a drawer key exists
Syntax and Examples
If you want a real array of objects, define a type for one object and then use that type in an array.
type Status = {
id: number;
name: string;
};
const userTestStatus: Status[] = [
{ id: 0, name: "Available" },
{ id: 1, name: "Ready" },
{ id: 2, name: "Started" }
];
This is the most common solution.
TypeScript will now allow:
userTestStatus[0].name
and flag:
userTestStatus[0].nammme
because nammme does not exist on Status.
Inline type
You can also write the type inline:
const userTestStatus: { id: number; name: string }[] = [
{ id: 0, name: "Available" },
{ id: 1, name: "Ready" },
{ id: 2, name: "Started" }
];
This works well for small one-off cases.
If you really want an object with numeric keys
Your original code is closer to this:
type Status = {
id: number;
name: string;
};
const userTestStatus: Record<number, Status> = {
0: { id: 0, name: "Available" },
1: { id: 1, name: "Ready" },
2: { id: 2, name: "Started" }
};
This means:
- keys are numbers
- values are
Status
You still get property safety on each value:
userTestStatus[1].name
and TypeScript still flags:
userTestStatus[1].nammme
Using an interface
Instead of a type alias, you can use an interface:
interface Status {
id: number;
name: string;
}
const userTestStatus: Status[] = [
{ id: 0, name: "Available" },
{ id: 1, name: "Ready" },
{ id: 2, name: "Started" }
];
For this use case, type and interface are both fine.
Step by Step Execution
Consider this example:
type Status = {
id: number;
name: string;
};
const userTestStatus: Status[] = [
{ id: 0, name: "Available" },
{ id: 1, name: "Ready" },
{ id: 2, name: "Started" }
];
const firstName = userTestStatus[0].name;
Step by step:
-
type Status = ...defines the shape of one status object.- every
Statusmust have:id: numbername: string
- every
-
const userTestStatus: Status[]means:userTestStatusis an array- every item in the array must be a
Status
-
TypeScript checks each object in the array.
{ id: 0, name: "Available" }is valid{ id: "0", name: "Available" }would be invalid becauseidmust be a number
-
userTestStatus[0]gives one array element.- TypeScript knows that element is a
Status
- TypeScript knows that element is a
-
.nameis valid becausenameexists onStatus.
Now look at this broken example:
const wrong = userTestStatus[0].nammme;
TypeScript reports an error because nammme is not defined in Status.
If you use this instead:
const maybeItem = userTestStatus[3];
TypeScript usually allows the access for normal arrays, because arrays can theoretically have any numeric index. But at runtime, if index 3 does not exist, the result is undefined.
If you want stricter checking for exact positions, use a tuple:
const userTestStatus: [Status, Status, Status] = [
{ id: 0, name: "Available" },
{ id: 1, name: "Ready" },
{ id: 2, name: "Started" }
];
That tells TypeScript there are exactly three items.
Real World Use Cases
Typing arrays and object collections is common in almost every TypeScript codebase.
API responses
type User = {
id: number;
name: string;
};
const users: User[] = await fetchUsers();
This helps you safely access users[0].name.
Dropdown options
type Option = {
id: number;
label: string;
};
const options: Option[] = [
{ id: 1, label: "Small" },
{ id: 2, label: "Medium" },
{ id: 3, label: "Large" }
];
Status lookup tables
type Status = {
id: number;
name: string;
};
const statusById: Record<number, Status> = {
0: { id: 0, name: "Available" },
1: { id: 1, name: "Ready" }
};
Useful when you want fast lookup by ID.
Configuration objects
type RouteConfig = {
path: string;
secure: boolean;
};
const routes: RouteConfig[] = [
{ path: "/", secure: false },
{ path: "/admin", secure: true }
];
Real Codebase Usage
In real projects, developers usually separate two concerns:
- the type of one item
- the type of the collection
Example:
interface Status {
id: number;
name: string;
}
const statuses: Status[] = [
{ id: 0, name: "Available" },
{ id: 1, name: "Ready" }
];
This pattern makes code easier to reuse.
Common patterns
Validation before use
function printStatus(status: Status) {
console.log(status.name);
}
Array methods
Once your array is typed, methods like map, filter, and find become safer:
const names = statuses.map(status => status.name);
const ready = statuses.find(status => status.name === "Ready");
Lookup by ID with Record
If code often accesses items by ID, developers may use:
const statusMap: Record<number, Status> = {
0: { id: 0, name: "Available" },
1: { id: 1, name: "Ready" }
};
Guarding missing values
Because array indexes or object lookups may be missing, developers often add checks:
const status = statusMap[3];
if (!status) {
throw new Error("Status not found");
}
console.log(status.name);
Reusable domain models
A Status type might be shared across:
- API client code
- form logic
- UI components
- tests
- database mapping layers
That keeps names and property types consistent across the application.
Common Mistakes
1. Calling an object an array
This is very common.
Broken idea:
const data = {
0: { id: 0, name: "Available" },
1: { id: 1, name: "Ready" }
};
This is an object, not an array.
A real array uses square brackets:
const data = [
{ id: 0, name: "Available" },
{ id: 1, name: "Ready" }
];
2. Repeating inline types everywhere
This works:
const a: { id: number; name: string }[] = [];
But repeating it many times makes code harder to maintain. Prefer a reusable type:
type Status = {
id: number;
name: string;
};
3. Assuming array access guarantees an item exists
Broken assumption:
const name = userTestStatus[3].name;
If index 3 does not exist, this fails at runtime.
Safer approach:
const status = userTestStatus[3];
if (status) {
console.log(status.name);
}
4. Using the wrong collection type
If your goal is ordered data, use an array.
If your goal is key-based lookup, use Record or an object type.
5. Misspelling properties
Broken code:
userTestStatus[0].nammme
Correct code:
userTestStatus[0].name
This is exactly the kind of mistake TypeScript helps catch.
6. Mixing string and number meanings
In JavaScript objects, keys are effectively strings.
const obj = {
0: "zero"
};
Accessing obj[0] works, but it is still an object property, not true array behavior.
7. Using any
Avoid this:
const userTestStatus: any = [
{ id: 0, name: "Available" }
];
any removes TypeScript's protection.
Comparisons
| Choice | Best for | Example | Notes |
|---|---|---|---|
Status[] | A normal list of items | const statuses: Status[] = [...] | Most common option |
| Inline array type | Small one-off arrays | const statuses: { id: number; name: string }[] = [...] | Fine for short examples |
Record<number, Status> | Lookup by numeric key | const statusMap: Record<number, Status> = { 0: ..., 1: ... } | Better for key-value access |
| Tuple | Fixed number of items in fixed positions | const statuses: [Status, Status, Status] = [...] | Stricter than a normal array |
| Explicit object type | Known exact keys | const statusMap: { 0: Status; 1: Status; 2: Status } = ... | Good when keys are fixed |
type vs interface
| Feature | type | interface |
|---|---|---|
| Object shape | Yes | Yes |
| Arrays/unions/tuples | Yes | Limited compared to type |
| Common for simple models | Yes | Yes |
| Extendable | Yes | Yes |
For this problem, both are acceptable.
Array vs object with numeric keys
| Feature | Array | Object / Record |
|---|---|---|
| Preserves order naturally | Yes | Not the main purpose |
| Indexed access | Yes | Yes |
Array methods like map | Yes | No |
| Good for lookup by key | Sometimes | Yes |
| Best for your original syntax | No | Yes |
If you want array behavior, use an array.
If you want exact key-value mapping, use an object or Record.
Cheat Sheet
// One object type
type Status = {
id: number;
name: string;
};
// Array of objects
const list: Status[] = [
{ id: 0, name: "Available" }
];
// Inline array type
const list2: { id: number; name: string }[] = [
{ id: 1, name: "Ready" }
];
// Object lookup by numeric key
const map: Record<number, Status> = {
0: { id: 0, name: "Available" }
};
// Fixed-size tuple
const tuple: [Status, Status] = [
{ id: 0, name: "Available" },
{ id: 1, name: "Ready" }
];
Quick rules
- Use
Status[]for a normal array of objects. - Use
Record<number, Status>for a numeric-key lookup object. - Use a tuple for a fixed number of items.
- Use a named
typeorinterfacewhen the object shape is reused. - TypeScript catches misspelled properties like
.nammmewhen the item type is known. - Normal arrays do not guarantee that a given index exists.
Edge case
This:
{ "0": {...}, "1": {...} }
is not an array. It is an object.
FAQ
Is { "0": ..., "1": ... } an array in TypeScript?
No. That is an object with keys that look like numbers. A real array uses square brackets: [...].
How do I type an array of objects in TypeScript?
Define the item shape, then use []:
type Status = { id: number; name: string };
const statuses: Status[] = [];
Can I declare the type inline instead of creating a separate type?
Yes.
const statuses: { id: number; name: string }[] = [];
This is fine for small cases.
Should I use type or interface for object arrays?
Either is fine here. Use whichever style is preferred in your codebase.
Why does TypeScript catch .nammme but not always statuses[3]?
Because the object shape is known, so misspelled property names are invalid. But a normal array type does not mean only specific indexes exist.
How can I restrict the collection to exactly three items?
Use a tuple:
const statuses: [Status, Status, Status] = [...];
When should I use Record<number, Status>?
Use it when your data is a lookup object keyed by number rather than an ordered list.
Mini Project
Description
Build a small TypeScript status registry that demonstrates both ways of storing structured data: as an array of status objects and as a lookup object keyed by ID. This helps you practice choosing the right collection type and safely accessing object properties.
Goal
Create a typed collection of statuses and write functions that read them safely without misspelling property names.
Requirements
- Define a reusable type for a status object with
idandname. - Create one collection as an array of statuses.
- Create another collection as a lookup object using numeric keys.
- Write a function that prints all status names from the array.
- Write a function that returns a status name from the lookup object by ID, or a fallback message if not found.
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.
Accessing Input Value from EventTarget in TypeScript
Learn why EventTarget has no value property in TypeScript and safely read values from HTML input events in Angular applications.
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.