Question

TypeScript Indexed Access Types: Get a Value Type from a Key

Question

In TypeScript, how can you make a function accept a key of an object and ensure that the value parameter matches the type of that specific property?

For example:

type JWT = { id: string; token: string; expire: Date };

const obj: JWT = {
  id: 'abc123',
  token: 'tk01',
  expire: new Date(2018, 2, 14)
};

function print(key: keyof JWT) {
  switch (key) {
    case 'id':
    case 'token':
      console.log(obj[key].toUpperCase());
      break;
    case 'expire':
      console.log(obj[key].toISOString());
      break;
  }
}

function onChange(key: keyof JWT, value: any) {
  switch (key) {
    case 'id':
    case 'token':
      obj[key] = value + ' (assigned)';
      break;
    case 'expire':
      obj[key] = value;
      break;
  }
}

print('id');
print('expire');
onChange('id', 'def456');
onChange('expire', new Date(2018, 3, 14));
print('id');
print('expire');

onChange('expire', 1337); // this should fail at compile time
print('expire'); // otherwise it fails later at runtime

The goal is for the value parameter to depend on the key parameter, so that passing 'expire' requires a Date, while passing 'id' or 'token' requires a string.

What TypeScript feature should be used instead of any to express “the value type for the given key”?

Short Answer

By the end of this page, you will understand how to make one function parameter depend on another in TypeScript. You will learn how keyof, generics, and indexed access types like T[K] work together so that a key such as 'expire' automatically requires a Date, while 'id' requires a string.

Concept

TypeScript does not have a built-in keyword called valueof that mirrors keyof in the way beginners often expect. Instead, the usual solution is indexed access types.

If you have a type:

type JWT = {
  id: string;
  token: string;
  expire: Date;
};

then:

type ExpireType = JWT['expire']; // Date
type IdType = JWT['id']; // string

That is how you ask TypeScript for the type of a property value.

The important next step is making that depend on a function argument. For that, you use a generic type parameter:

function onChange<K extends keyof JWT>(key: K, value: JWT[K]) {
  obj[key] = value;
}

Here is what this means:

  • K extends keyof JWT means K can only be one of the keys of JWT
  • value: JWT[K] means the type of value is the property type for that exact key

So TypeScript can infer:

  • onChange('id', ...) requires a string
  • onChange('token', ...) requires a string
  • onChange('expire', ...) requires a Date

This matters because it moves bugs from runtime to compile time. Instead of accidentally storing the wrong kind of data and crashing later, TypeScript stops the mistake where it happens.

Mental Model

Think of an object type as a labeled cabinet:

  • keyof JWT gives you the list of drawer labels: id, token, expire
  • JWT[K] means: “open the drawer named K and tell me what kind of thing belongs inside”

So if the label is:

  • id → the drawer holds string
  • token → the drawer holds string
  • expire → the drawer holds Date

A generic function lets TypeScript remember which drawer label you passed in, so it can enforce the correct type for the value going into that drawer.

Take Quiz

Syntax and Examples

Core syntax

function update<K extends keyof T>(key: K, value: T[K]) {
  // ...
}

This pattern means:

  • key must be a valid key from T
  • value must match the type of that key

Example with JWT

type JWT = {
  id: string;
  token: string;
  expire: Date;
};

const obj: JWT = {
  id: 'abc123',
  token: 'tk01',
  expire: new Date(2018, 2, 14)
};

function onChange<K extends keyof JWT>(key: K, value: JWT[K]) {
  obj[key] = value;
}

onChange('id', 'def456');
onChange('token', 'tk02');
onChange('expire', new Date());

// Error: number is not assignable to Date
onChange('expire', 1337);

If you want extra processing per key

If different keys need different logic, you can still use the same generic signature:

function onChange<K extends keyof JWT>(key: K, value: JWT[K]) {
  switch (key) {
    case 'id':
    case 'token':
      obj[key] = `${value} (assigned)` as JWT[K];
      break;
    case 'expire':
      obj[key] = value;
      break;
  }
}

Because id and token are strings, adding text makes sense there. The type assertion is needed in this version because TypeScript does not fully narrow JWT[K] inside that switch the way beginners often expect.

Reading a value type directly

type ValueOfExpire = JWT['expire']; // Date
type ValueOfId = JWT['id']; // string
type AnyJWTValue = JWT[keyof JWT]; // string | Date

JWT[keyof JWT] gives the union of all property value types.

Step by Step Execution

Example

type JWT = {
  id: string;
  token: string;
  expire: Date;
};

const obj: JWT = {
  id: 'abc123',
  token: 'tk01',
  expire: new Date('2018-03-14')
};

function onChange<K extends keyof JWT>(key: K, value: JWT[K]) {
  obj[key] = value;
}

onChange('id', 'def456');
onChange('expire', new Date('2018-04-14'));

What happens step by step

Call 1

onChange('id', 'def456');

TypeScript infers:

K = 'id'

So the function becomes, conceptually:

function onChange(key: 'id', value: JWT['id'])

And since JWT['id'] is string, this is valid.

Call 2

onChange('expire', new Date('2018-04-14'));

TypeScript infers:

K = 'expire'

So the value type becomes:

JWT['expire'] // Date

Passing a Date is valid.

Invalid call

onChange('expire', 1337);

Again TypeScript infers:

K = 'expire'

So it expects:

value: Date

But 1337 is a number, so TypeScript reports an error before the code runs.

That is the main benefit: the type of value changes automatically based on the key you pass.

Real World Use Cases

Updating form state

When building forms, different fields have different types:

type FormState = {
  name: string;
  age: number;
  subscribed: boolean;
};

A typed update function can enforce correct values for each field.

Updating configuration objects

Application config often mixes strings, numbers, and booleans. Using T[K] prevents assigning the wrong type to a setting.

Working with API models

If you receive structured data from an API, typed key/value updates help keep model objects valid.

Generic utility functions

Libraries and shared utilities often need to read or write object properties safely without losing type information.

State management

In React, Vue, or other UI code, this pattern is useful when updating state by field name while keeping compile-time type safety.

Take Quiz

Real Codebase Usage

In real projects, developers commonly combine indexed access types with a few patterns.

Generic property setters

function setProp<T, K extends keyof T>(obj: T, key: K, value: T[K]) {
  obj[key] = value;
}

This works for many object types, not just JWT.

Validation before assignment

Sometimes the value is unknown at first, then checked before storing:

function updateExpire(value: unknown) {
  if (!(value instanceof Date)) {
    throw new Error('expire must be a Date');
  }

  obj.expire = value;
}

Static types help at compile time, and runtime validation helps when data comes from outside TypeScript.

Guard clauses

Developers often split logic for readability:

function print(key: keyof JWT) {
  if (key === 'expire') {
    console.log(obj[key].toISOString());
    return;
  }

  console.log(obj[key].toUpperCase());
}

Reusable ValueOf helper

Some codebases define a utility type:

type ValueOf<T> = T[keyof T];

This does not mean “value for a specific key”. It means “union of all possible property values”. For JWT, that would be:

type JWTValue = ValueOf<JWT>; // string | Date

That is useful in some places, but not enough for onChange, because onChange needs the value type for the specific key, not for every key at once.

Common Mistakes

Using any

function onChange(key: keyof JWT, value: any) {
  obj[key] = value;
}

Why it is a problem

any disables type safety. TypeScript will allow wrong assignments like:

onChange('expire', 1337);

Fix

Use a generic and indexed access type:

function onChange<K extends keyof JWT>(key: K, value: JWT[K]) {
  obj[key] = value;
}

Using JWT[keyof JWT] for the value

function onChange(key: keyof JWT, value: JWT[keyof JWT]) {
  obj[key] = value;
}

Why it is wrong here

JWT[keyof JWT] becomes:

string | Date

That means TypeScript allows any string or any Date for any key. So this would still compile:

onChange('expire', 'not a date');

Fix

Use JWT[K], not JWT[keyof JWT].

Expecting switch cases to fully narrow generic property types

function onChange<K extends keyof JWT>(key: K, value: JWT[K]) {
  switch (key) {
    case 'id':
      obj[key] = value + 'x';
  }
}

Why it can be confusing

Even inside case 'id', TypeScript may not narrow value exactly the way you expect when K is generic.

Fix

Use simpler direct assignment when possible, or split logic into separate functions.

Confusing a custom ValueOf<T> helper with key-based lookup

type ValueOf<T> = T[keyof T];

This utility is real and useful, but it means “any property value from T”, not “the value type for the key passed into this function”.

Forgetting runtime validation for external input

TypeScript only checks code at compile time. If values come from JSON, user input, or APIs, they may still be wrong at runtime. Validate external data before storing it.

Comparisons

Related type patterns

PatternMeaningExample result with JWTBest use
keyof JWTUnion of keys`'id''token'
JWT['id']Value type for one known keystringGet one property type
JWT[K]Value type for a generic keydepends on KFunctions where value depends on key
JWT[keyof JWT]Union of all value types`stringDate`
anyTurns off checkinganythingRarely, only when unavoidable
unknownSafe unknown valueunknownExternal input that must be checked

any vs unknown vs T[K]

TypeSafe?Keeps relation between key and value?Use case
anyNoNoAvoid when possible
unknownSafer than anyNoData from outside your program
T[K]YesYesTyped property access and updates

ValueOf<T> vs indexed access with a specific key

PatternMeaning
type ValueOf<T> = T[keyof T]Any possible value type from T
T[K]The exact value type for the chosen key K

For this problem, T[K] is the correct tool.

Take Quiz

Cheat Sheet

// Keys of an object type
keyof T

// Value type of one property
T['name']

// Value type for a generic key
T[K]

// Generic key/value function
function setProp<T, K extends keyof T>(obj: T, key: K, value: T[K]) {
  obj[key] = value;
}

// Union of all value types
type ValueOf<T> = T[keyof T];

Rules to remember

  • keyof T gives property names
  • T[K] gives the value type for key K
  • Use K extends keyof T to restrict valid keys
  • Use value: T[K] when the value type depends on the key
  • T[keyof T] is a union of all value types, not the exact value for one key
  • Avoid any if you want compile-time safety

Example

function onChange<K extends keyof JWT>(key: K, value: JWT[K]) {
  obj[key] = value;
}

Common edge case

Inside a switch, generic types may not narrow as much as you expect. If needed:

  • split logic into separate functions
  • assign directly when possible
  • use a type assertion only when you are sure it is correct

FAQ

Is there a built-in valueof in TypeScript?

No. The usual equivalent idea is an indexed access type such as T[K] or T[keyof T].

How do I get the type of a property by key in TypeScript?

Use indexed access syntax:

type NameType = User['name'];

How do I make one function parameter depend on another in TypeScript?

Use a generic key parameter:

function f<K extends keyof T>(key: K, value: T[K]) {}

What is the difference between T[K] and T[keyof T]?

T[K] is the value type for one specific key. T[keyof T] is the union of all property value types.

Why does any cause problems here?

Because any disables type checking, so TypeScript cannot stop invalid assignments like putting a number into a Date property.

Can I define my own ValueOf<T> type?

Yes:

type ValueOf<T> = T[keyof T];

But remember that this gives a union of all property values, not the exact value type for a selected key.

Does TypeScript still need runtime validation?

Yes. TypeScript only checks your code during development. Data from APIs, forms, or JSON may still need runtime checks.

Take Quiz

Mini Project

Description

Build a type-safe profile updater for a user settings object. The goal is to practice linking a property key to the correct value type so that invalid updates are rejected at compile time.

Goal

Create a reusable update function that only accepts values matching the selected object key.

Requirements

  • Define a UserSettings type with at least one string, one number, and one boolean property.
  • Create a settings object that matches the type.
  • Write a generic updateSetting function using K extends keyof UserSettings and UserSettings[K].
  • Demonstrate at least three valid updates.
  • Include one invalid update that TypeScript should reject.
Take Quiz

Keep learning

Related questions

View more