Skip to content

Type Parameters (Generics)

  • Selector: typeParameter
  • Required: PascalCase
  • Custom: Must match one of the allowed patterns for generic type parameters

Why This Rule

Generic type parameters in TypeScript use specific conventions that differ from regular types:

  • Single uppercase letters (T, U, V, K): Most common for general-purpose type parameters
  • T/K/V prefix + descriptive name: Combines clarity with convention (TData, KKey, VValue)
  • Numeric subscripts (T0, T1, T2, T3): For sequences of related types

These conventions are:

  • Widely recognized: Established patterns across TypeScript, Java, C#, and other languages
  • Self-documenting: Instantly recognizable as generic placeholders
  • Concise: Short names reduce noise in generic-heavy code

Allowed Patterns

Single Letters

The four standard single-letter generics follow clear conventions:

  • T: Most common for a single generic type (Type)
  • U: Second generic type (alphabetical progression after T)
  • V: Third generic type (alphabetical progression after U)
  • K: Standard for keys in key-value pairs (e.g., Map<K, V>, Record<K, V>)

Descriptive with Prefix

For better clarity, prefix descriptive names with:

  • T prefix: TData, TItem, TElement, TError
  • K prefix: KKey (for keys)
  • V prefix: VValue (for values)

Numeric Subscripts

Use numeric subscripts for sequences of related types:

  • T0, T1 T2, T3, ...: Useful for tuples or multiple similar generic parameters
  • K1, K2: For multiple key types
  • V1, V2: For multiple value types

Note: The number must start with 1-9 (not 0), and can be followed by additional digits.

References

✅ Good

ts
// ✅ Single letter generics - T is most common
export function identity<T>(arg: T): T {
  return arg;
}

// ✅ Multiple generic types - T and U in alphabetical progression
export function map<T, U>(value: T, transform: (v: T) => U): U {
  return transform(value);
}

// ✅ Key-Value pairs - K and V are standard for dictionaries
export interface Dictionary<K, V> {
  get(key: K): V | undefined;
  set(key: K, value: V): void;
}

// ✅ Descriptive generic with T prefix - combines clarity with convention
export function mapArray<TItem, TResult>(
  items: TItem[],
  transform: (item: TItem) => TResult,
): TResult[] {
  return items.map(transform);
}

// ✅ Descriptive generic with T prefix - makes key/value types explicit
export type Cache<TKey, TValue> = Map<TKey, TValue>;

// ✅ Numeric subscript convention - useful for tuples
export type Tuple<T0, T1, T2, T3, T999> = [T0, T1, T2, T3, T999];

// ✅ Complex example combining all conventions
export class GenericContainer<T, TData, K1, K2> {
  private value: T;

  private data: TData;

  private keys: [K1, K2];

  constructor(value: T, data: TData, keys: [K1, K2]) {
    this.value = value;
    this.data = data;
    this.keys = keys;
  }
}

❌ Bad

ts
// ❌ Descriptive name without prefix - should use T or TType
function bad1<Type>(arg: Type): Type {
  return arg;
}

// ❌ Lowercase t prefix - generic type parameters must be uppercase
function bad2<tItem>(items: tItem[]): tItem[] {
  return items;
}

// ❌ No T prefix for descriptive names - should use TData
function bad3<Data>(data: Data): Data {
  return data;
}

// ❌ Redundant Type suffix - TItem is sufficient
function bad4<TItemType>(item: TItemType): TItemType {
  return item;
}

// ❌ Redundant prefix on K and V - should be TKey and TValue, not KKey and VVal
interface BadDict<KKey, VVal> {
  get(key: KKey): VVal;
}

// ❌ Non-standard single letters (lowercase) - must be uppercase
function bad5<a, b>(x: a, y: b): [a, b] {
  return [x, y];
}

// ❌ Snake case - should use PascalCase like TItem
function bad6<T_Item>(items: T_Item[]): T_Item[] {
  return items;
}

// ❌ camelCase - should use PascalCase like TData
function bad7<tData>(data: tData): tData {
  return data;
}