Frontend System Design
A structured framework for approaching frontend system design interviews and real-world architecture decisions.
Frontend system design is the practice of defining how a frontend application is structured, how data flows, and how components interact. Whether you're in an interview or architecting a real product, a clear framework helps you make consistent, scalable decisions.
A Framework for Frontend System Design
Approach any design problem in stages: clarify requirements, identify constraints, propose a high-level architecture, then drill into key areas. Always start with the user and the problem—technology choices should follow from those.
Step 1: Clarify Requirements
Ask about functional requirements: What should the app do? Who are the users? What are the core user flows? Then probe non-functional requirements: performance targets (e.g., LCP under 2.5s), accessibility (WCAG level), offline support, and scale (DAU, concurrent users).
Step 2: Define the Scope
Decide what's in scope for this design. Is it a greenfield app or an incremental change? Are you designing the full stack or only the frontend? Establishing boundaries prevents scope creep and keeps the discussion focused.
Step 3: Propose a High-Level Architecture
Sketch the main layers: UI (pages and components), state and data layer, API layer, and any cross-cutting concerns (auth, routing, error handling). Show how user actions flow through the system and how data comes back.
Component Hierarchy and Structure
How you organize components directly affects maintainability and reuse.
Atomic Design and Beyond
Brad Frost's Atomic Design (2016) introduced a hierarchy from atoms (buttons, inputs) to molecules, organisms, templates, and pages. You can also use feature-based folders, domain-driven structure, or a hybrid. The goal is clear ownership and discoverability.
Composition Over Configuration
Prefer composition—small, focused components combined together—over large components with many props. This improves testability and reuse. Use slots, render props, or compound components when configuration alone isn't enough.
Separation of Concerns
Keep presentation (how it looks) separate from logic (state, side effects). Use custom hooks for reusable logic and keep components mostly presentational. This makes testing and refactoring easier.
Data Flow and State Management
State is central to frontend architecture. Choose the right strategy for each type of state.
Types of State
- Server state—data from APIs; use a library like TanStack Query (React Query) or SWR for caching and sync.
- Client state—UI state, user preferences; consider local state, context, or a global store.
- URL state—filters, tabs, modals; keep in the URL when shareable or bookmarkable.
Unidirectional Data Flow
Prefer one-way data flow: parent components pass data down via props; child components emit events or callbacks up. Avoid prop drilling with context or state management libraries, but don't overuse global state—co-locate state as close to where it's used as possible.
API Design and Data Fetching
Design your data layer with clear boundaries. Use typed API clients, handle loading and error states consistently, and consider colocating data requirements with components (e.g., via data loaders or Suspense). Think about optimistic updates, retries, and offline behavior early.
Example: Designing a Dashboard Application
Consider a dashboard with charts, filters, and a data table.
Component hierarchy: A DashboardPage composes Filters, ChartContainer, and DataTable. Each can be broken down further—ChartContainer might use BarChart or LineChart based on config.
Data flow: Filters live in URL state (shareable). Chart and table data are server state, fetched based on filter values. Use a query library with keyed queries so filter changes trigger refetches. Loading skeletons and error boundaries wrap each data section.
State management: Filter state in the URL; chart/table data from the server with TanStack Query; local UI state (e.g., expanded rows) in component state or a small store.
Performance: Lazy-load chart libraries; virtualize the table for large datasets; debounce filter changes before refetching. Consider prefetching likely filter combinations.
Error Handling and Resilience
A good design explicitly handles failure modes. Define error boundaries so a failing component doesn't take down the whole app. Decide how to surface API errors: inline messages, toasts, or a dedicated error state. Consider retry strategies, offline support, and fallback UI when data is missing. Document which errors are recoverable and which require user action. Consistency in error handling improves both UX and maintainability.
Evolving the Design Over Time
Requirements change; so should the architecture, within reason. Prefer incremental evolution over big rewrites: add new routes, extract new components, or introduce a state library where it clearly helps. Avoid over-engineering for hypothetical future needs—design for the next 6–12 months, and leave clear extension points. When technical debt accumulates, schedule time to refactor; tie it to a feature so the work is visible and justified. The best frontend system design is one that can adapt without constant re-architecture. Start with a clear problem statement, propose a coherent structure, and refine based on trade-offs. The best designs balance simplicity, maintainability, and the ability to evolve as requirements change.
Related articles
- Frontend ArchitectureFeature Flags and A/B Testing
Ship behind flags, run experiments, and roll out features gradually. How to implement feature flags and A/B testing in frontend applications.
Read article - Frontend ArchitectureInternationalization (i18n)
Supporting multiple languages and regions: locales, formatting, RTL, and translation workflows for frontend applications.
Read article - Frontend ArchitectureMicro-Frontends
When and how to adopt micro-frontends: implementation approaches, trade-offs, and common challenges.
Read article - Frontend ArchitectureState Management at Scale
Master state architecture for large frontend applications: when to use local vs global state, library trade-offs, server state, and state machine patterns.
Read article