Question

How to Type an Array of Objects in TypeScript

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 be undefined at runtime
  • a tuple can restrict exact positions
  • an object type with explicit keys like 0 | 1 | 2 can 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 name are spelled correctly
  • in some cases, it can even check whether a drawer key exists
Take Quiz

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:

  1. type Status = ... defines the shape of one status object.

    • every Status must have:
      • id: number
      • name: string
  2. const userTestStatus: Status[] means:

    • userTestStatus is an array
    • every item in the array must be a Status
  3. TypeScript checks each object in the array.

    • { id: 0, name: "Available" } is valid
    • { id: "0", name: "Available" } would be invalid because id must be a number
  4. userTestStatus[0] gives one array element.

    • TypeScript knows that element is a Status
  5. .name is valid because name exists on Status.

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 }
];
Take Quiz

Real Codebase Usage

In real projects, developers usually separate two concerns:

  1. the type of one item
  2. 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

ChoiceBest forExampleNotes
Status[]A normal list of itemsconst statuses: Status[] = [...]Most common option
Inline array typeSmall one-off arraysconst statuses: { id: number; name: string }[] = [...]Fine for short examples
Record<number, Status>Lookup by numeric keyconst statusMap: Record<number, Status> = { 0: ..., 1: ... }Better for key-value access
TupleFixed number of items in fixed positionsconst statuses: [Status, Status, Status] = [...]Stricter than a normal array
Explicit object typeKnown exact keysconst statusMap: { 0: Status; 1: Status; 2: Status } = ...Good when keys are fixed

type vs interface

Featuretypeinterface
Object shapeYesYes
Arrays/unions/tuplesYesLimited compared to type
Common for simple modelsYesYes
ExtendableYesYes

For this problem, both are acceptable.

Array vs object with numeric keys

FeatureArrayObject / Record
Preserves order naturallyYesNot the main purpose
Indexed accessYesYes
Array methods like mapYesNo
Good for lookup by keySometimesYes
Best for your original syntaxNoYes

If you want array behavior, use an array. If you want exact key-value mapping, use an object or Record.

Take Quiz

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 type or interface when the object shape is reused.
  • TypeScript catches misspelled properties like .nammme when 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.

Take Quiz

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 id and name.
  • 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.
Take Quiz

Keep learning

Related questions

View more