The Five Custom TS Utility Types I Add to Every Project

TypeScript ships great utility types but the five I miss in every fresh project are these: NonEmptyArray, Branded, Awaited inverse (Promisify), DeepReadonly, and ExactKeys. Each is one or two lines that has saved me from a bug.

TypeScript
Frontend
4 snippets
type-system
generics
code-template
tylerperry

By @tylerperry

March 21, 2026

·

Updated May 20, 2026

834 views

14

4.3 (14)

// NonEmptyArray<T>: a tuple-like type that guarantees at least one element
// at the type level. Lets you call arr[0] without `| undefined` in noUncheckedIndexedAccess.

type NonEmptyArray<T> = [T, ...T[]];

function firstWord(words: NonEmptyArray<string>): string {
    // No `?? throw` needed; the type guarantees words[0] is a string.
    return words[0];
}

// Branded<T, B>: nominal typing for primitives. Stops you from passing a
// raw string where a UserId is expected.

type Brand<T, B extends string> = T & { readonly __brand: B };
type UserId = Brand<string, 'UserId'>;
type OrgId = Brand<string, 'OrgId'>;

function asUserId(s: string): UserId { return s as UserId; }
function asOrgId(s: string): OrgId { return s as OrgId; }

function lookupUser(id: UserId) { return 'user:' + id; }

const u = asUserId('u_42');
const o = asOrgId('o_7');
console.log(lookupUser(u));
// lookupUser(o);            // would be a type error: OrgId is not UserId
// lookupUser('u_42');       // would be a type error: raw string is not UserId

// Demonstrate NonEmptyArray.
console.log(firstWord(['hello', 'world']));
// firstWord([]);            // would be a type error at compile time

NonEmptyArray is the tiny win that prevents the most common TypeScript bug I see in code review: indexing an array and getting T | undefined. By declaring at the type level that the array has at least one element, downstream code can use arr[0] without a guard. Branded<T, B> solves the opposite-shape problem: the compiler treats string as a universal currency for ids, but in practice mixing up a UserId and an OrgId is exactly the kind of bug that ships to production. The phantom __brand field is erased at runtime, so the cost is zero, and the compiler treats UserId and OrgId as incompatible.