What is YAGNI?
"Don't implement something until you actually need it."
YAGNI = You Aren't Gonna Need It
✓ Build only what's required now
✓ Avoid speculative features
✓ Reduce complexity
✓ Ship faster
Avoid Premature Abstraction
// ❌ Bad - over-engineered for "future" needs
interface DataSource {
connect(): void;
query(sql: string): any;
disconnect(): void;
}
class MySQLDataSource implements DataSource { /* ... */ }
class PostgresDataSource implements DataSource { /* ... */ }
class MongoDataSource implements DataSource { /* ... */ }
class OracleDataSource implements DataSource { /* ... */ }
class DataSourceFactory {
static create(type: string): DataSource {
// Complex factory for databases we don't use
}
}
// ✅ Good - only what we need now
class Database {
private connection: MySQLConnection;
query(sql: string) {
return this.connection.execute(sql);
}
}
// Add abstraction later IF we need multiple databases
Don't Add Unused Parameters
// ❌ Bad - parameters "we might need later"
function createUser(
name: string,
email: string,
phone?: string, // Not used yet
address?: string, // Not used yet
timezone?: string, // Not used yet
locale?: string, // Not used yet
preferences?: object // Not used yet
) {
return { name, email };
}
// ✅ Good - only required parameters
function createUser(name: string, email: string) {
return { name, email };
}
// Add parameters when features require them
Skip Speculative Features
// ❌ Bad - features nobody asked for
class ShoppingCart {
items: Item[] = [];
addItem(item: Item) { /* ... */ }
removeItem(id: string) { /* ... */ }
// "Might be useful someday"
saveForLater(id: string) { /* ... */ }
shareCart(email: string) { /* ... */ }
exportToPDF() { /* ... */ }
compareWithPreviousCart() { /* ... */ }
predictNextPurchase() { /* ... */ }
}
// ✅ Good - only requested features
class ShoppingCart {
items: Item[] = [];
addItem(item: Item) { /* ... */ }
removeItem(id: string) { /* ... */ }
getTotal(): number { /* ... */ }
}
Avoid Complex Configurations
// ❌ Bad - configurable everything
const config = {
database: {
type: 'mysql',
host: 'localhost',
port: 3306,
pool: {
min: 5,
max: 100,
idleTimeout: 30000,
acquireTimeout: 60000,
reapInterval: 1000,
// 50 more options...
},
replication: { /* ... */ },
sharding: { /* ... */ },
}
};
// ✅ Good - sensible defaults, minimal config
const config = {
database: {
host: 'localhost',
port: 3306,
}
};
// Add configuration options when needed
Simple Error Handling
// ❌ Bad - elaborate error handling for unlikely cases
class ApiError extends Error {
constructor(
message: string,
public code: string,
public statusCode: number,
public retryable: boolean,
public context: object,
public originalError?: Error,
public requestId?: string,
public timestamp?: Date,
) {
super(message);
}
}
// Custom errors for every possible scenario
class NetworkTimeoutError extends ApiError { }
class RateLimitError extends ApiError { }
class AuthenticationError extends ApiError { }
// ... 20 more error classes
// ✅ Good - handle what you need
class ApiError extends Error {
constructor(message: string, public statusCode: number) {
super(message);
}
}
// Add specific error types when requirements demand them
Avoid Unused Generics
// ❌ Bad - generic everything "for flexibility"
class Repository<T, K, Q extends Query<T>, R extends Result<T>> {
find(query: Q): R { /* ... */ }
save(entity: T): R { /* ... */ }
delete(key: K): R { /* ... */ }
}
// ✅ Good - specific implementation
class UserRepository {
find(id: string): User | null { /* ... */ }
save(user: User): User { /* ... */ }
delete(id: string): boolean { /* ... */ }
}
// Introduce generics when you have multiple similar repositories
Skip "Just In Case" Code
// ❌ Bad - defensive code for impossible scenarios
function processOrder(order: Order) {
// "Just in case" checks
if (!order) throw new Error('Order is null');
if (!order.id) throw new Error('Order has no id');
if (order.items === undefined) order.items = [];
if (order.items === null) order.items = [];
if (!Array.isArray(order.items)) order.items = [];
if (typeof order.total !== 'number') order.total = 0;
// Actual logic...
}
// ✅ Good - trust your types and validation
function processOrder(order: Order) {
// TypeScript ensures order shape
// Validation happens at API boundary
return calculateTotal(order.items);
}
YAGNI vs Planning Ahead
YAGNI does NOT mean:
✗ Never plan or design
✗ Ignore scalability entirely
✗ Write hacky code
✗ Skip basic abstractions
YAGNI DOES mean:
✓ Don't build features before they're needed
✓ Don't optimize before measuring
✓ Don't abstract before patterns emerge
✓ Keep it simple until complexity is required
Quick Reference
| Instead of... | Do this... |
|---|---|
| Building for every database | Support the one you use |
| Adding unused parameters | Add them when needed |
| Creating deep inheritance | Start with simple classes |
| Complex configuration | Sensible defaults |
| Many error types | Basic error handling |
| Generic everything | Specific implementations |
Related Principles
YAGNI works well with:
• KISS - Keep It Simple, Stupid
• DRY - Don't Repeat Yourself
• Agile - Iterative development
• Lean - Eliminate waste
"Simplicity is the ultimate sophistication" - Leonardo da Vinci