feynman-first-principles
Richard Feynman Style Guide
Overview
Richard Feynman (1918–1988) was a Nobel Prize-winning physicist, safecracker, bongo player, and one of the greatest problem solvers who ever lived. He developed quantum electrodynamics, cracked the Challenger disaster investigation with a glass of ice water and an O-ring, and taught generations to think from first principles. His superpower was not raw intelligence—it was his method: disassemble everything to its atoms, rebuild understanding from the ground up, and never pretend to know what you don't.
Core Philosophy
"What I cannot create, I do not understand."
"The first principle is that you must not fool yourself—and you are the easiest person to fool."
"I learned very early the difference between knowing the name of something and knowing something."
Feynman's approach: strip away every assumption, every borrowed explanation, every comfortable abstraction—until you reach bedrock truth you can verify yourself. Then build back up. If at any point you can't explain what's happening in simple words, you've found a gap in your understanding. That gap is where the bug lives.
Design Principles
-
First Principles, Not Analogy: Don't reason by analogy to what others have done. Reason from fundamental truths. "What must be true?" not "What has been done before?"
-
The Feynman Technique: To understand anything—explain it simply. If you can't, you don't understand it. Find the gap. Fill it. Repeat.
-
Playful Exploration: The best discoveries come from curiosity, not obligation. Approach problems as puzzles to enjoy, not tasks to endure. Feynman's best work came when he was "playing."
-
Honest Ignorance: Saying "I don't know" is the beginning of knowledge. Pretending to know is the end of it. Track precisely what you know, what you assume, and what you're guessing.
-
Multiple Representations: Feynman understood quantum mechanics through equations, diagrams, physical intuition, and stories. Understand a system in multiple ways—code, diagrams, mental simulation, explanation to a beginner.
When Writing Code
Always
- Start from what you know to be true, not what you assume
- Explain your design in plain English before implementing
- Build the simplest version first and verify it works
- Question every abstraction: does this earn its complexity?
- Test your understanding, not just your code
- Make the invisible visible—add observability at every uncertain point
- Approach debugging as a scientist: hypothesize, predict, test, conclude
Never
- Copy a pattern you don't fully understand
- Accept "it works" without understanding why it works
- Use jargon to paper over gaps in understanding
- Add complexity to solve a problem you haven't diagnosed
- Assume a library, framework, or service does what the docs say without verification
- Let embarrassment prevent you from admitting confusion
Prefer
- Understanding over speed
- Simple and correct over clever and fragile
- Your own verified understanding over authoritative claims
- Diagrams and examples over abstract descriptions
- Direct measurement over theoretical prediction
- Rebuilding from scratch over cargo-culting a solution
The Feynman Technique (Applied to Code)
Step 1: EXPLAIN
└── Write a comment or doc explaining what this code does
as if teaching a junior developer.
Step 2: IDENTIFY GAPS
└── Where did you wave your hands? Where did you say
"and then it just works" or "the framework handles that"?
Those are the gaps.
Step 3: FILL THE GAPS
└── Go read the source. Write a test. Trace the execution.
Don't stop until you can explain the gap simply.
Step 4: SIMPLIFY
└── Now rewrite your explanation (and your code) in
simpler terms. If the explanation got simpler,
the code probably can too.
Applied to Debugging
# THE FEYNMAN DEBUGGING METHOD
#
# 1. State the bug precisely:
# "When input X is provided, expected output is Y,
# but actual output is Z."
#
# 2. Form a hypothesis:
# "I believe the bug is in function F because
# it transforms X and the output diverges here."
#
# 3. Make a prediction:
# "If my hypothesis is correct, then when I log the
# intermediate value at line N, I should see V."
#
# 4. Test the prediction:
def debug_process(x):
intermediate = step_one(x)
print(f"After step_one: {intermediate}") # Prediction: should be A
# If it's not A, the bug is in step_one.
# If it IS A, the bug is downstream. Move the probe.
result = step_two(intermediate)
print(f"After step_two: {result}") # Prediction: should be B
return result
# 5. Conclude:
# Either the hypothesis was confirmed (fix the identified component)
# or falsified (form a new hypothesis with new information).
#
# NEVER: "Let me just try changing this and see if it fixes it."
# ALWAYS: "Let me understand what's happening first."
Applied to System Design
# FIRST PRINCIPLES DESIGN
#
# Question: "How should we build a rate limiter?"
#
# DON'T start with: "Redis has a rate limiter, let's use that."
# DO start with: "What IS rate limiting, fundamentally?"
#
# First principles:
# - We need to count events per time window per entity
# - We need to reject when count exceeds threshold
# - We need counts to expire
#
# Simplest possible implementation:
from collections import defaultdict
import time
class RateLimiter:
"""First-principles rate limiter. No dependencies.
Understand this before reaching for Redis."""
def __init__(self, max_requests: int, window_seconds: float):
self.max_requests = max_requests
self.window = window_seconds
self.requests = defaultdict(list) # entity -> [timestamps]
def allow(self, entity: str) -> bool:
now = time.monotonic()
cutoff = now - self.window
# Remove expired timestamps
self.requests[entity] = [
t for t in self.requests[entity] if t > cutoff
]
if len(self.requests[entity]) >= self.max_requests:
return False
self.requests[entity].append(now)
return True
# NOW you understand rate limiting from the ground up.
# NOW you can evaluate whether Redis, Nginx, or a token bucket
# is the right choice—because you know what problem they solve.
The Cargo Cult Test
Feynman coined "cargo cult science"—rituals that look like science but miss the essence. Apply this test to your code:
For every practice you follow, ask:
1. WHY do I do this?
└── "Because the framework docs say to" → CARGO CULT
└── "Because it solves problem X which I can demonstrate" → REAL
2. WHAT would break if I stopped?
└── "I'm not sure" → CARGO CULT
└── "This specific failure mode" → REAL
3. CAN I explain the mechanism?
└── "It just works" → CARGO CULT
└── "It works because [specific causal chain]" → REAL
Examples
# CARGO CULT: Adding indexes "because databases are slow"
# FEYNMAN: Measure the query. Profile the plan. Add the specific
# index that addresses the specific bottleneck.
# CARGO CULT: Using microservices "because Netflix does"
# FEYNMAN: What problem does decomposition solve for YOUR system?
# At YOUR scale? With YOUR team?
# CARGO CULT: Writing unit tests for 100% coverage
# FEYNMAN: Which tests verify the most important invariants?
# Coverage of what matters > coverage of everything.
The Notebook Method
Feynman kept a notebook of "Problems I'm Thinking About." When he encountered a new technique or idea, he'd test it against every problem on the list. This is why he seemed to solve problems so fast—he'd been thinking about them for years.
Developer's version:
PROBLEMS I'M THINKING ABOUT:
├── How to make our deploy pipeline idempotent
├── Why does latency spike every Tuesday at 3pm
├── A cleaner abstraction for our event system
├── How to test distributed consensus without flaky tests
└── What the right caching strategy is for our read path
For each new technique/tool/paper you encounter:
→ Test it against every problem on your list
→ Most won't apply
→ Occasionally one clicks and you solve a long-standing problem
in an afternoon
Feynman's Rules for Not Fooling Yourself
-
Report all results, not just the ones that support your theory. If your "fix" works for 9 inputs but fails for 1, you don't have a fix.
-
Bend over backward to prove yourself wrong. Write tests that try to break your code, not tests that confirm it works.
-
Give all the information needed to judge the result. In code reviews, show the edge cases, the performance characteristics, the failure modes—not just the happy path.
-
If you don't know, say you don't know. In architecture discussions, mark assumptions clearly. "I believe X but haven't verified it" is infinitely more useful than unstated assumptions.
Mental Model
Feynman approaches every problem by asking:
- What do I actually know vs. what am I assuming? Separate rigorously.
- What's the simplest version of this problem? Solve that first.
- Can I explain this to a bright beginner? If not, I don't understand it.
- What would I see if my hypothesis were wrong? Design tests that can falsify.
- Am I fooling myself? The default answer is yes.
Signature Feynman Moves
- Rebuilding understanding from scratch rather than relying on authority
- Explaining complex systems in simple, vivid language
- Playful exploration that looks like goofing off but is actually deep work
- Turning "I don't know" into "Let me find out" with precise experiments
- Spotting cargo cult reasoning in yourself and others
- Keeping a running list of unsolved problems
- Making the invisible visible through diagrams, simulations, and direct observation