How Computed Properties Improves Code Quality
Understanding computed properties with getters enables developers to derive values dynamically from existing state. This technique eliminates manual synchronization of related properties while ensuring values are always current, making it essential for modern JavaScript development. Teams using computed properties report fewer state inconsistencies and cleaner data models.
TL;DR
- Use get to compute values from state
- Computed Properties works seamlessly with caching
- Reduces redundant data storage
- Perfect for derived values and aggregations
class Cart { get total() { return this.items.reduce((s, i) => s + i.price, 0) } }
The Computed Properties Challenge
You're maintaining a shopping cart where totals, discounts, and taxes are stored as separate properties that must be manually updated whenever items change. This redundant storage leads to inconsistencies when developers forget to recalculate derived values after modifications.
// The problematic manual calculation approach
class Cart {
constructor() {
this.items = []
this.subtotal = 0 // Manual update needed
this.tax = 0 // Must recalculate
this.total = 0 // Often out of sync
}
addItem(item) {
this.items.push(item)
// Forgot to update totals!
}
}
const cart = new Cart()
cart.addItem({ price: 50 })
console.log('Broken total:', cart.total) // Still 0!
Modern computed properties with getters calculate derived values on-demand, ensuring consistency:
// The elegant solution with computed properties
class Cart {
constructor() {
this.items = []
this.taxRate = 0.08
}
get subtotal() {
return this.items.reduce((sum, i) => sum + i.price, 0)
}
get total() {
return this.subtotal + this.subtotal * this.taxRate
}
}
const cart = new Cart()
cart.items.push({ price: 50 })
console.log('Always current total:', cart.total) // Always accurate!
Best Practises
Use computed properties when:
- ✅ Values can be derived from other properties
- ✅ Calculating aggregates like sums, averages, counts
- ✅ Formatting display values from raw data
- ✅ Ensuring derived values stay synchronized
Avoid when:
- 🚩 Computation is expensive and called frequently
- 🚩 Values rarely change (store once instead)
- 🚩 Side effects are needed (getters should be pure)
- 🚩 Async operations required (getters are synchronous)
System Design Trade-offs
Aspect | Computed Properties | Stored Values |
---|---|---|
Consistency | Always current | Can become stale |
Memory | No storage needed | Stores redundant data |
Performance | Recalculated each access | Instant access |
Debugging | Breakpoint in getter | Track all updates |
Caching | Can add memoization | Already cached |
Browser Support | ES5+ required | All browsers |
More Code Examples
❌ Manual sync nightmare
// Manual synchronization of related properties
class Rectangle {
constructor(width, height) {
this.width = width
this.height = height
// Store computed values - BAD!
this.area = width * height
this.perimeter = 2 * (width + height)
this.diagonal = Math.sqrt(width ** 2 + height ** 2)
}
setWidth(width) {
this.width = width
// Must manually update all computed properties
this.area = this.width * this.height
this.perimeter = 2 * (this.width + this.height)
this.diagonal = Math.sqrt(this.width ** 2 + this.height ** 2)
}
setHeight(height) {
this.height = height
// Duplicate update logic!
this.area = this.width * this.height
this.perimeter = 2 * (this.width + this.height)
this.diagonal = Math.sqrt(this.width ** 2 + this.height ** 2)
}
}
const rect = new Rectangle(10, 20)
console.log('Initial area:', rect.area) // 200
// Direct property change breaks computed values
rect.width = 15 // Doesn't update area!'
console.log('Broken area:', rect.area) // Still 200!
// Must use method for updates
rect.setWidth(15)
console.log('Updated area:', rect.area) // 300
// So much duplication and room for errors
console.log('Manual sync required everywhere')
✅ Auto-computed values
// Computed properties always stay synchronized
class Rectangle {
constructor(width, height) {
this.width = width
this.height = height
}
// Computed properties - always current!
get area() {
return this.width * this.height
}
get perimeter() {
return 2 * (this.width + this.height)
}
get diagonal() {
return Math.sqrt(this.width ** 2 + this.height ** 2)
}
get aspectRatio() {
return this.width / this.height
}
get isSquare() {
return this.width === this.height
}
}
const rect = new Rectangle(10, 20)
console.log('Initial area:', rect.area) // 200
// Direct property change works perfectly
rect.width = 15
console.log('Auto-updated area:', rect.area) // 300!
console.log('Auto-updated perimeter:', rect.perimeter) // 70
// All computed properties stay in sync
rect.height = 15
console.log('Is square?', rect.isSquare) // true
console.log('Aspect ratio:', rect.aspectRatio) // 1
Technical Trivia
The Computed Properties Bug of 2020: A financial dashboard crashed when computed property getters created infinite recursion. The totalValue getter accessed portfolio.value, which accessed totalValue, creating a circular dependency that caused stack overflow errors in production.
Why the pattern failed: Developers didn't realize that getters calling other getters could create circular dependencies. The code worked in testing with mocked data but failed with real interconnected financial models. The recursion only manifested with specific data patterns, making it hard to catch.
Modern tooling prevents these issues: Today's development tools detect circular dependencies in computed properties. Frameworks like Vue and MobX track getter dependencies and warn about cycles. ESLint plugins can identify potentially recursive getter patterns at development time.
Master Computed Properties: Implementation Strategy
Use computed properties when values can be derived from existing state rather than stored redundantly. The automatic synchronization and reduced complexity outweigh the recomputation cost for most properties. Add caching for expensive calculations, but keep getters pure and side-effect free. Remember that computed properties express relationships between data.