This is the final article in my series on working with LLMs at scale. Part 1 through Part 4 focused on building with LLMs—managing sessions, ensuring quality, coordinating parallel work.
This article is about something different: using LLMs as learning tools. Not just to write code, but to help you understand complex systems, internalize patterns, and build lasting knowledge.
The Learning Problem
As a developer, you're constantly learning:
- New frameworks and libraries
- Complex algorithms
- System architecture patterns
- Domain-specific knowledge
- Best practices and anti-patterns
Traditional learning methods are slow:
- Reading documentation takes hours
- Watching tutorials is passive
- Trial-and-error is inefficient
- Taking notes manually is tedious
What if LLMs could accelerate this? Not by just answering questions, but by creating personalized learning materials tailored to how you learn best?
Pattern 1: Heavily-Annotated Code (Worksheets)
When learning a new codebase or pattern, I ask Claude to generate worksheets—code with extensive inline comments explaining every decision.
Example: Understanding React Hooks
Instead of:
Explain React hooks to me
I ask:
Generate a complete React component using hooks, but add inline comments
explaining:
- Why this hook is needed
- What problems it solves
- Common mistakes to avoid
- How it compares to class components
- Performance implications
Claude produces:
import { useState, useEffect, useCallback, useMemo } from 'react';
function UserProfile({ userId }) {
// useState: Manages local component state
// Why: We need to track user data that changes over time
// Replaces: this.state in class components
// Common mistake: Forgetting state updates are asynchronous
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
// useEffect: Handles side effects (API calls, subscriptions)
// Why: We need to fetch data after component mounts
// Dependencies: [userId] means "re-run when userId changes"
// Common mistake: Missing dependencies causes stale closures
// Performance: Cleanup function prevents memory leaks
useEffect(() => {
// Start loading
setLoading(true);
// Fetch user data
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => {
setUser(data);
setLoading(false);
});
// Cleanup: Cancel fetch if component unmounts
return () => {
// In production, you'd actually cancel the fetch here
};
}, [userId]); // Dependency array: re-run when userId changes
// useMemo: Memoizes expensive computations
// Why: Prevents recalculating on every render
// Only recalculates when 'user' changes
// Common mistake: Overusing for cheap operations (adds overhead)
const displayName = useMemo(() => {
if (!user) return 'Loading...';
// Expensive operation example (yours might be more complex)
return `${user.firstName} ${user.lastName}`.toUpperCase();
}, [user]);
// useCallback: Memoizes function references
// Why: Prevents child components from re-rendering unnecessarily
// Without this: New function created on every render
// Performance: Only useful if passed to optimized child components
const handleRefresh = useCallback(() => {
setLoading(true);
// Refresh logic here
}, []);
if (loading) return <div>Loading...</div>;
if (!user) return <div>User not found</div>;
return (
<div>
<h1>{displayName}</h1>
<button onClick={handleRefresh}>Refresh</button>
</div>
);
}
This isn't just code—it's a learning artifact. I can:
- Read it top to bottom to understand the flow
- See why each decision was made
- Learn common mistakes to avoid
- Understand performance implications
The Prompt Pattern
The key is being specific about what you want explained:
Generate [concept] code with inline comments explaining:
- Why this approach (vs alternatives)
- Common mistakes
- Performance considerations
- When to use / not use
- How it connects to [related concept]
I've used this pattern to learn:
- Rust's ownership system
- Clojure's transducers
- Kubernetes operators
- Distributed system patterns
- Database indexing strategies
Pattern 2: Flashcards for Spaced Repetition
Reading annotated code helps understanding, but doesn't ensure retention. For that, I use flashcards with spaced repetition.
The Flashcard System
I built a flashcard CLI (see github.com/charignon/flashcards) that implements spaced repetition algorithms (SM-2, Leitner boxes).
But manually creating flashcards is tedious. So I ask Claude to generate them.
Example: Learning System Design
Generate flashcards for distributed systems concepts. Format as JSON.
Focus on: CAP theorem, consensus algorithms, partitioning strategies.
Each card should have:
- A precise question
- A complete answer
- Tags for organization
- Notes with additional context
Claude generates:
[
{
"question": "In the CAP theorem, what does 'Partition Tolerance' actually mean?",
"answer": "The system continues to operate despite network partitions (messages between nodes being lost or delayed). You must choose: consistency (all nodes see same data) or availability (all requests get responses) during a partition.",
"tags": ["cap-theorem", "distributed-systems", "fundamentals"],
"notes": "Common misconception: You can't 'give up' partition tolerance—network partitions will happen. You choose between CP (consistent but not available during partition) or AP (available but inconsistent during partition)."
},
{
"question": "What is the key difference between Raft and Paxos consensus algorithms?",
"answer": "Raft uses leader election + log replication (simpler to understand). Paxos uses proposal numbers + acceptors (more flexible but complex). Raft is easier to implement correctly.",
"tags": ["consensus", "raft", "paxos", "algorithms"],
"notes": "Raft was explicitly designed to be more understandable than Paxos. In practice, most systems use Raft (etcd, Consul) or Raft-like algorithms (Zab in ZooKeeper)."
},
{
"question": "When should you use hash-based partitioning vs range-based partitioning?",
"answer": "Hash partitioning: Uniform distribution, good for random access. Range partitioning: Preserves ordering, good for range queries. Choose based on your query patterns.",
"tags": ["partitioning", "sharding", "database"],
"notes": "Real example: User IDs → hash partition (uniform load). Timestamps → range partition (range queries like 'last 24 hours'). Hybrid approaches exist (compound keys)."
}
]
I import these directly into my flashcard system:
cat system_design_cards.json | ./flashcards bulk-create --project system-design
Now I can quiz myself:
$ ./flashcards quiz --project system-design --limit 5
Question 1/5: In the CAP theorem, what does 'Partition Tolerance' actually mean?
[Show answer? y/n]
The Spaced Repetition Loop
Here's how the system works:
- Generate flashcards from concept (Claude)
- Import into flashcard CLI
- Quiz yourself (spaced repetition schedules reviews)
- Rate confidence (1-5): Low = see again soon, High = see much later
- Repeat over days/weeks until internalized
The beauty: I can generate flashcards for any technical topic in minutes. No manual card creation. No finding existing decks that don't quite match my needs.
Topics I've Created Flashcard Decks For
- Algorithms: Time complexity, specific algorithm details, when to use what
- System design: Distributed systems patterns, scalability techniques
- Languages: Rust ownership, Clojure idioms, Python async/await
- Databases: Indexing strategies, transaction isolation levels, CAP tradeoffs
- Kubernetes: Resource types, networking concepts, operator patterns
- Interview prep: LeetCode patterns, behavioral question frameworks
The Meta-Pattern: AI as a Learning Compiler
These patterns share a common insight:
LLMs can transform any learning goal into a personalized learning artifact.
You provide a concept to learn, your preferred style (flashcards, annotated code), and your knowledge gaps. Claude generates materials tailored to you—at the right complexity, in the format you learn best from.
Practical Workflow
My typical learning workflow:
- Generate annotated code: Complete implementation with explanatory comments
- Extract flashcards: Key concepts become cards for spaced repetition
- Quiz over time: Spaced repetition embeds knowledge long-term
This takes 30-60 minutes to generate materials, then ongoing review. Traditional learning (reading papers, watching videos, manual note-taking) would take hours or days to achieve the same depth.
The Flashcard System Details
Since I mentioned my flashcard system, here's how it works:
Features:
- Multiple spaced repetition algorithms (SM-2, Leitner, fixed intervals)
- Project organization (tag by topic)
- Adaptive quiz sessions (repeat failed items immediately)
- JSON import/export (easy to generate with LLMs)
- CLI for terminal-based studying
Usage:
# Create from Claude-generated JSON
flashcards bulk-create --file cards.json --project rust
# Start adaptive quiz session
flashcards quiz --project rust --limit 10
# Review a specific card
flashcards review <id> --confidence 4
# See statistics
flashcards stats --project rust
The adaptive sessions are key: if you get a card wrong (confidence < 3), it immediately re-queues it. You practice until you get it right, then spaced repetition takes over for long-term retention.
Key Learnings
After a year of using LLMs for learning:
Annotated code beats plain code. The "why" matters more than the "what."
Flashcards with spaced repetition work. Generate hundreds of cards in minutes, internalize them over weeks.
Active learning beats passive. Quizzing yourself is more effective than rereading notes.
Conclusion: Beyond Code Generation
This series has been about working with LLMs at scale—managing multiple sessions, ensuring quality, building coordination patterns. But it's also been about a fundamental shift in how we work with code and knowledge.
LLMs aren't just code generators. They're:
- Productivity multipliers (Part 2)
- Quality enforcers (Part 3)
- Coordination layers (Part 3)
- Learning accelerators (Part 5)
The developers who thrive in this new world won't be the ones who use LLMs as fancy autocomplete. They'll be the ones who build workflows, tooling, and patterns that let them work at 10x their previous speed while maintaining quality and continuously learning.
My system—tmux integration, telemetry, memento, smoke tests, code review workflows, flashcards—is just one approach. Yours will look different based on your needs, your style, your projects.
But the core principles apply:
- Make the invisible visible (ergonomics, telemetry)
- Enforce quality automatically (smoke tests)
- Share context between sessions (memento)
- Learn continuously (flashcards, annotated code)
The future of software development is human-AI collaboration. Not humans being replaced, but humans being amplified. The question isn't whether to use LLMs—it's how to use them most effectively.
I hope this series has given you ideas, tools, and patterns you can adapt to your own workflow.
Now go build something amazing.
All tools mentioned are open source:
- Memento - Note system for LLM context
- Flashcards - Spaced repetition CLI
- Dotfiles - Tmux scripts and config
- AppDaemon - Home automation examples
Top comments (1)
Your Part 1 and Part 4 links are broken.