Skip to main content

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 multiplication
from 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 needed
def 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 for
class 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 asked
def 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.