Перейти к содержанию
Фронтенд

8 вопросов по теме «Frontend: Системный дизайн» на собеседовании

В этом материале — 8 вопросов из русской колоды RecallDeck по теме «Frontend: Системный дизайн». Сначала сформулируйте короткий ответ сами, затем откройте подробный разбор и проверьте примеры, ограничения и отказные случаи.

7 мин чтения8 подробных ответовПроверено 24 августа 2026
Главная мысль

Связывайте ответ с браузером, доступностью, производительностью и пользовательским состоянием — именно там видна зрелость frontend-инженера.

Вопросы и ответы

8 подробных ответов

01

Как подойти к задаче на frontend system design на собеседовании?

Короткий ответ: Не бросайся рисовать UI. Двигайся по фреймворку RADIO: сначала проясни требования, затем зафиксируй контракт данных/API, потом компонентную архитектуру, состояние и в конце — сквозные аспекты (производительность, доступность, состояния ошибок).

Подробно:

  1. Requirements (требования) — функциональные и нефункциональные: кто пользователь, сколько данных, офлайн, целевые устройства, ключевые метрики (TTI, частота обновлений). Запиши их явно.
  2. Architecture / API — нарисуй высокоуровневые блоки и контракт данных: эндпоинты, форма ответа, пагинация (cursor vs offset), кто владеет состоянием.
  3. Data model — нормализованный server state и отдельный client/UI state; что кэшируем и как инвалидируем.
  4. Interface (компоненты) — дерево компонентов, props и события, переиспользуемость, границы загрузки.
  5. Optimizations — виртуализация, code splitting, дебаунс, отмена гонок запросов, a11y, loading/empty/error.
Clarify ──► API/Data ──► Components ──► State ──► Cross-cutting
requirements   contract     tree          server/UI   perf · a11y · errors

⚠️ Частая ошибка: прыгать к вёрстке UI, не проговорив требования и контракт данных — интервьюер ждёт именно структурированного движения сверху вниз.

02

Как спроектировать компонентную архитектуру: композиция, container/presentational, compound-компоненты?

Короткий ответ: Предпочитай композицию наследованию, разделяй логику и представление, а для связанных групп компонентов используй compound-паттерн через context — это убирает prop drilling и даёт гибкий, переиспользуемый API.

Подробно:

  • Композиция вместо наследования — собирай UI из мелких компонентов и children/слотов, не плоди иерархии классов.
  • Container vs presentational — контейнер тянет данные и держит состояние, presentational получает props и только рисует; так проще тестировать и переиспользовать.
  • Compound components<Tabs><Tab/></Tabs> делят неявное состояние через context, а не через десяток props.
  • Против prop drilling — поднимай состояние ровно настолько, насколько нужно; глубокую передачу решай через context или композицию, а не сквозными props.
const TabsCtx = createContext<TabsState | null>(null);

function Tabs({ defaultValue, children }: TabsProps) {
  const [value, setValue] = useState(defaultValue);
  return <TabsCtx value={{ value, setValue }}>{children}</TabsCtx>;
}
Tabs.List = TabList;   // compound: общее состояние через context
Tabs.Trigger = TabTrigger;
Tabs.Panel = TabPanel;

⚠️ Частая ошибка: превращать один компонент в «бога» с 20 props на все случаи — лучше расщепить на compound-части с понятным контрактом.

03

Как организовать состояние в крупном SPA: server state, client/UI state, кэш и нормализация?

Короткий ответ: Раздели состояние на server state (данные с бэкенда) и client/UI state (локальный интерфейс). Server state отдай специализированному кэшу (React Query/SWR/RTK Query), а реляционные данные нормализуй по id, чтобы не было рассинхрона.

Подробно:

  1. Server state — асинхронный, разделяемый, устаревает; ему нужны кэш, дедупликация запросов, фоновая ре-валидация и инвалидация. Не держи его в Redux руками.
  2. Client/UI state — модалки, табы, форма, тема; живёт в useState/контексте/легковесном сторе.
  3. Нормализация — храни сущности как { byId, allIds }, ссылайся по id, а не вкладывай дубликаты; обновление в одном месте обновляет везде.
  4. Инвалидация кэша — по ключам после мутаций; задавай staleTime, чтобы не бить сеть зря.
Аспект Server state Client/UI state
Источник бэкенд сам клиент
Свежесть устаревает, ре-валидация всегда актуально
Инструмент React Query / SWR useState / context
Кэш да, по ключам обычно нет

⚠️ Частая ошибка: складывать ответы API в глобальный Redux и вручную писать loading/error/кэш — это переизобретение библиотек server state.

04

Как спроектировать переиспользуемую библиотеку компонентов / дизайн-систему?

Короткий ответ: В основе — токены (цвет, типографика, spacing) и компоненты с минимальным, предсказуемым API: варианты вместо булевых флагов, полный набор состояний, доступность по умолчанию и семантическое версионирование.

Подробно:

  • Токены — единый источник правды для цвета/spacing/радиусов как переменные; темизация и контраст строятся на них.
  • API компонентаvariant/size как объединения строк, а не россыпь boolean; разумные дефолты; проброс ...rest и ref на корневой элемент.
  • Доступность — корректные роли/ARIA, фокус-кольца, навигация с клавиатуры встроены, а не на потребителе.
  • Варианты и состояния — hover/focus/disabled/loading/error предусмотрены сразу.
  • Версионирование — semver, документация и changelog; ломающие изменения API — это major.
type ButtonProps = {
  variant?: 'primary' | 'secondary' | 'ghost';  // не isPrimary/isGhost
  size?: 'sm' | 'md' | 'lg';
  loading?: boolean;
} & React.ButtonHTMLAttributes<HTMLButtonElement>;  // проброс остального

⚠️ Частая ошибка: разрастание булевых флагов (isPrimary, isLarge, isGhost) вместо единого variant — взаимоисключающие состояния становятся возможны, а API — хрупким.

05

Как спроектировать ленту с бесконечной прокруткой (новостной feed)?

Короткий ответ: Cursor-пагинация для подгрузки, виртуализация для отрисовки только видимых элементов, кэш страниц по курсору, IntersectionObserver как триггер дозагрузки и оптимистичные апдейты для лайков/действий. Не забудь восстановление позиции скролла.

Подробно:

  1. Пагинация курсором?cursor=<id>&limit=20; устойчива к вставкам/удалениям, в отличие от offset, где элементы дублируются или пропадают.
  2. Виртуализация — рендерим только видимое окно (react-virtual/virtuoso), иначе тысячи DOM-узлов убивают память и скролл.
  3. Триггер дозагрузки — IntersectionObserver на сентинеле в конце списка вместо слушателя scroll.
  4. Кэш и оптимизм — страницы кэшируются по курсору; лайки применяем оптимистично с откатом при ошибке.
  5. Scroll restoration — сохраняем якорь/offset, чтобы возврат с детального экрана не прыгал.
[scroll] ─► IntersectionObserver(sentinel)
              │ fetch(?cursor=last_id)

   ┌─────────────────────────┐
   │ cache: page1·page2·page3 │ ──► virtualized window
   └─────────────────────────┘      (renders ~visible rows)

⚠️ Частая ошибка: offset-пагинация для живой ленты — при новых записях сверху страницы сдвигаются, и пользователь видит дубли или пропуски.

06

Как спроектировать autocomplete / typeahead (поисковые подсказки)?

Короткий ответ: Дебаунс ввода, кэш результатов по запросу, обязательная отмена устаревших запросов через AbortController (иначе медленный ответ перезапишет свежий), полная клавиатурная навигация и ARIA-роль combobox.

Подробно:

  1. Дебаунс — ждём ~200–300 мс паузы в наборе, чтобы не слать запрос на каждое нажатие.
  2. Отмена гонок — каждый новый ввод отменяет предыдущий fetch через AbortController; так ответ на «react» не перетрёт ответ на «react native».
  3. Кэш — мемоизируем результаты по строке запроса; повтор — без сети.
  4. Доступностьrole="combobox", aria-activedescendant, стрелки/Enter/Esc, объявление количества результатов.
  5. Состояния — loading, пусто («ничего не найдено»), ошибка с возможностью повтора.
useEffect(() => {
  const ctrl = new AbortController();
  const t = setTimeout(async () => {
    try {
      const res = await fetch(`/search?q=${query}`, { signal: ctrl.signal });
      setItems(await res.json());
    } catch (e) {
      if (e.name !== 'AbortError') setError(e);  // отмена — не ошибка
    }
  }, 250);                                       // дебаунс
  return () => { clearTimeout(t); ctrl.abort(); }; // отмена устаревшего
}, [query]);

⚠️ Частая ошибка: не отменять запросы в гонке — старый ответ приходит позже нового и подменяет актуальные подсказки.

07

Как реализовать realtime-обновления UI: polling vs SSE vs WebSocket?

Короткий ответ: Выбор по направлению и частоте данных. Polling — для редких обновлений и простоты; SSE — для односторонних серверных push (лента, нотификации); WebSocket — для двунаправленного низколатентного обмена (чат, коллаборация). Сверху — оптимистичный UI и реконсиляция при приходе серверного состояния.

Подробно:

  • Polling — клиент сам опрашивает через интервал; просто, но создаёт лишние запросы и лаг; long-polling сглаживает.
  • SSE — один HTTP-стрим сервер→клиент, авто-reconnect, поверх обычного HTTP; только в одну сторону.
  • WebSocket — постоянное двунаправленное соединение, минимальная задержка; дороже в инфраструктуре, нужен heartbeat/reconnect.
  • Оптимизм и реконсиляция — применяем изменение локально сразу, при подтверждении/конфликте сверяемся с серверным состоянием и откатываем при расхождении.
Критерий Polling SSE WebSocket
Направление client→server server→client двунаправленное
Задержка высокая низкая очень низкая
Сложность низкая средняя высокая
Когда редкие апдейты фид, нотификации чат, коллаборация

⚠️ Частая ошибка: тянуть WebSocket туда, где хватило бы SSE или периодического polling — лишняя сложность инфраструктуры и обработки reconnect без реальной потребности в двунаправленности.

08

Как сделать фронтенд устойчивым: error boundaries, ретраи, деградация и состояния загрузки/ошибки?

Короткий ответ: Локализуй сбои error boundary'ями, чтобы падал виджет, а не всё приложение; повторяй транзиентные запросы с экспоненциальным backoff; всегда проектируй четыре состояния (loading/empty/error/success) и деградируй мягко при отказе некритичных частей и офлайне.

Подробно:

  1. Error boundaries — оборачивай рискованные поддеревья; вместо белого экрана — fallback с кнопкой «повторить».
  2. Retry с backoff — повторяй сетевые ошибки/5xx с экспоненциальной задержкой и jitter; 4xx не ретраим.
  3. Состояния UI — loading (скелетоны), empty (понятный пустой экран), error (с действием), success — все спроектированы, а не забыты.
  4. Graceful degradation — упавший некритичный блок не валит страницу; ядро функциональности остаётся.
  5. Offline — детектим navigator.onLine, кэшируем через Service Worker, ставим мутации в очередь до восстановления связи.
class ErrorBoundary extends React.Component<Props, { error: Error | null }> {
  state = { error: null };
  static getDerivedStateFromError(error: Error) { return { error }; }
  componentDidCatch(error, info) { logToService(error, info); }  // телеметрия
  render() {
    return this.state.error
      ? <Fallback onRetry={() => this.setState({ error: null })} />
      : this.props.children;                  // изолирован сбой поддерева
  }
}

⚠️ Частая ошибка: рисовать только happy path и забывать loading/empty/error — реальный пользователь первым делом ловит именно эти состояния.

Источники

Источники и редакционная политика

Материалы RecallDeck сопоставлены с официальной документацией и открытыми публикациями компаний, когда первичный источник доступен. Мы не связаны с упомянутыми работодателями, не публикуем конфиденциальные задания и не продаём места в подборках. Формат найма может меняться — уточняйте его у рекрутера.

От чтения к воспроизведению

Отрепетируйте полный цикл интервью.

RecallDeck возвращает сложные темы по расписанию и помогает удерживать в памяти язык, SQL, архитектуру и поведенческие истории.

Начать подготовку

Продолжить подготовку

Библиотека собеседований RecallDeck

Подробные русские ответы, разборы этапов найма и планы подготовки для российского IT-рынка.

RSS