Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/multica-ai/andrej-karpathy-skills/llms.txt

Use this file to discover all available pages before exploring further.

The Andrej Karpathy Skills project identifies over-engineering as one of the most common and expensive LLM coding failures. Models tend to add abstractions, invent configuration options, handle impossible edge cases, and write speculative features for requirements that were never asked for. This principle — Simplicity First — holds the line: write only the minimum code that solves the stated problem.

The core problem

From Andrej Karpathy’s observation:
“They really like to overcomplicate code and APIs, bloat abstractions, don’t clean up dead code… implement a bloated construction over 1000 lines when 100 would do.”
Over-engineering isn’t always obviously wrong. Strategy patterns, configuration layers, and caching systems are legitimate tools — the problem is timing. Adding them before they’re needed creates code that’s harder to read, harder to test, harder to change, and more likely to contain bugs.

The five rules

  • No features beyond what was asked. If the request is “add a discount function,” write a discount function. Not a discount system.
  • No abstractions for single-use code. Interfaces, base classes, and protocol layers add value when there are multiple implementations. One implementation doesn’t justify the overhead.
  • No “flexibility” or “configurability” that wasn’t requested. Optional parameters, strategy injection, and plugin systems are speculative — they solve problems the user may never have.
  • No error handling for impossible scenarios. Handle the errors that can actually occur. Don’t guard against inputs that the call site can never produce.
  • If you write 200 lines and it could be 50, rewrite it. Length is a signal. If a solution is long, review it before shipping it.

The senior engineer test

Ask yourself: “Would a senior engineer say this is overcomplicated?” A senior engineer reviewing a discount calculation function doesn’t want to see an abstract base class, a strategy pattern, a dataclass config object, and a calculator wrapper. They want to see a function that takes an amount and a percentage and returns a number. The elaborate version signals that the author solved a problem they invented, not the problem they were given. If the answer to the test is yes — simplify before submitting.

Examples from real requests

Over-abstraction: “Add a function to calculate discount”

The request says “a function.” The over-engineered response creates five classes.
Bad example — strategy pattern for a single use case
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
Good example — one function that does the job
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

When to add complexity

Add the strategy pattern only when you actually need multiple discount types. If that requirement arrives later, refactor then — with real requirements to guide the design, not imagined ones. Code written for actual needs is almost always simpler and more correct than code written for hypothetical needs.

Speculative features: “Save user preferences to database”

The request asks for one operation: save preferences. The over-engineered version adds caching, validation, merging, and a notification system that nobody requested.
Bad example — solves problems that don't exist yet
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
Good example — exactly what was asked for
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)
    )
Add caching when performance data shows it’s needed. Add validation when bad data causes a real bug. Add merging when the product requirement arrives. Each feature earns its place by solving a real problem at the right time.

The right direction for complexity

Simplicity First doesn’t mean “write bad code.” It means complexity should be earned, not anticipated.
Add it nowAdd it later
Error handling for inputs that can actually occurError handling for inputs the call site can never produce
The one implementation that’s neededAn abstraction layer for multiple implementations that don’t exist
Clear variable names and a short docstringConfiguration systems, registries, and plugin architectures
Tests for real behaviorTests for behavior no one has asked for

Think Before Coding

Agree on what to build before writing any code. Simpler starts with a clear scope.

Surgical Changes

When editing existing code, don’t add complexity beyond the change requested.

Build docs developers (and LLMs) love