# Frontend System Design співбесіда: фреймворк відповіді та приклад розбору

> Що насправді перевіряють на frontend system design інтерв’ю, покроковий фреймворк відповіді від вимог до observability і повний приклад: стрічка з нескінченним скролом. З таблицею компромісів і рубрикою самооцінки.

- Автор: Юра Скиба (https://cookiesoftware.io)
- Опубліковано: 2026-09-24
- Категорія: Backend і System Design
- Canonical: https://cookiesoftware.io/blog/frontend-system-design-interview

---

**Коротка відповідь.** Frontend system design – це не про малювання квадратиків із «load balancer». Це розмова про те, як ти проєктуєш клієнтський застосунок: дані та API, стан, рендеринг і кеш, продуктивність, доступність, стани помилок. Виграшна стратегія – йти по явному фреймворку, вголос називати компроміси і не ускладнювати без запиту інтерв'юера. Нижче – сам фреймворк і повний приклад розбору стрічки з нескінченним скролом.

## Що перевіряють насправді

Інтерв'юер дивиться не на «правильну архітектуру» (її не існує), а на чотири речі:

- чи вмієш ти **звужувати розмиту задачу** до чітких вимог;
- чи бачиш **компроміси**, а не один завчений варіант;
- чи думаєш про **несчасливі сценарії**: помилки, повільну мережу, порожні стани;
- чи можеш **спілкуватися** як із колегою: структура, малі кроки, реакція на підказки.

Це той самий набір, який відрізняє Senior від Middle у щоденній роботі – докладніше про цю різницю в статті про [перехід Middle → Senior](/blog/middle-to-senior-frontend).

## Фреймворк відповіді: 8 кроків

1. **Вимоги.** Хто користувач? Які ключові сценарії? Що точно поза скоупом? Реальний час чи можна оновлювати за запитом? Які пристрої й мережі?
2. **API і дані.** Форма даних, пагінація (offset чи cursor), що віддає бекенд, що доводиться агрегувати на клієнті.
3. **Стан.** Що є server state (кешовані відповіді API), що – client state (фільтри, введення). Де він живе і хто ним володіє.
4. **Рендеринг і кеш.** SSR/CSR/гібрид і чому; що кешуємо, коли інвалідовуємо, що робимо зі stale-даними.
5. **Performance.** Що вантажимо одразу, що ліниво; списки – віртуалізація; зображення – розміри й формати; метрики, за якими стежимо (LCP, INP, CLS – визначення є на [web.dev](https://web.dev/articles/vitals)).
6. **Доступність.** Клавіатура, фокус, семантика, анонси динамічних змін.
7. **Стани помилок.** Loading / empty / error / partial для кожного блоку даних; ретраї; офлайн-поведінка, якщо релевантно.
8. **Observability.** Як дізнаємось, що в користувачів щось зламалося: логування помилок, метрики продуктивності, алерти.

Не обов'язково проходити всі вісім глибоко – назви структуру на початку і запитай інтерв'юера, куди пірнати. Це сам по собі сильний сигнал.

## Приклад розбору: стрічка з нескінченним скролом

Задача: «спроєктуй стрічку постів, як у соцмережі».

### Крок 1 – вимоги (2–3 хвилини, вголос)

Питання, які варто поставити: стрічка персоналізована чи однакова для всіх? Потрібен реальний час чи оновлення при заході? Чи є взаємодії (лайк, коментар)? Мобільні користувачі з повільною мережею – цільова аудиторія? Припустимо відповіді: персоналізована, реальний час не потрібен, є лайк, мобільні важливі.

### Крок 2 – API

Cursor-пагінація замість offset: у стрічку постійно додаються нові пости, offset «з'їжджає» і дає дублікати. Контракт:

```ts
type FeedResponse = {
  items: Post[];
  nextCursor: string | null; // null – кінець стрічки
};

type Post = {
  id: string;
  author: { id: string; name: string; avatarUrl: string };
  text: string;
  media: { url: string; width: number; height: number } | null;
  likeCount: number;
  likedByMe: boolean;
  createdAt: string;
};
```

`width`/`height` медіа у відповіді – не дрібниця: без них картки стрибають під час завантаження зображень (CLS).

### Крок 3 – стан

Server state: сторінки стрічки, кешовані за cursor. Client state: позиція скролу, чернетка коментаря. Лайк – оптимістичне оновлення: міняємо UI одразу, відкочуємо при помилці запиту.

### Крок 4 – компонентна структура

```
FeedPage
├── FeedList        (керує сторінками, sentinel для догрузки)
│   └── PostCard    (чистий, отримує все через props)
├── NewPostsBanner  («З'явилися нові пости» – за кліком, не зсуваючи стрічку)
└── FeedErrorState / FeedSkeleton
```

Догрузка – IntersectionObserver на sentinel-елементі внизу; кнопка «Показати ще» як fallback для доступності.

### Крок 5 – довгий список

Нескінченний скрол означає, що DOM росте необмежено. Називаємо проблему і два рівні рішення: простіший – відвантажувати далекі сторінки, зберігаючи їхню висоту плейсхолдером; повніший – віртуалізація (рендеримо лише видиме вікно елементів). На співбесіді достатньо пояснити принцип і чесно сказати, що в реальному проєкті взяв би перевірену бібліотеку, а не писав із нуля.

### Кроки 6–8 – стани, доступність, спостережуваність

Кожна сторінка стрічки має власні loading/error стани: помилка догрузки не має вбивати вже показане – показуємо існуючі пости + «Не вдалося завантажити, спробувати ще». Порожня стрічка – окремий екран з дією. Доступність: пости – семантичні `<article>`, лайк – `<button>` з `aria-pressed`, нові елементи не крадуть фокус. Спостережуваність: репортинг JS-помилок і Web Vitals у аналітику.

## Таблиця компромісів (корисно проговорити вголос)

| Рішення | Альтернатива | Ціна обраного |
|---|---|---|
| Cursor-пагінація | Offset | Складніше «стрибнути на сторінку N» – стрічці не потрібно |
| Оптимістичний лайк | Чекати відповідь сервера | Потрібен відкат при помилці |
| IntersectionObserver | Скрол-listener | Майже жодної: observer простіший і дешевший |
| Віртуалізація | Рендер усього | Складність, крихкість динамічних висот |
| CSR стрічки після SSR-оболонки | Повний SSR кожної сторінки | Перший контент трохи пізніше; для приватної стрічки SEO не потрібне |

## SSR чи CSR: як аргументувати вибір рендерингу

Це питання виринає майже в кожному design-раунді, тому підготуй логіку заздалегідь:

- **SSR** виграє, коли перший контент критичний (публічні сторінки, SEO, повільні пристрої): HTML приходить готовим, до виконання JS.
- **CSR** достатній для застосунків за логіном: SEO не потрібне, а після першого завантаження навігація дешевша.
- **Гібрид** – найчастіша чесна відповідь: SSR-оболонка і перша порція даних, далі клієнтські переходи. Для нашої стрічки: якщо вона за логіном – SSR повної стрічки не окупається; якщо публічна – перший екран варто рендерити на сервері.

Аргументуй завжди через користувача й вимоги, а не через «так модно»: інтерв'юер чує різницю миттєво.

## Типові помилки на design-раунді

1. **Малювати без вимог.** Перші хвилини без жодного питання – найчастіший провал раунду.
2. **Стартувати з технологій.** «Візьму Redux і GraphQL» до того, як зрозуміла форма даних, – рішення без задачі.
3. **Щасливий шлях і все.** Жодного слова про помилки мережі, порожні стани, повільні пристрої.
4. **Перепроєктування.** Віртуалізація, офлайн-режим і мікрофронтенди у перші п'ять хвилин простої задачі. Складність має з'являтися у відповідь на вимогу, а не про запас.
5. **Мовчазні рішення.** Кожне «беру X» без «замість Y, тому що…» – втрачений бал.

## Рубрика самооцінки

Після тренування оціни себе за чотирма пунктами: (1) чи поставив я хоч три уточнювальні питання до того, як малювати; (2) чи назвав хоча б один компроміс на кожне велике рішення; (3) чи спроєктував error/empty/loading, не чекаючи підказки; (4) чи вклався в таймінг, лишивши час на питання. Два «ні» – значить, тренуватися ще рано на реальній співбесіді.

Design-раунд тісно пов'язаний з архітектурною розмовою про компоненти й межі відповідальності – це окрема тема в [React-архітектурі для Senior](/blog/senior-react-architecture). А якщо design-питання прилітають у контексті бекенда, подивись [розбір Node.js-співбесіди](/blog/nodejs-backend-interview).

## Практичне завдання

Візьми продукт, яким користуєшся щодня, і спроєктуй один його екран за фреймворком вище – письмово, за 40 хвилин, з таймером. Потім перечитай як інтерв'юер: де ти прийняв рішення мовчки, без альтернативи? Саме ці місця на реальній співбесіді викликають питання «а чому так?», на які боляче не мати відповіді.
