Собеседования

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

Как пройти секцию frontend system design на примере ленты: требования, курсорная пагинация, кеш, виртуализация, оптимистичный лайк с откатом и доступность.

В секции frontend system design нет одного правильного ответа. Интервьюер смотрит, как вы собираете требования, раскладываете систему на части и обосновываете компромиссы. Пройдём весь путь на классической задаче - лента новостей с лайками.

Шаг 1. Требования и ограничения

Потратьте первые минуты на вопросы. Какие устройства и сеть? Сколько постов в ленте и как часто появляются новые? Есть ли медиа? Нужен ли офлайн-режим? Что важнее - свежесть или скорость? Зафиксируйте функциональные требования (лента, лайк, подгрузка) и нефункциональные (быстрая первая отрисовка, плавная прокрутка, доступность с клавиатуры).

Шаг 2. Контракт API

Для ленты, куда постоянно добавляются записи, offset-пагинация (page=3) даёт дубли и пропуски: пока пользователь читает, новые посты сдвигают выборку. Курсорная пагинация передаёт «продолжить после этой записи». Сервер возвращает элементы и nextCursor; null означает конец.

TypeScript// Склейка страниц ленты: курсор и защита от дублей export type Post = { id: string; createdAt: string; text: string } export type FeedPage = { items: Post[]; nextCursor: string | null } export function mergePages(current: Post[], page: FeedPage): Post[] { const seen = new Set(current.map((post) => post.id)) return [...current, ...page.items.filter((post) => !seen.has(post.id))] }

Защита от дублей нужна даже с курсором: повторный запрос после обрыва сети или гонка двух подгрузок могут вернуть уже показанные посты. Сам запрос следующей страницы стоит блокировать, пока предыдущий не завершился.

Шаг 3. Модель данных на клиенте

Пост может появиться в ленте, в профиле и в поиске. Если хранить копии, лайк в одном месте не обновит другие. Нормализованный кеш хранит сущности по id, а списки - массивы id. Библиотеки серверного состояния (TanStack Query, RTK Query) решают кеширование, повторные запросы и инвалидацию; на собеседовании важно объяснить принцип, а не назвать библиотеку.

Шаг 4. Оптимистичный лайк

Пользователь ждёт мгновенной реакции, поэтому счётчик меняется сразу, а запрос идёт в фоне. Ключевая деталь - откат при ошибке. Нельзя просто вернуть старое значение: пока шёл запрос, пользователь мог нажать ещё раз. Откатываем, только если состояние всё ещё наше.

TypeScript// Оптимистичный лайк с безопасным откатом type Like = { liked: boolean; count: number } type LikeState = Record<string, Like> export async function toggleLike( get: () => LikeState, set: (next: LikeState) => void, id: string, send: (id: string, liked: boolean) => Promise<void>, ): Promise<void> { const before = get()[id] if (!before) return const next: Like = { liked: !before.liked, count: before.count + (before.liked ? -1 : 1) } set({ ...get(), [id]: next }) try { await send(id, next.liked) } catch (error) { // Откатываем только свою правку: пользователь мог успеть нажать ещё раз if (get()[id] === next) set({ ...get(), [id]: before }) throw error } }

Ошибку стоит показать пользователю коротким сообщением рядом с кнопкой. Для частых нажатий можно объединять запросы - отправлять итоговое состояние после паузы, как в debounce из разбора задач лайвкодинга.

Шаг 5. Производительность

  • Первая отрисовка. Первую страницу ленты отдаём вместе с HTML, изображения первого экрана - с приоритетом, остальные лениво.
  • Длинный список. Виртуализация рендерит только видимые элементы. Цена - сложнее поиск по странице, якоря и скринридеры; для сотни постов она часто не нужна.
  • Стабильная вёрстка. Резервируем размеры медиа, чтобы подгрузка не сдвигала контент (CLS).
  • Отзывчивость. Тяжёлые вычисления - вне обработчиков ввода; измеряем INP. Подробнее - в разборе Core Web Vitals.

Шаг 6. Доступность и плохая сеть

Лента - список статей: у каждого поста заголовок или подпись автора, кнопки с понятными именами, видимый фокус. Подгрузку дублируем кнопкой «Показать ещё», состояние загрузки объявляем через live-регион, а при ошибке показываем причину и кнопку повтора. Проверка интерфейсов клавиатурой разобрана в материале про доступные формы.

Как построить ответ

  1. Требования и ограничения - 5 минут.
  2. Схема: компоненты, хранилище, API - и поток данных между ними.
  3. Глубже в 2-3 темы, которые интересны интервьюеру: пагинация, кеш, оптимистичные обновления.
  4. Компромиссы и риски: что сделаете в первой версии, что отложите и почему.

Такие секции тренируются на мок-собеседованиях: это часть подготовки на менторстве Frontend.

Источники

Ленивая загрузка изображений: web.dev. Метрики: Web Vitals. Live-регионы: MDN ARIA live regions. Оптимистичные обновления: TanStack Query.

Вопросы по теме

Чем frontend system design отличается от backend?
Фокус на клиенте: модель данных в браузере, контракт API с точки зрения интерфейса, состояние, производительность рендера, доступность и поведение при плохой сети. Масштабирование серверов обсуждается только на уровне требований к API.
Бесконечная прокрутка или кнопка «Показать ещё»?
Бесконечная прокрутка удобна в ленте, но мешает добраться до подвала и сложнее для клавиатуры. Компромисс - подгружать автоматически, но оставлять доступную кнопку и сохранять позицию при возврате.
Нужно ли рисовать схему?
Да. Простая схема компонентов, хранилища и запросов помогает интервьюеру следить за рассуждением. Подпишите поток данных: откуда приходят посты, где хранятся и кто их обновляет.

Читайте также

Все статьи

Обсудим вашу подготовку?

Напишите, что уже умеете и с чем возникли трудности. Если есть проект, пришлите ссылку на него.

Отвечаю лично, поэтому прошу рассказать о ситуации подробно. Все поля обязательны, кроме отмеченных. После отправки покажу ссылку на мой Telegram: напишите туда, и начнём разговор.

Что не получается, что уже пробовали. От 100 знаков, сейчас 0.
Где работали или учились, сколько лет, какой стек, сколько откликов и собеседований. От 100 знаков, сейчас 0.
PDF или DOCX, до 5 МБ. Файл видит только администратор.
GitHub, hh, LinkedIn, сайт. Можно несколько, через пробел или с новой строки.