JS Guide
HomeQuestionsSearchResources
Search

Built for developers preparing for JavaScript, React & TypeScript interviews.

ResourcesQuestionsSupport
HomeQuestionsSearchProgress
HomeQuestionstypescript
PrevNext
typescript
senior
advanced

How do you handle complex generic constraints and variance in TypeScript?

generics
variance
constraints
covariant
contravariant
advanced
Quick Answer

Complex constraints use multiple extends, conditional types, and recursive patterns. Variance determines how type parameters relate in subtyping: covariant (out), contravariant (in), invariant (both), and bivariant (neither). Understanding variance helps design safe generic APIs.

Detailed Explanation

Complex Constraints:

  • Multiple constraints: T extends A & B
  • Conditional constraints: T extends C ? D : E
  • Self-referential: T extends { prop: T }
  • Higher-kinded pattern emulation

Variance:

  • Covariant (out): T in output position, subtype preserving
  • Contravariant (in): T in input position, subtype reversing
  • Invariant: T in both positions, exact match required
  • Bivariant: Functions in strict mode (parameters contravariant)

Practical Implications:

  • Array<T> is covariant (can assign Dog[] to Animal[])
  • Function parameters are contravariant
  • Affects assignability of generic types

Code Examples

Complex generic constraints
// Multiple constraints
interface HasId { id: number; }
interface HasName { name: string; }

function merge<T extends HasId & HasName>(item: T) {
  return { ...item, displayName: `${item.name} (${item.id})` };
}

// Constraint referencing other type parameter
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

// Conditional type in constraint
type ArrayElement<T> = T extends Array<infer E> ? E : never;

function first<T extends any[]>(arr: T): ArrayElement<T> {
  return arr[0];
}

const n = first([1, 2, 3]); // number
const s = first(['a', 'b']); // string

// Recursive constraint (JSON-like values)
type JSONValue = 
  | string 
  | number 
  | boolean 
  | null 
  | JSONValue[] 
  | { [key: string]: JSONValue };

function toJSON<T extends JSONValue>(value: T): string {
  return JSON.stringify(value);
}

Resources

TypeScript - Generics

docs

Covariance and Contravariance in TypeScript

article

Related Questions

How do conditional types work in TypeScript?

senior
conditional-types

How do mapped types work in TypeScript?

senior
mapped-types
Previous
What are template literal types and how do you use them?
Next
How do you write declaration files for JavaScript libraries?