Build for scalability and reuse.
Scalability: code that survives growth in features, contributors, and data. Patterns: feature-folder structure, typed contracts at boundaries, small modules with single responsibilities, dependency direction (UI → domain → infra), monitoring, perf budgets, design-system tokens. Reuse: compose, don't configure; ship primitives + presets; document; favor narrow APIs over deep config.
"Build for scalability and reuse" is asked vaguely — give a concrete answer with actual patterns, not platitudes.
1. Architecture: dependency direction
Code becomes unmaintainable when dependencies flow in every direction. Set a clear direction:
UI (components / pages)
↓ depends on
Domain (business logic, types)
↓ depends on
Infrastructure (HTTP, storage, third-party SDKs)UI calls into the domain; the domain calls into infrastructure. Infrastructure never calls into UI. Domain never imports React. This survives team scaling because contributors can change the UI without breaking the domain, and swap infra (Stripe → Adyen) without touching either.
2. Feature folders, not type folders
✗ components/ hooks/ utils/ api/ (cross-cutting; hard to find ownership)
✓ features/cart/ features/checkout/ features/profile/ (cohesive; clear ownership)Code that changes together lives together. New teammates find related code in one place. Refactors and deletions are surgical.
3. Typed contracts at boundaries
- API responses → Zod / typed schemas.
- Props → TypeScript.
- Events between modules → typed event bus.
Types at the seam catch the bugs that compound at scale.
4. Small modules with one job
- Functions do one thing.
- Components own one concern (view, interaction shell, or feature wrapper).
- Hooks return one piece of state or one capability.
When a file approaches 300 lines or 5+ exports, that's a signal to split.
5. Composition over configuration
For UI primitives, compose:
<Card><Card.Header/><Card.Body/></Card>not:
<Card header={...} body={...} showHeader />Compound APIs scale; flag-laden APIs collapse.
6. Design system
Tokens (colors, spacing, type) + primitives (Button, Input, Card) + presets (Card variants) gives consistency across the app without N teams each rolling their own.
7. Perf and quality budgets
- Bundle size budget per route — CI enforces.
- LCP/INP/CLS budgets — block PRs that regress.
- Test coverage required on critical paths.
Without budgets, gains regress within a quarter.
8. Observability from day one
- Sentry for errors.
- web-vitals for RUM.
- Server-side traces.
- Feature analytics.
You can't scale what you can't see.
9. Documentation and discoverability
- Storybook / playground for components.
- ADRs (Architecture Decision Records) for decisions.
- README per feature folder explaining the intent.
Reuse fails when nobody knows what exists.
10. Narrow APIs over deep config
Every prop, every config flag, every option is a maintenance commitment. Add only when a second caller demands it. YAGNI at the API level.
11. Avoid premature abstraction
Three near-duplicates is fine — the abstraction is clearer once you've seen the actual shapes. Two is usually too early.
12. Reuse boundaries — when NOT to reuse
- Two callers with diverging requirements — fork; don't bend one component to fit both.
- One-off components — don't promote to "reusable" if there's no second caller.
- Cross-domain reuse — extracting an Admin component to share with the Marketing site usually couples them badly. Different products, different lifecycles.
13. Build vs adopt
A design system, a state library, a router — for most of these, adopting (Radix, Zustand, TanStack) wins over building. Build only where your problem is genuinely distinctive.
14. Team-scaling concerns
- Codeowners so the right people review the right code.
- Conventions in lint and formatter so churn is small.
- Migration paths documented (Codemod / interim adapter) when patterns change.
- PR templates that prompt for tests, screenshots, and reasoning.
Interview framing
"Scalability is about surviving growth in features, contributors, and data. Set a dependency direction — UI → domain → infrastructure — so UI changes don't break domain logic and infra is swappable. Organize by feature folder, not by type, so code that changes together lives together. Type the boundaries (props, API responses) so bugs surface at the seam. Compose primitives instead of configuring monoliths. Budget perf and bundle size in CI. Observability — Sentry, web-vitals, traces — from day one; you can't scale what you can't see. For reuse: ship narrow APIs, add props only when a second caller asks, and don't promote one-offs. Adopt libraries (Radix, Zustand) when your problem isn't distinctive. And avoid premature abstraction — three near-duplicates is when the shape is clearest."
Follow-up questions
- •Walk through your folder structure for a non-trivial app.
- •When have you resisted abstracting something, and why?
- •How do you enforce perf budgets across teams?
- •Compose vs configure — an example.
Common mistakes
- •Type-based folders (components/, hooks/) that lose cohesion at scale.
- •Premature abstraction — three duplicates is the right threshold.
- •Configurability creep — too many props.
- •Coupling reusable components to global state.
- •No budgets or observability.
Performance considerations
- •Budgets in CI; observability in prod; code splitting per route; design tokens to avoid heavy theming code.
Edge cases
- •Multi-product monorepo — when to share vs fork.
- •Component that's reusable but slow — perf vs reuse trade-offs.
- •Migrating from one pattern to another mid-codebase.
Real-world examples
- •Vercel/Next.js architecture; Radix UI / shadcn primitives + presets.
- •Bazel-style monorepos; CODEOWNERS-driven review.