AI's TypeScript Types: From Too Simple to Incomprehensible
"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
- Start simple - You can always add complexity
- Avoid type gymnastics - If it takes 5 minutes to understand, it's too complex
- Use built-in utility types - Partial, Pick, Omit are your friends
- Type what matters - Not everything needs generics
- 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.