TypeScript Advanced Patterns: Generics, Types as Tests

TypeScript fluency is now baseline for senior frontend roles. Beyond the basics (interfaces, type aliases, basic generics), interviews probe whether you understand the type system as a small but capable functional language — capable of expressing complex constraints and catching bugs at compile time.

Generics deeper

Generic constraints with extends:

function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
  // ...
}

The constraint K extends keyof T means K must be a key of T. Misuse is caught at the call site.

Conditional types

The basic form:

type IsString<T> = T extends string ? true : false;

More useful: distribute over unions.

type ToArray<T> = T extends any ? T[] : never;
type R = ToArray<string | number>; // string[] | number[]

Mapped types

Transform every property:

type Readonly<T> = { readonly [K in keyof T]: T[K] };
type Partial<T> = { [K in keyof T]?: T[K] };

These are built-in but illustrate the pattern. Combine with conditional types for sophisticated transforms.

Template literal types

String types with template-like composition:

type EventName<T extends string> = `on${Capitalize<T>}`;
type R = EventName<'click'>; // 'onClick'

Used in libraries for inferring types from string keys (Tailwind classes, route paths, etc.).

Inferring with infer

Extract a type from another:

type ReturnType<T> = T extends (...args: any) => infer R ? R : never;
type R = ReturnType<() => string>; // string

Powerful for library types. Used in React for inferring component props, in Zod for inferring schemas.

Discriminated unions

One of the most-used patterns:

type Result<T> = { ok: true; value: T } | { ok: false; error: string };

Switching on the discriminator narrows the type:

if (result.ok) {
  console.log(result.value); // type narrowed to T
} else {
  console.log(result.error); // type narrowed to string
}

Branded types

Distinguish between types that share a structure:

type UserId = string & { __brand: 'UserId' };
type CompanyId = string & { __brand: 'CompanyId' };

function getUser(id: UserId) { /* ... */ }
const cid = "abc" as CompanyId;
getUser(cid); // type error — cannot pass CompanyId where UserId is expected

Useful for IDs, monetary amounts, units.

Const assertions

as const makes the type as narrow as possible:

const colors = ['red', 'green', 'blue'] as const;
type Color = typeof colors[number]; // 'red' | 'green' | 'blue'

Critical for deriving types from runtime values.

Types as tests

For library authors, type-level tests catch regressions:

type Expect<T extends true> = T;
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends
  (<T>() => T extends Y ? 1 : 2) ? true : false;

type _ = Expect<Equal<ReturnType<() => string>, string>>;

If the assertion fails, TypeScript errors at compile time.

Common pitfalls

  • any as escape hatch — defeats the purpose
  • as casts to bypass type errors — usually a bug masking
  • Excessive optionality (?) when properties are actually required
  • Using {} as “any object” — actually means non-null
  • Object index types like [k: string]: any — loses type safety

Strict mode

Enable in tsconfig:

{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true
  }
}

Strict mode + the additional flags catches more bugs. Mandatory for serious code.

The Zod pattern

Zod schemas double as runtime validators and types:

const Schema = z.object({ email: z.string().email(), age: z.number() });
type Data = z.infer<typeof Schema>;

One source of truth for runtime + compile-time. Used everywhere in 2026.

Frequently Asked Questions

Should I use TypeScript for small projects?

For projects above a single file, yes. The friction is minimal; the benefits compound.

How do I handle third-party libraries without types?

Look for @types/ definitions on DefinitelyTyped. If none exist, write a small declaration file or contribute to the library.

Are template literal types overkill?

For most app code: yes. For library code (especially type-safe APIs and DSLs): often essential.

Scroll to Top