Library
Learning Patterns · 12 of 12
Learning Patterns
UI/UX Design CRITICAL

Rules of Thumb

Learning Patterns Lydia Hallie & Addy Osmani
heuristics decision-rules quick-reference

Key Principle

Pattern selection must be driven by measurable performance impact and practical need, not theoretical elegance. Patterns are time-tested templates, not silver bullets. Every choice exists on a spectrum with hybrid middle-ground solutions; treat decisions as tradeoffs, not binaries.

Why This Matters

Wrong pattern choices cascade: a design pattern decision (e.g., Provider vs. prop drilling) affects hydration cost, which affects rendering strategy viability, which determines performance ceiling. These three layers -- design, rendering, performance -- are interconnected, and a heuristic at any layer saves compounding mistakes downstream.

Good Examples

Design Patterns

  • Replace HOCs and render props with custom Hooks for flatter component trees and co-located logic (Ch. 14)
  • Use Object.freeze + ES modules instead of Singleton classes for global state without testing fragility (Ch. 4)
  • Use Provider Pattern (Context API) to eliminate prop drilling; extract custom context hooks with validation (Ch. 6)
  • Compound Pattern via Context for multi-part components with shared state (Ch. 18)

Rendering

  • CSR-to-SSR is a spectrum: CSR for TTI-tolerant apps, SSR for SEO/FCP, SSG/iSSG for content sites, Islands for mostly-static with interactive pockets (Ch. 20-31)
  • Progressive and Selective Hydration close the "uncanny valley" between FCP and TTI (Ch. 26, 29)
  • Islands Architecture achieved 83% JS reduction vs. Next.js/Nuxt.js in benchmarks (Ch. 31)

Performance

  • Route-based splitting first, then component-level splitting by visibility/interaction (Ch. 37-38)
  • List virtualization for 1000+ items: react-window yields ~100x speedup for 10K rows (Ch. 43)
  • Preconnect over preload for cross-origin resources: 16.61% LCP improvement in case study (Ch. 30)

Counterpoints

  • Not all "optimizations" improve performance: native lazy loading and react-scroll caused regressions in the case study (Ch. 30)
  • Proxy Pattern incurs measurable performance overhead; avoid in hot paths (Ch. 5)
  • Provider Pattern causes re-renders for all consumers on frequently-changing values; split contexts by update frequency (Ch. 6)
  • HTTP/2 server push is not HTTP cache aware; service workers are the workaround, but the long-term solution remains unclear (Ch. 39)
  • Ideal code-splitting granularity is not well-defined; too fine creates request overhead, too coarse defeats the purpose (Ch. 38)

Key Quotes

  • "Traditional software design patterns must be re-evaluated for the modern JavaScript and React ecosystem, where many classical OOP patterns are either obsolete, replaced by language features (ES modules, Hooks), or need adaptation to component-based architectures." (EXTRACTION_SUMMARY.md, Core Thesis)
  • "The book treats performance not as an afterthought but as a first-class architectural concern, connecting design decisions directly to measurable metrics (FCP, LCP, TTI, TBT, CLS)." (running-context.md, Recurring Themes)
  • "Patterns are time-tested templates but not silver bullets; measure before adopting." (book-spine.md, Ch. 44 Conclusions)
  • "Intuition about performance is unreliable and some 'optimizations' actually cause regressions." (running-context.md, Recurring Themes)

Rules of Thumb

Pattern Selection

  1. If a class-based pattern (Singleton, Mixin, Container/Presentational) has a Hook or ES module equivalent, prefer the modern alternative.
  2. Use the Compound Pattern when a component has 3+ tightly coupled sub-parts sharing state; otherwise plain props suffice.
  3. Observer Pattern trades debugging opacity for loose coupling -- use RxJS in production, not hand-rolled implementations.
  4. Flyweight, Factory, and Command patterns have diminishing relevance in modern JS; reach for them only when the classical use case is exact.

Rendering Strategy

  1. Choose by content type: static content -> SSG/iSSG; dynamic personalized content -> SSR; app-like interaction -> CSR; mostly static with interactive pockets -> Islands.
  2. Use iSSG (stale-while-revalidate + lazy generation) to avoid full rebuilds on content-heavy sites.
  3. Streaming SSR (renderToNodeStream) reduces TTFB vs. renderToString; prefer it when server infrastructure supports chunked transfer.
  4. React Server Components complement SSR -- they are not a replacement. Use them for zero-bundle-size server logic.

Performance Optimization

  1. Always measure before and after. Baseline -> incremental change -> re-measurement. No exceptions.
  2. Audit third-party libraries for bundle impact before adding them. Case study: replacing react-slick with react-glider saved 78% bundle size.
  3. Inline critical CSS, defer the rest. Render-blocking CSS delays FCP.
  4. Use Import on Interaction (facade pattern) for third-party widgets: auth dialogs, chat widgets, analytics.
  5. Use Import on Visibility (IntersectionObserver) for below-the-fold components.
  6. Apply the PRPL pattern for HTTP/2: Push critical resources, Render initial route, Pre-cache with service workers, Lazy-load remaining routes.
  7. Tree shaking only works with ES modules and is blocked by side effects. Set the sideEffects field in package.json.

Loading Priority

  1. Split bundles at route boundaries first -- natural navigation pauses mask loading latency.
  2. Preload late-discovered critical resources (fonts, hero images). Prefetch anticipated next-page resources during idle time.
  3. Align resource loading order to FCP -> LCP -> FID milestones; eliminate dead time between them.

Related References

  • Core Web Vitals Case Study (Ch. 30) for end-to-end optimization walkthrough
  • Hooks Pattern (Ch. 14) for the universal design pattern bridge
  • Islands Architecture (Ch. 31) for the rendering strategy frontier
  • PRPL Pattern (Ch. 39) for HTTP/2-optimized delivery
  • Loading Sequence Optimization (Ch. 32) for resource priority ordering