Documentation Index
Fetch the complete documentation index at: https://docs.karpathy.phronesisagent.com/llms.txt
Use this file to discover all available pages before exploring further.
LLMs have a strong pull toward over-abstraction. Ask for a discount function and you might get a strategy pattern with abstract base classes, dataclasses, and a calculator object — all for a single multiplication. Simplicity First is the discipline of writing the minimum code that actually solves the stated problem, deferring every other complexity until the moment it’s genuinely needed.
The rules
- No features beyond what was asked. If the request didn’t mention it, don’t build it.
- No abstractions for single-use code. One call site does not need an interface.
- No “flexibility” or “configurability” that wasn’t requested. Optional parameters, pluggable backends, and configuration systems are features — treat them that way.
- No error handling for impossible scenarios. Handle the errors that can actually happen.
- If you write 200 lines and it could be 50, rewrite it. The first draft is not the final answer.
The test
Ask yourself: “Would a senior engineer say this is overcomplicated?”
If yes, simplify. Every time.
Over-abstraction example
Request: “Add a function to calculate discount”
What LLMs do wrong — strategy pattern for a single multiplicationfrom abc import ABC, abstractmethod
from enum import Enum
from typing import Protocol, Union
from dataclasses import dataclass
class DiscountStrategy(ABC):
@abstractmethod
def calculate(self, amount: float) -> float:
pass
class PercentageDiscount(DiscountStrategy):
def __init__(self, percentage: float):
self.percentage = percentage
def calculate(self, amount: float) -> float:
return amount * (self.percentage / 100)
class FixedDiscount(DiscountStrategy):
def __init__(self, fixed_amount: float):
self.fixed_amount = fixed_amount
def calculate(self, amount: float) -> float:
return min(self.fixed_amount, amount)
@dataclass
class DiscountConfig:
strategy: DiscountStrategy
min_purchase: float = 0.0
max_discount: float = float('inf')
class DiscountCalculator:
def __init__(self, config: DiscountConfig):
self.config = config
def apply_discount(self, amount: float) -> float:
if amount < self.config.min_purchase:
return 0
discount = self.config.strategy.calculate(amount)
return min(discount, self.config.max_discount)
# Usage requires 30+ lines of setup for a simple calculation
The correct approach — one function until complexity is actually neededdef calculate_discount(amount: float, percent: float) -> float:
"""Calculate discount amount. percent should be 0-100."""
return amount * (percent / 100)
# Usage
discount = calculate_discount(100.0, 10.0) # $10 off
Speculative features example
Request: “Save user preferences to database”
What LLMs do wrong — building cache, validation, and notifications nobody asked forclass PreferenceManager:
def __init__(self, db, cache=None, validator=None):
self.db = db
self.cache = cache or InMemoryCache()
self.validator = validator or DefaultValidator()
def save(self, user_id: int, prefs: dict,
merge: bool = True,
validate: bool = True,
notify: bool = False) -> bool:
"""Save preferences with optional merging, validation, and notifications."""
if validate:
errors = self.validator.validate(prefs)
if errors:
raise ValidationError(errors)
existing = self.get(user_id) if merge else {}
if merge:
existing.update(prefs)
prefs = existing
self.db.execute(
"UPDATE users SET preferences = ? WHERE id = ?",
(json.dumps(prefs), user_id)
)
if self.cache:
self.cache.set(f"prefs:{user_id}", prefs)
if notify:
self.notify_preference_change(user_id, prefs)
return True
def notify_preference_change(self, user_id: int, prefs: dict):
# Another 30 lines for a feature nobody asked for
pass
The correct approach — just what was askeddef save_preferences(db, user_id: int, preferences: dict):
"""Save user preferences to database."""
db.execute(
"UPDATE users SET preferences = ? WHERE id = ?",
(json.dumps(preferences), user_id)
)
When to add complexity
Complexity earns its place only when you actually need it — not when you might need it someday.
Add caching when performance data shows it’s needed. Add validation when bad data actually appears. Add merging when the requirement is stated. Refactor to a class when you have multiple call sites with shared state. Until then, the simple version is the correct version.
The simple versions are faster to implement, easier to test, easier to understand, and easier to refactor when real complexity arrives. Good code solves today’s problem simply — not tomorrow’s problem prematurely.