| name | avoid-anecdotal-types |
| description | Use when creating types from example data. Use when types don't match all cases. Use when API responses vary. |
Avoid Types Based on Anecdotal Data
Overview
Don't infer types from a single example.
If you create a type based on one API response or one sample, you'll miss edge cases. Get authoritative type definitions or examine multiple examples to understand the full shape of your data.
When to Use This Skill
- Creating types for external APIs
- Typing data from databases
- Working with third-party services
- Debugging "impossible" type errors
The Iron Rule
Types should be based on specifications, not samples.
One example doesn't show all possibilities.
Remember:
- APIs have optional fields, variants, edge cases
- Sample data shows the common case, not all cases
- "Works with my data" ≠ "Type is correct"
- Get the spec, not just an example
Detection: Example-Based Types
interface GeocodingResult {
lat: number;
lng: number;
address: string;
city: string;
country: string;
}
Sources of Truth
1. Official Documentation/Spec
interface GeocodingResult {
lat: number;
lng: number;
formatted_address: string;
address_components: AddressComponent[];
partial_match?: boolean;
}
2. TypeScript/JSON Schema Definitions
import { GeocodingResult } from '@google/maps-types';
import Ajv from 'ajv';
const validate = ajv.compile(geocodingSchema);
3. Generated Types from OpenAPI/GraphQL
import { GeocodingResponse } from './generated/api-types';
Real Example: GitHub API
interface GitHubUser {
id: number;
login: string;
name: string;
email: string;
company: string;
bio: string;
}
interface GitHubUser {
id: number;
login: string;
name: string | null;
email: string | null;
company: string | null;
bio: string | null;
}
One example user had all fields filled in. Many don't.
The JSON.parse Problem
const response = await fetch('/api/user');
const user: User = await response.json();
Runtime Validation
Use runtime validation to ensure data matches your types:
import { z } from 'zod';
const UserSchema = z.object({
id: z.number(),
name: z.string().nullable(),
email: z.string().email().nullable(),
});
type User = z.infer<typeof UserSchema>;
async function fetchUser(id: string): Promise<User> {
const response = await fetch(`/api/user/${id}`);
const data = await response.json();
return UserSchema.parse(data);
}
Now you'll catch mismatches at runtime, not in production bugs.
Multiple Examples
If you can't get a spec, examine multiple examples:
interface User {
id: number;
login: string;
name: string | null;
role?: 'user' | 'admin';
deletedAt?: Date;
}
Common Anecdotal Patterns
"My Data Has This Field"
const user = { name: "Alice", age: 30 };
interface User {
name: string;
age?: number;
}
"The API Always Returns..."
const response = { data: [...], total: 100 };
const emptyResponse = { data: [], total: 0 };
const notFound = { error: "Not found" };
"This Field is Always a Number"
{ id: 123 }
{ id: 456 }
{ id: "abc-123" }
Pressure Resistance Protocol
1. "The Example Works"
Pressure: "I tested it and it works fine"
Response: It works with your test data. What about edge cases?
Action: Get the spec or test with varied data.
2. "We Don't Have Documentation"
Pressure: "There's no API spec available"
Response: Examine multiple responses. Check error cases. Ask the API owners.
Action: Build defensive types with optional fields and validation.
Red Flags - STOP and Reconsider
- Types created from copying one JSON response
- All properties required when API might not always include them
- Missing error/empty state handling
- Types that only match "happy path" data
Common Rationalizations (All Invalid)
| Excuse | Reality |
|---|
| "It works in testing" | Testing shows one path, not all |
| "We control the API" | APIs change, have edge cases |
| "We'll fix it if it breaks" | Users hit edge cases first |
Quick Reference
interface User {
id: number;
name: string;
email: string;
}
interface User {
id: number;
name: string | null;
email?: string;
}
const UserSchema = z.object({
id: z.number(),
name: z.string().nullable(),
email: z.string().optional(),
});
import { User } from '@api/types';
The Bottom Line
Types based on samples will miss edge cases.
One API response or one database record doesn't show all possibilities. Use official documentation, generated types, or runtime validation. When creating types from examples, examine multiple cases including edge cases and errors.
Reference
Based on "Effective TypeScript" by Dan Vanderkam, Item 42: Avoid Types Based on Anecdotal Data.