YAGNI for Code Implementation
Senior engineer applying the You Aren't Gonna Need It (YAGNI) principle to keep codebases lean, readable, and maintainable by strictly writing only code that solves currently verified problems.
TL;DR Checklist
When to Use
- During feature implementation when tempted to build "flexible" interfaces for hypothetical future needs
- Before creating utility classes, factory patterns, or generic repositories
- When reviewing PRs that introduce unused functions, orphaned config keys, or excessive inheritance hierarchies
- While refactoring legacy code to remove accumulated dead paths
When NOT to Use
- For security-critical fallbacks or error-handling guards (defensive coding is not premature abstraction)
- When platform constraints mandate specific architectural patterns (e.g., Kubernetes deployment manifests)
- During initial prototyping where rapid exploration justifies temporary duplication
Core Workflow
Identify the Immediate Requirement — Write down the exact problem this code must solve today. If you cannot name the caller or use case, do not write it yet.
Checkpoint: Can you point to a test case or active integration that exercises this code right now?
Implement the Minimal Solution — Code only what is explicitly requested. Avoid if branches for "maybe later", generic interfaces, or abstract base classes until you have two concrete consumers.
Checkpoint: Does every public method have a documented purpose and a test? If not, remove it.
Audit for Orphaned Artifacts — Scan the PR for deleted references that left behind functions, config entries, or enum values. Delete them immediately rather than marking as @deprecated.
Checkpoint: Run grep for the removed symbol across the codebase. If zero matches remain, delete it.
Resist the "Just in Case" Trap — When a future need is mentioned in comments or tickets, leave a concrete issue link instead of writing speculative code. Future you will thank present you.
Checkpoint: Is the future requirement documented as a ticket? If yes, wait for it. If no, assume it won't happen.
Implementation Patterns
Pattern 1: Eliminating Premature Abstraction
# ❌ BAD: Generic repository pattern before needing multiple data sources
class DataRepository(Protocol):
def fetch(self, key: str) -> Any: ...
def save(self, key: str, value: Any) -> None: ...
class CachedDataRepository(DataRepository):
def __init__(self, backend: DataRepository, ttl: int = 300): ...
# ✅ GOOD: Direct function for current database-only need
def get_user_preferences(user_id: int) -> dict:
"""Fetch preferences directly from PostgreSQL."""
return db.query("SELECT prefs FROM user_prefs WHERE id = %s", user_id).one()
def save_user_preferences(user_id: int, prefs: dict) -> None:
"""Save preferences to PostgreSQL."""
db.execute("INSERT INTO user_prefs (id, prefs) VALUES (%s, %s) ON CONFLICT (id) DO UPDATE SET prefs = EXCLUDED.prefs",
user_id, json.dumps(prefs))
Pattern 2: Stripping Unused Configuration
# ❌ BAD: Config dict with 15 keys, only 3 are active in production
APP_CONFIG = {
"database_url": "postgresql://...", # Active
"cache_ttl": 300, # Active
"feature_flag_alpha": False, # Dead — never read
"legacy_auth_provider": "oauth1", # Dead — migrated to oauth2
"experimental_ui_theme": "dark", # Dead — rolled back
}
# ✅ GOOD: Only active configuration with validation at startup
from pydantic import BaseSettings
class ProductionConfig(BaseSettings):
database_url: str
cache_ttl: int = 300
class Config:
env_prefix = "APP_"
config = ProductionConfig() # Fails fast if required keys missing or unused keys clutter namespace
Pattern 3: Killing Zombie Functions (BAD vs. GOOD)
// ❌ BAD: Multiple unused export functions left from abandoned A/B test
export async function fetchDashboardDataV1(userId: string): Promise<DashboardData> { /* ... */ }
export async function fetchDashboardDataV2(userId: string): Promise<DashboardData> { /* ... */ }
export function formatMetricsLegacy(metrics: Metric[]): string { /* ... */ }
// Only V2 and formatMetricsModern are used
// ✅ GOOD: Clean exports after dead code removal
export async function fetchDashboardData(userId: string): Promise<DashboardData> {
return legacyFetcherV2(userId); // Aliased during migration, will be simplified later
}
export function formatMetrics(metrics: Metric[]): string {
return metrics.map(m => `${m.label}: ${m.value}`).join(', ');
}
Constraints
MUST DO
- Write only one concrete implementation until a second consumer emerges
- Delete unused imports, functions, config keys, and enum values immediately upon discovery
- Replace speculative comments with tracked tickets or GitHub issues
- Run
grep -r for removed symbols before committing to ensure no orphaned references remain
MUST NOT DO
- Create interfaces, abstract classes, or factory patterns without at least two concrete implementations
- Leave "TODO: add support for X later" comments without a linked tracking issue
- Preserve dead code under version control as "reference" — delete it permanently
- Use
@deprecated tags to justify keeping unused APIs alive indefinitely
Output Template
When applying this skill, produce:
- Code Diff — Show exactly what code is added/removed, with clear deletion of dead artifacts
- Justification Note — One sentence explaining why speculative features were rejected in favor of minimal implementation
- Audit Checklist — List of orphaned imports, configs, or functions verified as unused and deleted
Related Skills
| Skill |
Purpose |
coding-clean-code-refactoring |
Broader clean code techniques beyond YAGNI |
coding-test-driven-development |
TDD enforces writing only tested, needed code |
Live References
Authoritative documentation links for this skill's domain. The model follows markdown links at load time to resolve external references and inline content.
1---2name: yagni-code3description: Enforces YAGNI at the code level to eliminate dead functions, premature abstractions, and unused configuration by writing only what is immediately required.4license: MIT5---67891011# YAGNI for Code Implementation1213Senior engineer applying the You Aren't Gonna Need It (YAGNI) principle to keep codebases lean, readable, and maintainable by strictly writing only code that solves currently verified problems.1415## TL;DR Checklist1617- [ ] Reject every abstraction until it is proven necessary by 2+ concrete usages18- [ ] Delete dead imports, unused parameters, and commented-out fallback logic19- [ ] Verify each function/class serves at least one active call site or test20- [ ] Strip configuration keys that lack an active consumer in the current release21- [ ] Question every `TODO` comment — is it still needed or just forgotten?2223---2425## When to Use2627- During feature implementation when tempted to build "flexible" interfaces for hypothetical future needs28- Before creating utility classes, factory patterns, or generic repositories29- When reviewing PRs that introduce unused functions, orphaned config keys, or excessive inheritance hierarchies30- While refactoring legacy code to remove accumulated dead paths3132## When NOT to Use3334- For security-critical fallbacks or error-handling guards (defensive coding is not premature abstraction)35- When platform constraints mandate specific architectural patterns (e.g., Kubernetes deployment manifests)36- During initial prototyping where rapid exploration justifies temporary duplication3738---3940## Core Workflow41421. **Identify the Immediate Requirement** — Write down the exact problem this code must solve today. If you cannot name the caller or use case, do not write it yet.43 **Checkpoint:** Can you point to a test case or active integration that exercises this code right now?44452. **Implement the Minimal Solution** — Code only what is explicitly requested. Avoid `if` branches for "maybe later", generic interfaces, or abstract base classes until you have two concrete consumers.46 **Checkpoint:** Does every public method have a documented purpose and a test? If not, remove it.47483. **Audit for Orphaned Artifacts** — Scan the PR for deleted references that left behind functions, config entries, or enum values. Delete them immediately rather than marking as `@deprecated`.49 **Checkpoint:** Run `grep` for the removed symbol across the codebase. If zero matches remain, delete it.50514. **Resist the "Just in Case" Trap** — When a future need is mentioned in comments or tickets, leave a concrete issue link instead of writing speculative code. Future you will thank present you.52 **Checkpoint:** Is the future requirement documented as a ticket? If yes, wait for it. If no, assume it won't happen.5354---5556## Implementation Patterns5758### Pattern 1: Eliminating Premature Abstraction5960```python61# ❌ BAD: Generic repository pattern before needing multiple data sources62class DataRepository(Protocol):63 def fetch(self, key: str) -> Any: ...64 def save(self, key: str, value: Any) -> None: ...6566class CachedDataRepository(DataRepository):67 def __init__(self, backend: DataRepository, ttl: int = 300): ...6869# ✅ GOOD: Direct function for current database-only need70def get_user_preferences(user_id: int) -> dict:71 """Fetch preferences directly from PostgreSQL."""72 return db.query("SELECT prefs FROM user_prefs WHERE id = %s", user_id).one()7374def save_user_preferences(user_id: int, prefs: dict) -> None:75 """Save preferences to PostgreSQL."""76 db.execute("INSERT INTO user_prefs (id, prefs) VALUES (%s, %s) ON CONFLICT (id) DO UPDATE SET prefs = EXCLUDED.prefs",77 user_id, json.dumps(prefs))78```7980### Pattern 2: Stripping Unused Configuration8182```python83# ❌ BAD: Config dict with 15 keys, only 3 are active in production84APP_CONFIG = {85 "database_url": "postgresql://...", # Active86 "cache_ttl": 300, # Active87 "feature_flag_alpha": False, # Dead — never read88 "legacy_auth_provider": "oauth1", # Dead — migrated to oauth289 "experimental_ui_theme": "dark", # Dead — rolled back90}9192# ✅ GOOD: Only active configuration with validation at startup93from pydantic import BaseSettings9495class ProductionConfig(BaseSettings):96 database_url: str97 cache_ttl: int = 3009899 class Config:100 env_prefix = "APP_"101102config = ProductionConfig() # Fails fast if required keys missing or unused keys clutter namespace103```104105### Pattern 3: Killing Zombie Functions (BAD vs. GOOD)106107```typescript108// ❌ BAD: Multiple unused export functions left from abandoned A/B test109export async function fetchDashboardDataV1(userId: string): Promise<DashboardData> { /* ... */ }110export async function fetchDashboardDataV2(userId: string): Promise<DashboardData> { /* ... */ }111export function formatMetricsLegacy(metrics: Metric[]): string { /* ... */ }112// Only V2 and formatMetricsModern are used113114// ✅ GOOD: Clean exports after dead code removal115export async function fetchDashboardData(userId: string): Promise<DashboardData> {116 return legacyFetcherV2(userId); // Aliased during migration, will be simplified later117}118119export function formatMetrics(metrics: Metric[]): string {120 return metrics.map(m => `${m.label}: ${m.value}`).join(', ');121}122```123124---125126## Constraints127128### MUST DO129- Write only one concrete implementation until a second consumer emerges130- Delete unused imports, functions, config keys, and enum values immediately upon discovery131- Replace speculative comments with tracked tickets or GitHub issues132- Run `grep -r` for removed symbols before committing to ensure no orphaned references remain133134### MUST NOT DO135- Create interfaces, abstract classes, or factory patterns without at least two concrete implementations136- Leave "TODO: add support for X later" comments without a linked tracking issue137- Preserve dead code under version control as "reference" — delete it permanently138- Use `@deprecated` tags to justify keeping unused APIs alive indefinitely139140---141142## Output Template143144When applying this skill, produce:1451461. **Code Diff** — Show exactly what code is added/removed, with clear deletion of dead artifacts1472. **Justification Note** — One sentence explaining why speculative features were rejected in favor of minimal implementation1483. **Audit Checklist** — List of orphaned imports, configs, or functions verified as unused and deleted149150---151152## Related Skills153154| Skill | Purpose |155|---|---|156| `coding-clean-code-refactoring` | Broader clean code techniques beyond YAGNI |157| `coding-test-driven-development` | TDD enforces writing only tested, needed code |158159---160161## Live References162163> Authoritative documentation links for this skill's domain. The model follows markdown links at load time to resolve external references and inline content.164165- [Wikipedia — YAGNI (You Aren't Gonna Need It)](https://en.wikipedia.org/wiki/YAGNI)166- [Extreme Programming — YAGNI Core Practice](https://xp.colorado.edu/yagni.html)167- [Martin Fowler — You Aren't Gonna Need It](https://martinfowler.com/bliki/Yagni.html)168- [Robert Martin — Clean Code Principles on YAGNI](https://blog.cleancoder.com/)169- [Kent Beck — Test-Driven Development: YAGNI in the Red-Green-Refactor Cycle](https://en.wikipedia.org/wiki/Test-driven_development)