Irbisa · Шпаргалка

Frontend system design

Проектирование ленты, автокомплита или чата, стратегия рендеринга, API и состояние, кэш, офлайн, обновления в реальном времени, микрофронтенды, сборка

Senior-разработчик · 21 вопрос

  1. 01

    Как построить ответ на собеседовании по frontend system design?

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

  2. 02

    Чем offset-пагинация отличается от курсорной и какую вы выберете для бесконечной ленты?

    Offset-пагинация запрашивает элементы по позиции («пропусти 40, дай 20»), курсорная просит «следующие 20 после этого элемента» по непрозрачному токену из предыдущей страницы.

  3. 03

    Как выбрать между CSR, SSR, SSG и ISR для страниц продукта?

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

  4. 04

    Что такое performance budget, что в него включить и как его соблюдать?

    Performance budget задаёт жёсткие лимиты, которые изменение не может превысить; о них договариваются заранее: пользовательские метрики вроде LCP и INP плюс величины, которые на них влияют, например размер JavaScript и число запросов.

  5. 05

    Какие слои кэширования есть в веб-приложении между пользователем и базой и что хранить в каждом?

    Если идти от пользователя: кэш запросов в памяти, постоянное хранилище (IndexedDB), кэш service worker, HTTP-кэш браузера, CDN и кэши самого сервера.

  6. 06

    Как реализовать бесконечный скролл длинного списка и какие задачи нужно решить кроме загрузки следующей страницы?

    Поставьте после последнего элемента сторожевой элемент (sentinel) и следите за ним через IntersectionObserver; когда он приближается к вьюпорту на заданный отступ, загружайте следующую страницу по курсору.

    import { useEffect, useRef } from 'react';import { useInfiniteQuery } from '@tanstack/react-query'; type Page = { items: Post[]; nextCursor: string | null }; export function Feed() {…
  7. 07

    Как бы вы спроектировали компонент карусели изображений?

    Стройте её на нативном горизонтальном скролле с CSS scroll snap, грузите только текущий слайд и соседние, а требования доступности и автопрокрутки считайте обязательными, а не дополнительными.

  8. 08

    Как бы вы спроектировали поле поиска с автодополнением (typeahead)?

    Делайте debounce нажатий, отменяйте устаревшие запросы, чтобы старый ответ никогда не перезаписал новый, кэшируйте результаты по запросу и рендерьте список как доступный combobox.

    import { useEffect, useState } from 'react'; type Suggestion = { id: string; label: string }; const cache = new Map<string, Suggestion[]>();…
  9. 09

    Когда для обновлений в реальном времени использовать WebSocket, Server-Sent Events или long polling и как работает каждый из них?

    Long polling держит обычный запрос открытым, пока у сервера не появятся новости; SSE представляет собой один длинный HTTP-ответ со стримом событий от сервера к клиенту; WebSocket превращает соединение в двусторонний канал сообщений.

    // Long polling: the server holds each request until it has events or about 30 s passasync function longPoll(cursor = '') {  for (;;) {    try {      const res = await fetch(`/api/events?after=${encodeURIComponent(cursor)}`);      if (!res.ok) throw new Error(`HTTP ${res.status}`);…
  10. 10

    Как бы вы спроектировали фронтенд новостной ленты вроде Facebook или X?

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

  11. 11

    Как бы вы спроектировали страницу каталога маркетплейса: фильтры, сортировку, пагинацию, SEO и быструю первую загрузку?

    Сделайте URL единственным источником правды для фильтров, сортировки и страницы, рендерите по нему первую страницу на сервере и кешируйте HTML по нормализованному URL, а дальнейшие изменения грузите на клиенте, не убирая старые результаты.

    type Filters = Record<string, string[]>; // One filter set, one URL: one CDN cache entry and one canonicalexport function toSearch(filters: Filters, sort: string, page: number): string {  const params = new URLSearchParams();  for (const key of Object.keys(filters).sort()) {…
  12. 12

    Как бы вы спроектировали клиент чата в реальном времени?

    Один WebSocket на клиент для событий в обе стороны, оптимистичная отправка с id, который генерирует клиент и возвращает сервер, порядок по серверному sequence-номеру и досинхронизация с последнего увиденного номера после каждого переподключения.

    type Message = {  clientId: string; // made on the device, doubles as the idempotency key  id?: string; // assigned by the server  seq?: number; // server order within the conversation  text: string;  status: 'sending' | 'sent' | 'failed';…
  13. 13

    Как спроектировать контракт API между фронтендом и бэкендами: REST, GraphQL или BFF?

    Стройте API вокруг того, что нужно экранам, а не вокруг таблиц базы.

  14. 14

    Как бы вы спроектировали дашборд реального времени, который получает сотни обновлений в секунду?

    Отвяжите частоту обновлений от частоты рендера: держите входящие данные в сторе вне React, схлопывайте обновления по ключу и рендерьте не чаще раза в кадр (или нескольких раз в секунду), причём только виджеты, чьи данные изменились.

    type Tick = { symbol: string; price: number }; // collect stream messages and hand the UI one batch per frame, not one update per messageexport function subscribeBatched(url: string, onBatch: (latest: Map<string, Tick>) => void) {  const source = new EventSource(url);  let pending = new Map<string, Tick>();…
  15. 15

    Как сделать веб-приложение offline-first?

    Отдавайте оболочку приложения из кэша service worker, сделайте IndexedDB хранилищем, из которого читает UI, а записи складывайте в outbox, который синхронизируется при появлении сети.

  16. 16

    Как бы вы спроектировали совместный редактор вроде Google Docs, где несколько человек одновременно правят один документ?

    Главная задача: сливать одновременные правки так, чтобы копии сошлись, через Operational Transformation на центральном сервере (как в Google Docs) или через CRDT вроде Yjs, который сливает обновления в любом порядке и офлайн.

    type Insert = { pos: number; text: string; client: string }; // Shift `op` so it keeps its meaning after `applied`, a concurrent edit already appliedfunction transform(op: Insert, applied: Insert): Insert {  const before = applied.pos < op.pos || (applied.pos === op.pos && applied.client < op.client);  return before ? { ...op, pos: op.pos + applied.text.length } : op;…
  17. 17

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

    Загружайте файл напрямую в объектное хранилище по presigned URL частями, которые грузятся параллельно и повторяются по отдельности, показывайте прогресс по частям через XMLHttpRequest и запоминайте готовые части, чтобы после обрыва или перезагрузки докачать файл, а не начинать заново.

    const PART_SIZE = 8 * 1024 * 1024; // S3 parts: at least 5 MiB except the last // XMLHttpRequest, because fetch reports no upload progressfunction putPart(url: string, part: Blob, onProgress: (sent: number) => void, signal: AbortSignal) {  return new Promise<string>((resolve, reject) => {    const xhr = new XMLHttpRequest();…
  18. 18

    Как бы вы спроектировали дизайн-систему и библиотеку компонентов для многих продуктовых команд?

    Начните с дизайн-токенов как единого источника цветов, отступов и типографики, постройте поверх доступные примитивы и поставляйте их версионированным пакетом с документацией, визуальными тестами и понятным процессом контрибьюта.

  19. 19

    Как организовать интернационализацию в большом масштабе: десятки локалей, плюралы, языки справа налево и бандлы на локаль?

    Выносите строки из кода под стабильные ключи в формате ICU MessageFormat, форматируйте числа и даты через Intl, грузите только сообщения активной локали для маршрута и используйте логические CSS-свойства, чтобы письмо справа налево включалось сменой dir.

  20. 20

    Как добавить во фронтенд фича-флаги и A/B-эксперименты, не замедлив первый рендер и без мелькания не того варианта?

    Вычисляйте флаги до первого рендера, на сервере или на edge, и отдавайте значения вместе с HTML, чтобы клиент гидрировался с тем же вариантом.

    import { createContext, use, type ReactNode } from 'react'; // FNV-1a hash: the same id lands in the same bucket on the server, at the edge and in the browserexport function bucket(userId: string, experiment: string): number {  let h = 0x811c9dc5;  for (const ch of `${experiment}:${userId}`) h = Math.imul(h ^ ch.charCodeAt(0), 0x01000193);…
  21. 21

    Когда микрофронтенды оправданы и как их разделить и интегрировать?

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