Loading...
Loading...
Use when writing type annotations on variables. Use when TypeScript can infer the type. Use when code feels cluttered with types.
npx skill4agent add marius-townhouse/effective-typescript-skills avoid-inferable-annotationsNEVER annotate types that TypeScript can correctly infer.// ❌ VIOLATION: Redundant annotations
let x: number = 12;
const name: string = 'Alice';
const numbers: number[] = [1, 2, 3];
// ✅ CORRECT: Let TypeScript infer
let x = 12;
const name = 'Alice';
const numbers = [1, 2, 3];// ❌ VERBOSE: Don't do this
const person: {
name: string;
born: {
where: string;
when: string;
};
} = {
name: 'Sojourner Truth',
born: {
where: 'Swartekill, NY',
when: 'c.1797',
},
};
// ✅ CONCISE: Just write the value
const person = {
name: 'Sojourner Truth',
born: {
where: 'Swartekill, NY',
when: 'c.1797',
},
};
// TypeScript infers the exact same type!// Parameters need types (TypeScript can't infer them)
function greet(name: string) {
return `Hello, ${name}`;
}
// Return type is inferred - no need to annotate
function square(x: number) {
return x * x; // TypeScript infers number
}// ❌ VERBOSE: Unnecessary parameter types
app.get('/health', (request: express.Request, response: express.Response) => {
response.send('OK');
});
// ✅ CONCISE: Let context provide the types
app.get('/health', (request, response) => {
response.send('OK'); // Types are inferred from context
});interface Product {
id: string;
name: string;
price: number;
}
// ✅ ANNOTATE: Enables excess property checking
const elmo: Product = {
id: '123',
name: 'Tickle Me Elmo',
price: 28.99,
inStock: true, // Error! 'inStock' does not exist in type 'Product'
};
// ❌ NO ANNOTATION: Error appears far from definition
const furby = {
id: 456, // Wrong type! But no error here...
name: 'Furby',
price: 35,
};
logProduct(furby); // Error appears here, far from the mistake
// ~~~~~ Types of property 'id' are incompatible// Without annotation, easy to miss inconsistencies
function getQuote(ticker: string) {
if (cache[ticker]) {
return cache[ticker]; // Returns number
}
return fetch(`/api/${ticker}`).then(r => r.json()); // Returns Promise<any>
}
// Inferred: number | Promise<any> - probably not what you wanted!
// With annotation, TypeScript catches the bug
function getQuote(ticker: string): Promise<number> {
if (cache[ticker]) {
return cache[ticker]; // Error: Type 'number' is not assignable to 'Promise<number>'
}
return fetch(`/api/${ticker}`).then(r => r.json());
}// For library functions, explicit types are documentation
export function calculateTotal(items: CartItem[]): number {
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}interface Vector2D { x: number; y: number; }
// Without annotation: { x: number; y: number; }
function add(a: Vector2D, b: Vector2D) {
return { x: a.x + b.x, y: a.y + b.y };
}
// With annotation: Vector2D (clearer, documented)
function add(a: Vector2D, b: Vector2D): Vector2D {
return { x: a.x + b.x, y: a.y + b.y };
}// ❌ TERRIBLE: Destructuring with types is ugly
function logProduct(product: Product) {
const {id, name, price}: {id: string; name: string; price: number} = product;
console.log(id, name, price);
}
// ✅ CLEAN: Just destructure
function logProduct(product: Product) {
const {id, name, price} = product;
console.log(id, name, price);
}: number: string| Excuse | Reality |
|---|---|
| "It's more readable" | Extra noise hurts readability. |
| "What if inference is wrong?" | Then add an annotation for that case. |
| "Code reviews need types" | Reviewers can hover in modern tools. |
| "Consistency" | Consistent non-redundancy is better. |
| Situation | Annotate? |
|---|---|
| Variable with literal value | No |
| Object literal for known type | Yes (excess checking) |
| Destructured variables | No |
| Function parameters | Yes |
| Function return (simple) | Usually no |
| Function return (complex/public) | Yes |
| Callback parameters | Usually no |