Skip to main content

TS Ep 26: Indexed Access Types (`T[K]`)

Rachmat Hidayat
Author
Rachmat Hidayat
Learn & sharing insights on TypeScript, Go, Kubernetes, DevOps, DevSecOps, SRE, Platform Engineering, AI/ML Engineering, and MLOps.
typescript - This article is part of a series.
Part 26: This Article
In JavaScript, you access object properties using bracket notation (obj["key"]). In TypeScript, Indexed Access Types (T[K]) allow you to look up the exact type of a property, array element, or tuple index using the exact same bracket syntax at the type level.

1. The T[K] Lookup Syntax
#

Instead of manually duplicating nested type definitions, you can look up a property type directly from an existing interface:

interface UserAccount {
  id: string;
  profile: {
    displayName: string;
    avatarUrl: string;
    bio: string;
  };
  settings: {
    theme: "light" | "dark" | "system";
    notificationsEnabled: boolean;
  };
}

// 🟢 Extract the 'profile' property type directly:
type UserProfile = UserAccount["profile"];
/* Inferred Type:
{
  displayName: string;
  avatarUrl: string;
  bio: string;
}
*/

// 🟢 Extract the 'theme' property type directly (Nested Indexing):
type ThemeSetting = UserAccount["settings"]["theme"];
// Inferred Type: "light" | "dark" | "system"

The Index Constraint Rule (K extends keyof T)
#

The key used inside the bracket index must be a valid property key of the target type. If you attempt to index with a key that does not exist on T, TypeScript throws a compile-time error:

// ❌ Compiler Error: Property 'password' does not exist on type 'UserAccount'.
// type PasswordType = UserAccount["password"];

2. Union Indexing (T["a" | "b"])
#

You can pass a Union of Keys as the index parameter to extract a Union of Property Types simultaneously:

interface CustomerPayload {
  id: string;
  age: number;
  isVIP: boolean;
  registeredAt: Date;
}

// Look up types for 'id' AND 'age' simultaneously:
type IdOrAge = CustomerPayload["id" | "age"]; 
// Inferred Type: string | number

// Look up types for ALL properties by passing 'keyof CustomerPayload':
type AllCustomerValues = CustomerPayload[keyof CustomerPayload];
// Inferred Type: string | number | boolean | Date

3. Array Element Indexing (ArrayType[number])
#

To extract the type of elements stored inside an array or list, index the array type using the JavaScript keyword number:

interface Order {
  orderId: string;
  items: Array<{
    sku: string;
    quantity: number;
    price: number;
  }>;
}

// 🟢 Extract the type of a SINGLE item inside the 'items' array:
type OrderItem = Order["items"][number];
/* Inferred Type:
{
  sku: string;
  quantity: number;
  price: number;
}
*/

How ArrayType[number] Works
#

Because array elements are accessed via numeric indices (arr[0], arr[1], …), passing the type number as the index tells TypeScript: “Look up the union of all types accessible via any numeric index.”


4. Tuple Indexing (Tuple[0], Tuple[number])
#

Tuples support both specific numeric index lookups and union element lookups:

type HttpResponse = [statusCode: number, message: string, headers: Record<string, string>];

// 1. Look up element at index 0:
type StatusCode = HttpResponse[0]; // Inferred Type: number

// 2. Look up element at index 1:
type Message = HttpResponse[1]; // Inferred Type: string

// 3. Look up ALL possible tuple element types:
type TupleElements = HttpResponse[number]; 
// Inferred Type: number | string | Record<string, string>

5. Architectural Pattern: Single Source of Truth (DRY Types)
#

Indexed access types eliminate redundant interface declarations and ensure that updating a core domain model automatically cascades type updates across all dependent utility functions.

// 1. Core Domain API Contract (Single Source of Truth)
export interface EventPayload {
  eventId: string;
  metadata: {
    ip: string;
    userAgent: string;
  };
  payload: {
    action: "CLICK" | "SCROLL" | "SUBMIT";
    timestamp: number;
  };
}

// 2. Utility functions extract EXACT sub-types without copy-pasting definitions!
function logMetadata(meta: EventPayload["metadata"]) {
  console.log(`[IP]: ${meta.ip}, [UA]: ${meta.userAgent}`);
}

function processAction(action: EventPayload["payload"]["action"]) {
  console.log(`Executing Action: ${action}`);
}

If the core EventPayload interface is later updated (e.g. adding "HOVER" to action), processAction() updates its parameter type automatically without manual refactoring!


Summary & Next Steps
#

In this episode:

  • We learned the syntax and constraint rules of Indexed Access Types (T[K]).
  • We performed nested lookups (T["a"]["b"]) and union lookups (T["a" | "b"]).
  • We extracted array element types using ArrayType[number].
  • We indexed tuples using specific numeric literals (Tuple[0]) and Tuple[number].
  • We built a DRY domain model architecture using single-source-of-truth indexing.

In Episode 27: The keyof Operator, we will learn how to extract object keys as string literal unions!

typescript - This article is part of a series.
Part 26: This Article