Deep in Software with Hugo

Subscribe
Archives
September 9, 2025

Advanced TypeScript #4: loops in types

Welcome to the 100th (yay! 🎉) Edition of the Code with Hugo newsletter.

Here’s part 4 of 7 of the Advanced TypeScript series, where I share TypeScript patterns I’ve encountered in enterprise grade applications.

We're looking at a very interesting pattern that I wasn't aware of until years into my TypeScript journey, where we'll run loops in the TypeScript type system.

4. Zipping a type to a union

TS has a sort of loop construct using NonNullable<{ [K in keyof Fields]: /* use K here */ }>[keyof FIELDS]. Which is useful for example if you want key-value pairwise types.

import { expectTypeOf } from 'expect-type';

interface Fields {
  amount: number;
  formattedAmount: string;
}
type FieldDefs = NonNullable<{
  [K in keyof Fields]: {
    name: K;
    value: Fields[K];
  };
}>[keyof Fields];

expectTypeOf<FieldDefs>().toEqualTypeOf<
  { name: 'amount'; value: number } | { name: 'formattedAmount'; value: string }
>();

Why is this useful? It keeps the type stricter, { name: 'amount', value: number } ensures the name value matches the value type.

A more naive implementation doesn't have this strictness on the union:

type FieldNames = keyof Fields;
type FieldValues = Fields[keyof Fields];

expectTypeOf<FieldNames>().toEqualTypeOf<'amount' | 'formattedAmount'>();
expectTypeOf<FieldValues>().toEqualTypeOf<number | string>();

Note the lack of relationship between amount <-> number and formattedAmount <-> string, this means the type is not as strict as it could be.

function setFieldNaive(name: FieldNames, value: FieldValues) {}
setFieldNaive('amount', '200'); // no error

function setFieldStrict(update: FieldDefs) {}
// @ts-expect-error
// Argument of type '{ name: "amount"; value: string; }' is not assignable to parameter of type 'FieldDefs'.
//  Types of property 'value' are incompatible.
//    Type 'string' is not assignable to type 'number'.
setFieldStrict({ name: 'amount', value: '200' });
// no error
setFieldStrict({ name: 'amount', value: 200 });

That's this week's pattern, you can get a sneak peek of the rest at codewithhugo.com/typescript-types-in-the-trenches/, or access the annotated source at github.com/HugoDF/real-world-ts.

Don't miss what's next. Subscribe to Deep in Software with Hugo:
GitHub Bluesky
Powered by Buttondown, the easiest way to start and grow your newsletter.