Frontend
easy
mid
Build a Code structure & state management
Feature-first folders, data/logic/presentation separation, and deliberate state placement: local by default, lift when shared, server-cache library for server state, Context for low-frequency globals, a store for complex global client state, URL for shareable state.
6 min read·~15 min to think through
This pairs two related decisions usually graded together in an exercise: how the code is organized and where state lives.
Code structure — feature-first
ts
src/
features/
<feature>/
components/ # feature-specific UI
hooks/ # feature data/logic hooks
api/ # API calls for this feature
types.ts
index.ts # public surface
shared/
ui/ # design-system primitives
hooks/ # cross-cutting hooks
lib/ # utils
app/ # routing, providers, layout- Group by feature, not file type — everything a feature needs is colocated; deleting a feature is deleting a folder.
- Layer within a feature: data/logic hooks → presentational components → page composition.
- Explicit public API (
index.ts) so features don't import each other's internals.
State management — classify, then place
The skill is matching each kind of state to the cheapest mechanism that works:
| State kind | Where it lives |
|---|---|
| Local UI (toggles, inputs, hover) | useState/useReducer in the component |
| Shared by a subtree | lifted to nearest common parent |
| Server data | React Query / SWR (cache, dedup, refetch) |
| Low-frequency global (theme, user, locale) | Context |
| Complex/high-frequency global client state | Zustand / Redux Toolkit / Jotai (selectors) |
| Shareable / refresh-safe (filters, tab, page) | URL params |
Principles
- Local-first — don't globalize state preemptively; most "global state" is just state lifted too far.
- Server state ≠ client state — caching it in Redux means hand-rolling a cache; use a server-cache library.
- Context re-renders all consumers — split contexts or use a selector store for hot values.
- Derive, don't store — computed values via
useMemo/selectors, not duplicated state. - Colocate — keep state near where it's used.
How they connect
Structure and state reinforce each other: feature folders own their data hooks; shared global state lives in app/ providers; presentational components stay stateless and reusable. A reviewer wants to see that both decisions were deliberate and consistent, not accidental.
Follow-up questions
- •Why feature-first over type-first folders?
- •How do you decide between Context and a store?
- •Why keep server state out of your client store?
- •What belongs in the URL rather than in component state?
Common mistakes
- •Type-first folders that don't scale.
- •Globalizing state that only one subtree uses.
- •Server data in a global store with hand-rolled loading flags.
- •Storing derived values instead of computing them.
Performance considerations
- •Context re-renders every consumer — split it or use selector-based stores for frequently-changing values. Server-cache libraries dedupe and avoid waterfalls. Colocation and small components keep memoization effective.
Edge cases
- •State shared by exactly two features — lift to shared or duplicate?
- •SSR/hydration — initial state must match server and client.
- •Optimistic updates blurring server/client state lines.
- •Cross-tab state synchronization.
Real-world examples
- •A feature folder owning useFeatureData (React Query) + presentational components; theme/auth in app-level Context; filters in the URL.
Senior engineer discussion
Seniors present structure and state as one coherent design: feature-first organization with layered concerns and explicit public APIs, plus state classified by kind and placed in the cheapest fitting mechanism. The senior signals are the server-vs-client distinction, local-first defaults, treating the URL as state, and deriving rather than storing.
Related questions
Frontend
Medium
7 min
Frontend
Medium
6 min
Frontend
Hard
6 min