TypeScript's [number] Index Signature: Extract Union Types from Arrays
![TypeScript's [number] Index Signature: Extract Union Types from Arrays](/_next/image?url=https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1752960594694%2Fe2be9492-869f-4169-bb1a-950ee81a3887.png&w=3840&q=75)
Full stack engineer but passionnated by front-end Angular Expert / NX / JavaScript / Node / Redux State management / Rxjs
Search for a command to run...
![TypeScript's [number] Index Signature: Extract Union Types from Arrays](/_next/image?url=https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1752960594694%2Fe2be9492-869f-4169-bb1a-950ee81a3887.png&w=3840&q=75)
Full stack engineer but passionnated by front-end Angular Expert / NX / JavaScript / Node / Redux State management / Rxjs
A structured approach to Angular applications with Ngrx Signal Store

The useEffect hook is fundamental to React functional components, but its behaviour changes dramatically based on how you handle the dependencies array. Let's explore the three main patterns and when to use each one. useEffect without dependencies ar...

Git worktree is a Git feature that allows you to check out multiple branches at once in different directories, without cloning the repository again. This powerful capability transforms how developers handle parallel development workflows. The Problem...

When working with Git in collaborative environments, you'll inevitably encounter situations where you need to overwrite remote history. Two commands come into play: git push --force and git push --force-with-lease. Understanding the difference betwee...

Ever needed to create a union type from an array of objects in TypeScript? There's an elegant solution using the [number] index signature notation that can save you from maintaining duplicate type definitions.
Let's say you have an array of API status objects and need to create types from them:
const API_STATUSES = [
{ code: 'success', message: 'Request completed', httpCode: 200 },
{ code: 'error', message: 'An error occurred', httpCode: 500 },
{ code: 'loading', message: 'Request in progress', httpCode: null },
{ code: 'timeout', message: 'Request timed out', httpCode: 408 }
] as const;
// How do we get types from this array?
You could manually define union types, but that means maintaining the same information in two places:
// Manual approach - duplication!
type ApiStatusCode = 'success' | 'error' | 'loading' | 'timeout';
type ApiStatusObject = {
code: ApiStatusCode;
message: string;
httpCode: number | null;
};
The [number] notation extracts the type of elements that can be accessed with any numeric index:
const API_STATUSES = [
{ code: 'success', message: 'Request completed', httpCode: 200 },
{ code: 'error', message: 'An error occurred', httpCode: 500 },
{ code: 'loading', message: 'Request in progress', httpCode: null },
{ code: 'timeout', message: 'Request timed out', httpCode: 408 }
] as const;
// Extract the union of all object types
type ApiStatusObject = (typeof API_STATUSES)[number];
// Extract specific property types
type ApiStatusCode = ApiStatusObject['code']; // 'success' | 'error' | 'loading' | 'timeout'
Breaking down (typeof API_STATUSES)[number]:
typeof API_STATUSES - Gets the type of the array[number] - Accesses the type of elements at any numeric indexThe [number] essentially says "give me the type of whatever can be found at any number index in this array."
Here's how you might use this in practice:
const API_STATUSES = [
{ code: 'success', message: 'Completed', retryable: false },
{ code: 'error', message: 'Failed', retryable: true },
{ code: 'loading', message: 'In progress', retryable: false },
{ code: 'timeout', message: 'Timed out', retryable: true }
] as const;
type ApiStatusObject = (typeof API_STATUSES)[number];
type ApiStatusCode = ApiStatusObject['code'];
function getStatusInfo(code: ApiStatusCode): ApiStatusObject | undefined {
return API_STATUSES.find(status => status.code === code);
}
function isRetryable(code: ApiStatusCode): boolean {
const status = getStatusInfo(code);
return status?.retryable ?? false;
}
// TypeScript knows these are valid
console.log(isRetryable('error')); // true
console.log(isRetryable('success')); // false
// TypeScript error - invalid status code
// console.log(isRetryable('invalid')); // ❌
// Argument of type '"invalid"' is not assignable to parameter of type '"success" | "error" | "loading" | "timeout"'.(2345)
Single source of truth: Your array serves as both runtime data and type definition. Add a new status object to the array, and the types update automatically.
Type safety: TypeScript catches typos and invalid values at compile time.
Better refactoring: Rename a property in your array objects, and TypeScript will help you find all the places that need updating.
The [number] index signature is a simple but powerful TypeScript feature. It eliminates the need to manually maintain union types that mirror your array data, giving you better type safety with less code duplication.
Next time you find yourself writing the same information in both an array and a type definition, remember this elegant solution.