CtrlK
BlogDocsLog inGet started
Tessl Logo

mcollina/typescript-magician

Designs complex generic types, refactors `any` types to strict alternatives, creates type guards and utility types, and resolves TypeScript compiler errors. Use when the user asks about TypeScript (TS) types, generics, type inference, type guards, removing `any` types, strict typing, type errors, `infer`, `extends`, conditional types, mapped types, template literal types, branded/opaque types, or utility types like `Partial`, `Record`, `ReturnType`, and `Awaited`.

97

Quality

97%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

generics-basics.mdrules/

name:
generics-basics
description:
Fundamentals of generic types and functions in TypeScript
metadata:
{"tags":"generics, type-parameters, constraints, inference"}

Generics Fundamentals

Overview

Generics allow you to create reusable components that work with multiple types while maintaining type safety. They're essential for building flexible, type-safe APIs.

Basic Generic Functions

// Without generics - loses type information
function identity(value: any): any {
  return value;
}

// With generics - preserves type
function identity<T>(value: T): T {
  return value;
}

const num = identity(42); // Type: number (inferred)
const str = identity("hello"); // Type: string (inferred)
const explicit = identity<boolean>(true); // Type: boolean (explicit)

Inference Dependencies

When the type of one parameter depends on another, use generics:

// The return type depends on what keys exist in the config
const createComponent = <TConfig extends Record<string, string>>(
  config: TConfig,
) => {
  return (variant: keyof TConfig, ...otherClasses: string[]): string => {
    return config[variant] + " " + otherClasses.join(" ");
  };
};

// TConfig is inferred as { primary: string; secondary: string }
const getButtonClasses = createComponent({
  primary: "bg-blue-300",
  secondary: "bg-green-300",
});

// variant must be "primary" | "secondary"
getButtonClasses("primary", "px-4"); // OK
getButtonClasses("tertiary", "px-4"); // Error: "tertiary" not in keys

Generic Constraints with extends

Constrain generics to ensure they have required properties:

// Unconstrained - TFunc could be anything
type WrapFunction<TFunc> = (...args: any[]) => any;

// Constrained - TFunc must be a function
type WrapFunction<TFunc extends (...args: any) => any> = (
  ...args: Parameters<TFunc>
) => ReturnType<TFunc>;

Why Constraints Matter

// Without constraint
function getLength<T>(item: T): number {
  return item.length; // Error: Property 'length' does not exist on type 'T'
}

// With constraint
function getLength<T extends { length: number }>(item: T): number {
  return item.length; // OK - we know T has length
}

getLength("hello"); // 5
getLength([1, 2, 3]); // 3
getLength({ length: 10 }); // 10
getLength(42); // Error: number doesn't have length

Default Generic Parameters

Provide defaults for optional type parameters:

type WrapFunction<
  TFunc extends (...args: any) => any,
  TAdditional = {} // Default to empty object
> = (
  ...args: Parameters<TFunc>
) => Promise<Awaited<ReturnType<TFunc>> & TAdditional>;

// Can use without TAdditional
type BasicWrapper = WrapFunction<typeof fetchUser>;

// Or with TAdditional
type ExtendedWrapper = WrapFunction<typeof fetchUser, { meta: string }>;

Generic Slot Inference

TypeScript infers generic types from usage:

// Generic is inferred from the argument passed
const createComponent = <TConfig>(config: TConfig) => {
  return config;
};

// TConfig is inferred as { primary: string; secondary: string }
const component = createComponent({
  primary: "bg-blue-300",
  secondary: "bg-green-300",
});

When Inference Doesn't Work

If you don't USE the generic in arguments, it defaults to unknown:

// BAD - TConfig isn't used in arguments, defaults to unknown
const createComponent = <TConfig>(config: Record<string, string>) => {
  // TConfig is unknown here
};

// GOOD - TConfig IS the argument type
const createComponent = <TConfig extends Record<string, string>>(
  config: TConfig,
) => {
  // TConfig is inferred from what's passed
};

Multiple Generic Parameters

Use multiple parameters for related but distinct types:

function map<TInput, TOutput>(
  items: TInput[],
  transform: (item: TInput) => TOutput
): TOutput[] {
  return items.map(transform);
}

// Both TInput and TOutput are inferred
const numbers = map(["1", "2", "3"], (s) => parseInt(s));
// TInput: string, TOutput: number, Result: number[]

Pattern: keyof with Generics

Combine keyof with generics for type-safe property access:

function getProperty<TObj, TKey extends keyof TObj>(
  obj: TObj,
  key: TKey
): TObj[TKey] {
  return obj[key];
}

const user = { name: "Alice", age: 30 };
const name = getProperty(user, "name"); // Type: string
const age = getProperty(user, "age"); // Type: number
const invalid = getProperty(user, "email"); // Error: "email" not in keyof

Generics in Classes

class Container<T> {
  private value: T;

  constructor(value: T) {
    this.value = value;
  }

  getValue(): T {
    return this.value;
  }

  map<U>(transform: (value: T) => U): Container<U> {
    return new Container(transform(this.value));
  }
}

const numContainer = new Container(42);
const strContainer = numContainer.map((n) => n.toString());
// strContainer is Container<string>

Complete Example: Component Factory

// A factory that creates type-safe component class generators
export const createComponent = <TConfig extends Record<string, string>>(
  config: TConfig,
) => {
  // Return a function that requires valid variant keys
  return (variant: keyof TConfig, ...otherClasses: string[]): string => {
    return config[variant] + " " + otherClasses.join(" ");
  };
};

// Usage
const getButtonClasses = createComponent({
  primary: "bg-blue-500 text-white",
  secondary: "bg-gray-200 text-gray-800",
  danger: "bg-red-500 text-white",
});

// Type-safe: variant must be "primary" | "secondary" | "danger"
const classes = getButtonClasses("primary", "px-4", "py-2");
// Result: "bg-blue-500 text-white px-4 py-2"

// Type error on invalid variant
getButtonClasses("invalid"); // Error!

When to Use Generics

  • Type preservation: When you need to preserve type information through a function
  • Inference dependencies: When one type depends on another
  • Reusable components: When building APIs that work with multiple types
  • Constraints: When you need to ensure types have certain properties
  • Factory functions: When creating functions that return typed results

Common Pitfalls

Unnecessary Generics

// BAD - generic provides no value
function greet<T extends string>(name: T): string {
  return `Hello, ${name}`;
}

// GOOD - just use string
function greet(name: string): string {
  return `Hello, ${name}`;
}

Over-constraining

// BAD - overly specific constraint
function process<T extends { id: string; name: string; email: string }>(
  obj: T
): void {}

// GOOD - only require what you actually use
function process<T extends { id: string }>(obj: T): void {}

Forgetting to Constrain

// BAD - accessing property that might not exist
function getName<T>(obj: T): string {
  return obj.name; // Error: Property 'name' does not exist
}

// GOOD - constrain to types that have name
function getName<T extends { name: string }>(obj: T): string {
  return obj.name;
}

rules

array-index-access.md

as-const-typeof.md

builder-pattern.md

conditional-types.md

deep-inference.md

error-diagnosis.md

function-overloads.md

generics-basics.md

infer-keyword.md

mapped-types.md

opaque-types.md

template-literal-types.md

type-narrowing.md

utility-types.md

SKILL.md

tile.json