Catalypt LogoCatalypt.ai

Industry Focus

Developer Options

Resources

Back to Blog

AI's TypeScript Types: From Too Simple to Incomprehensible

December 1, 2023 Josh Butler Technical

"Add TypeScript types to this code." Simple request. The AI either responds with `any` everywhere or creates a type system so complex that even the TypeScript compiler gives up and goes home.

There is no middle ground. Welcome to AI TypeScript gymnastics.

The Two Extremes

Extreme 1: The Any Apocalypse

// Original JavaScript
function processUser(user) {
  return {
    name: user.name.toUpperCase(),
    age: user.age + 1,
    email: user.email.toLowerCase()
  };
}

// AI's "TypeScript" version
function processUser(user: any): any {
  return {
    name: user.name.toUpperCase(),
    age: user.age + 1,
    email: user.email.toLowerCase()
  };
}

// Thanks, very helpful

Extreme 2: The Type Olympian

// Same function, different AI mood
type Uppercase<S extends string> = S extends `${infer F}${infer R}` 
  ? `${Uppercase<F>}${Uppercase<R>}` 
  : S extends 'a' ? 'A' 
  : S extends 'b' ? 'B'
  // ... 24 more letters ...
  : S;

type IncrementAge<T extends number> = T extends T 
  ? number 
  : never;

type ProcessedUser<T extends User> = {
  name: Uppercase<T['name']>;
  age: IncrementAge<T['age']>;
  email: Lowercase<T['email']>;
};

function processUser<T extends User>(user: T): ProcessedUser<T> {
  // Type gymnastics continue...
}

// For uppercasing a string...

The Utility Type Abuse

// Simple requirement: Make all fields optional except 'id'
interface User {
  id: string;
  name: string;
  email: string;
  age: number;
}

// AI's solution
type PartialExceptId<T extends { id: any }> = 
  Partial<Omit<T, 'id'>> & Pick<T, 'id'>;

type DeepPartialExceptId<T> = T extends object
  ? T extends { id: any }
    ? Partial<Omit<T, 'id'>> & Pick<T, 'id'> & {
        [K in keyof Omit<T, 'id'>]: DeepPartialExceptId<T[K]>
      }
    : {
        [K in keyof T]: DeepPartialExceptId<T[K]>
      }
  : T;

// Or you could just write:
type UserUpdate = Partial<User> & { id: string };

The Conditional Type Circus

// Task: Type for a function that returns string or number
// Normal solution:
type SimpleReturn = string | number;

// AI's solution:
type ComplexReturn<T> = T extends string 
  ? string 
  : T extends number 
    ? number 
    : T extends boolean
      ? never
      : T extends object
        ? T extends any[]
          ? never
          : T extends Function
            ? never
            : never
        : never;

// Why.

The Generic Inception

// AI creating generics within generics within generics
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object 
    ? T[P] extends Function
      ? T[P]
      : T[P] extends any[]
        ? DeepReadonlyArray<T[P]>
        : DeepReadonly<T[P]>
    : T[P];
};

interface DeepReadonlyArray<T> extends ReadonlyArray<
  T extends object ? DeepReadonly<T> : T
> {}

// Used for: Making a config object readonly
// Could have been: Readonly<Config>

The Real-World Disasters

The API Response Type

// What we needed
interface ApiResponse<T> {
  data: T;
  error: string | null;
}

// What AI delivered
type ApiResponse<
  T extends Record<string, unknown>,
  E extends Error | null = null,
  M extends Record<string, any> = {},
  S extends number = 200
> = S extends 200
  ? { 
      data: T; 
      error: null; 
      metadata: M; 
      status: S;
    }
  : S extends 400
    ? { 
        data: null; 
        error: E; 
        metadata: M; 
        status: S;
      }
    : never;

// Now every API call needs 4 type parameters

The Form Validation Type

// Requirement: Type for form with validation
// AI's masterpiece:
type ValidatedForm<T extends Record<string, any>> = {
  [K in keyof T]: {
    value: T[K];
    error: T[K] extends string
      ? string | null
      : T[K] extends number
        ? 'Must be a number' | null
        : T[K] extends boolean
          ? never
          : 'Invalid type';
    touched: boolean;
    validators: Array<(value: T[K]) => string | null>;
  }
} & {
  isValid: () => boolean;
  errors: () => Partial<Record<keyof T, string>>;
};

// Actual usage: pain

The Mapped Type Madness

// Simple need: Stringify all properties
type AllStrings<T> = {
  [K in keyof T]: string;
};

// AI's interpretation:
type DeepStringify<T> = T extends string
  ? string
  : T extends number
  ? `${T}`
  : T extends boolean
  ? `${T}`
  : T extends null
  ? 'null'
  : T extends undefined
  ? 'undefined'
  : T extends any[]
  ? `[${DeepStringifyArray<T>}]`
  : T extends object
  ? `{${DeepStringifyObject<T>}}`
  : string;

// It's trying to create a type-level JSON.stringify

The Type Guard Gymnastics

// Need: Check if value is string
function isString(value: unknown): value is string {
  return typeof value === 'string';
}

// AI's version:
type IsString<T> = T extends string ? true : false;

function isString<T>(
  value: T
): value is T extends string ? T : never {
  return (
    typeof value === 'string' &&
    value !== null &&
    value !== undefined &&
    value.constructor === String &&
    Object.prototype.toString.call(value) === '[object String]'
  );
}

// It's just checking if it's a string!

The Balance: What Actually Works

// Good TypeScript: Clear, useful, maintainable

interface User {
  id: string;
  name: string;
  email: string;
  createdAt: Date;
}

type UserUpdate = Partial<Omit<User, 'id' | 'createdAt'>>;

function updateUser(id: string, updates: UserUpdate): Promise<User> {
  // Simple, clear types that help
}

// Not this:
type UserUpdate<T extends User = User> = {
  [K in keyof T as T[K] extends Function ? never : K]?: 
    T[K] extends object ? DeepPartial<T[K]> : T[K]
};

AI TypeScript Rules

  1. Start simple - You can always add complexity
  2. Avoid type gymnastics - If it takes 5 minutes to understand, it's too complex
  3. Use built-in utility types - Partial, Pick, Omit are your friends
  4. Type what matters - Not everything needs generics
  5. Remember the human - Someone has to maintain this

The Prompt That Works

"Add TypeScript types to this code.
Rules:
- Use simple, readable types
- Avoid unnecessary generics
- Use built-in utility types when possible
- Only use 'any' as last resort
- Keep type complexity proportional to value"

My Favorite AI Type

// AI was asked to type a toggle function
type Toggle<T extends boolean> = T extends true ? false : true;

type ToggleFunction = <T extends boolean>(value: T) => Toggle<T>;

const toggle: ToggleFunction = (value) => !value as any;

// Or you could just...
const toggle = (value: boolean): boolean => !value;

AI writing TypeScript types is like asking a mathematician to write a shopping list - you'll either get "buy stuff" or a formal proof of why you need exactly 2.347 bananas based on the Banach-Tarski paradox. The art is finding the sweet spot between type safety and type insanity. Remember: types should make your code easier to understand, not require a PhD to decipher. If your types have more angle brackets than a geometry textbook, you've gone too far.

Get Started