Kaizen: Continuous Improvement
Apply continuous improvement mindset - suggest small iterative improvements, error-proof designs, follow established patterns, avoid over-engineering; automatically applied to guide quality and simplicity
Overview
Small improvements, continuously. Error-proof by design. Follow what works. Build only what's needed.
Core principle:
Many small improvements beat one big change. Prevent errors at design time, not with fixes.
When to Use
Always applied for:
Code implementation and refactoring
Architecture and design decisions
Process and workflow improvements
Error handling and validation
Philosophy:
Quality through incremental progress and prevention, not perfection through massive effort.
The Four Pillars
1. Continuous Improvement (Kaizen)
Small, frequent improvements compound into major gains.
Principles
Incremental over revolutionary:
Make smallest viable change that improves quality
One improvement at a time
Verify each change before next
Build momentum through small wins
Always leave code better:
Fix small issues as you encounter them
Refactor while you work (within scope)
Update outdated comments
Remove dead code when you see it
Iterative refinement:
First version: make it work
Second pass: make it clear
Third pass: make it efficient
Don't try all three at once
// Iteration 2: Make it clear (refactor)
const calculateTotal = (items: Item[]): number => {
return items.reduce((total, item) => {
return total + (item.price * item.quantity);
}, 0);
};
// Iteration 3: Make it robust (add validation)
const calculateTotal = (items: Item[]): number => {
if (!items?.length) return 0;
return items.reduce((total, item) => {
if (item.price < 0 || item.quantity < 0) {
throw new Error('Price and quantity must be non-negative');
}
return total + (item.price * item.quantity);
}, 0);
};
Each step is complete, tested, and working
typescript
// Trying to do everything at once
const calculateTotal = (items: Item[]): number => {
// Validate, optimize, add features, handle edge cases all together
if (!items?.length) return 0;
const validItems = items.filter(item => {
if (item.price < 0) throw new Error('Negative price');
if (item.quantity < 0) throw new Error('Negative quantity');
return item.quantity > 0; // Also filtering zero quantities
});
// Plus caching, plus logging, plus currency conversion...
return validItems.reduce(...); // Too many concerns at once
};
Overwhelming, error-prone, hard to verify
In Practice
When implementing features:
Start with simplest version that works
Add one improvement (error handling, validation, etc.)
Test and verify
Repeat if time permits
Don't try to make it perfect immediately
When refactoring:
Fix one smell at a time
Commit after each improvement
Keep tests passing throughout
Stop when "good enough" (diminishing returns)
When reviewing code:
Suggest incremental improvements (not rewrites)
Prioritize: critical → important → nice-to-have
Focus on highest-impact changes first
Accept "better than before" even if not perfect
2. Poka-Yoke (Error Proofing)
Design systems that prevent errors at compile/design time, not runtime.
Principles
Make errors impossible:
Type system catches mistakes
Compiler enforces contracts
Invalid states unrepresentable
Errors caught early (left of production)
Design for safety:
Fail fast and loudly
Provide helpful error messages
Make correct path obvious
Make incorrect path difficult
Defense in layers:
Type system (compile time)
Validation (runtime, early)
Guards (preconditions)
Error boundaries (graceful degradation)
Type System Error Proofing
// Good: Only valid states possible
type OrderStatus = 'pending' | 'processing' | 'shipped' | 'delivered';
type Order = {
status: OrderStatus;
total: number;
};
// Better: States with associated data
type Order =
| { status: 'pending'; createdAt: Date }
| { status: 'processing'; startedAt: Date; estimatedCompletion: Date }
| { status: 'shipped'; trackingNumber: string; shippedAt: Date }
| { status: 'delivered'; deliveredAt: Date; signature: string };
// Now impossible to have shipped without trackingNumber
Type system prevents entire classes of errors
</Good>
<Good>typescript
// Make invalid states unrepresentable
type NonEmptyArray
Guards and Preconditions
In Practice
When designing APIs:
- Use types to constrain inputs
- Make invalid states unrepresentable
- Return Result
3. Standardized Work
Follow established patterns. Document what works. Make good practices easy to follow.
Principles
Consistency over cleverness: - Follow existing codebase patterns - Don't reinvent solved problems - New pattern only if significantly better - Team agreement on new patterns Documentation lives with code: - README for setup and architecture - CLAUDE.md for AI coding conventions - Comments for "why", not "what" - Examples for complex patterns Automate standards: - Linters enforce style - Type checks enforce contracts - Tests verify behavior - CI/CD enforces quality gates
Following Patterns
``typescript
// Existing codebase pattern for API clients
class UserAPIClient {
async getUser(id: string): Promise<User> {
return this.fetch(/users/${id});
}
}
// New code follows the same pattern
class OrderAPIClient {
async getOrder(id: string): Promise<Order> {
return this.fetch(/orders/${id}`);
}
}
Consistency makes codebase predictable
// New code introduces different pattern without discussion
const getOrder = async (id: string): Promise => {
// Breaking consistency "because I prefer functions"
};
Inconsistency creates confusion
Error Handling Patterns
- ```typescript
- // Project standard: Result type for recoverable errors
- type Result
= { ok: true; value: T } | { ok: false; error: E }; - // All services follow this pattern
- const fetchUser = async (id: string): Promise
> => { - try {
- const user = await db.users.findById(id);
- if (!user) {
- return { ok: false, error: new Error('User not found') };
- }
- return { ok: true, value: user };
- } catch (err) {
- return { ok: false, error: err as Error };
- }
- };
- // Callers use consistent pattern
- const result = await fetchUser('123');
- if (!result.ok) {
- logger.error('Failed to fetch user', result.error);
- return;
- }
- const user = result.value; // Type-safe!
- Standard pattern across codebase
- Documentation Standards
- In Practice
- Before adding new patterns:
- Search codebase for similar problems solved
- Check CLAUDE.md for project conventions
- Discuss with team if breaking from pattern
- Update docs when introducing new pattern
- When writing code:
- Match existing file structure
- Use same naming conventions
- Follow same error handling approach
- Import from same locations
- When reviewing:
- Check consistency with existing code
- Point to examples in codebase
- Suggest aligning with standards
- Update CLAUDE.md if new standard emerges
- 4. Just-In-Time (JIT)
- Build what's needed now. No more, no less. Avoid premature optimization and over-engineering.
- Principles
- YAGNI (You Aren't Gonna Need It):
- Implement only current requirements
- No "just in case" features
- No "we might need this later" code
- Delete speculation
- Simplest thing that works:
- Start with straightforward solution
- Add complexity only when needed
- Refactor when requirements change
- Don't anticipate future needs
- Optimize when measured:
- No premature optimization
- Profile before optimizing
- Measure impact of changes
- Accept "good enough" performance
- YAGNI in Action
- class ConsoleTransport implements LogTransport { /
- ...
- / }
- class FileTransport implements LogTransport { /
- ...
- / }
- class RemoteTransport implements LogTransport { /
- ...
- / }
- class Logger {
- private transports: LogTransport[] = [];
- private queue: LogEntry[] = [];
- private rateLimiter: RateLimiter;
- private formatter: LogFormatter;
- // 200 lines of code for "maybe we'll need it"
- }
- const logError = (error: Error) => {
- Logger.getInstance().log('error', error.message);
- };
- Building for imaginary future requirements
- When to add complexity:
- - Current requirement demands it
- - Pain points identified through use
- - Measured performance issues
- - Multiple use cases emerged
- ```typescript
- // Start simple
- const formatCurrency = (amount: number): string => {
- return
$${amount.toFixed(2)}; - };
- // Requirement evolves: support multiple currencies
- const formatCurrency = (amount: number, currency: string): string => {
- const symbols = { USD: '$', EUR: '€', GBP: '£' };
- return
${symbols[currency]}${amount.toFixed(2)}; - };
- // Requirement evolves: support localization
- const formatCurrency = (amount: number, locale: string): string => {
- return new Intl.NumberFormat(locale, {
- style: 'currency',
- currency: locale === 'en-US' ? 'USD' : 'EUR',
- }).format(amount);
- };
- Complexity added only when needed
- Premature Abstraction
- class GenericRepository { /
- 300 lines
- / }
- class QueryBuilder { /
- 200 lines
- / }
- // ... building entire ORM for single table
- Massive abstraction for uncertain future
- ```typescript
- // Simple functions for current needs
- const getUsers = async (): Promise
=> { - return db.query('SELECT * FROM users');
- };
- const getUserById = async (id: string): Promise
=> { - return db.query('SELECT * FROM users WHERE id = $1', [id]);
- };
- // When pattern emerges across multiple entities, then abstract
- Abstract only when pattern proven across 3+ cases
- Performance Optimization
- // Benchmark shows: 50ms for 1000 users (acceptable)
- // ✓ Ship it, no optimization needed
- // Later: After profiling shows this is bottleneck
- // Then optimize with indexed lookup or caching
- Optimize based on measurement, not assumptions
- ```typescript
- // Premature optimization
- const filterActiveUsers = (users: User[]): User[] => {
- // "This might be slow, so let's cache and index"
- const cache = new WeakMap();
- const indexed = buildBTreeIndex(users, 'isActive');
- // 100 lines of optimization code
- // Adds complexity, harder to maintain
- // No evidence it was needed
- };
- Complex solution for unmeasured problem
- In Practice
- When implementing:
- Solve the immediate problem
- Use straightforward approach
- Resist "what if" thinking
- Delete speculative code
- When optimizing:
- Profile first, optimize second
- Measure before and after
- Document why optimization needed
- Keep simple version in tests
- When abstracting:
- Wait for 3+ similar cases (Rule of Three)
- Make abstraction as simple as possible
- Prefer duplication over wrong abstraction
- Refactor when pattern clear
- Integration with Commands
- The Kaizen skill guides how you work. The commands provide structured analysis:
- /why
-
- Root cause analysis (5 Whys)
- /cause-and-effect
-
- Multi-factor analysis (Fishbone)
- /plan-do-check-act
-
- Iterative improvement cycles
- /analyse-problem
-
- Comprehensive documentation (A3)
- /analyse
- Smart method selection (Gemba/VSM/Muda) Use commands for structured problem-solving. Apply skill for day-to-day development. Red Flags Violating Continuous Improvement: "I'll refactor it later" (never happens) Leaving code worse than you found it Big bang rewrites instead of incremental Violating Poka-Yoke: "Users should just be careful" Validation after use instead of before Optional config with no validation Violating Standardized Work: "I prefer to do it my way" Not checking existing patterns Ignoring project conventions Violating Just-In-Time: "We might need this someday" Building frameworks before using them Optimizing without measuring Remember Kaizen is about: Small improvements continuously Preventing errors by design Following proven patterns Building only what's needed Not about: Perfection on first try Massive refactoring projects Clever abstractions Premature optimization Mindset: Good enough today, better tomorrow. Repeat.