Gernar
Frontend DeveloperJavaScript, алгоритмы и структуры данных

Интервью-вопрос

Какая алгоритмическая сложность при сортировке массива в JavaScript

Короткий ответ: стандарт не гарантирует конкретную сложность Array.prototype.sort, но в современных движках обычно ожидают O(n log n). На практике вам важно не забыть про comparator, мутацию массива и цену сортировки для UI.

Добавлен
Редакция

Подготовьте короткий ответ и пару деталей на случай уточняющих вопросов.

🐰0
🥚0

Мини-квиз

Проверка перед разбором

Несколько быстрых вопросов перед разбором. Так проще поймать места, которые только кажутся понятными.

Вопрос 1 из 50 правильно

Как лучше сказать про сложность sort?

Вы отвечаете на интервью на вопрос о сложности сортировки массива в JavaScript.

Варианты ответа

Разбор

Разобраться, а не зазубрить

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

Базовая идея

Вопрос выглядит как обычный вопрос про Big O, но на интервью в нем есть ловушка. У метода Array.prototype.sort в JavaScript нет одной гарантированной алгоритмической сложности, прописанной как часть языка. ECMAScript описывает поведение метода, но не говорит, какой алгоритм обязан использовать движок.

Можно ответить так:

В общем случае я ожидаю около O(n log n) для больших массивов в современных движках, но точный алгоритм и точная сложность зависят от реализации. В прикладном frontend-коде я не опираюсь на конкретный алгоритм. Я слежу за comparator, мутацией и объемом данных.

Такой ответ показывает, что вы знаете Big O и не выдаете деталь реализации за правило языка.

Что делает sort по умолчанию

Если не передать compare-функцию, sort приводит элементы к строкам и сравнивает их в строковом порядке. Для строк это часто выглядит ожидаемо. Для чисел это почти всегда источник бага.

const numbers = [10, 2, 1];

// Плохо: сортировка как строк
numbers.sort();
console.log(numbers); // [1, 10, 2]

Безопаснее явно описать порядок:

const numbers = [10, 2, 1];

const sorted = [...numbers].sort((a, b) => a - b);
console.log(sorted); // [1, 2, 10]

Практический вывод простой: если вы сортируете не простые строки, почти всегда нужен comparator. Так вы защищаете UI от неверного порядка цен, рейтингов, дат и числовых колонок. Comparator должен быть чистой функцией. Не меняйте элементы, не пишите в store, не отправляйте аналитику и не делайте запросы из сравнения.

Как выбрать безопасный подход

Что проверить перед сортировкой

1Сортируете числа или даты?
Передайте явный comparator, иначе получите строковый порядок.
2Массив пришел из state, props или кеша?
Не вызывайте sort на нем напрямую. Сначала создайте копию или используйте toSorted.
3Данных много и UI начинает лагать?
Снижайте объем данных, мемоизируйте результат или переносите тяжелую работу из основного потока.
4Есть одинаковые ключи сортировки?
Можно опираться на стабильность современной сортировки, но comparator должен честно возвращать 0 для равных элементов.

Мутация массива важна для frontend

sort сортирует массив на месте и возвращает тот же массив. Это нормально для локальной одноразовой переменной, но опасно для данных из state, props, store или кеша. Последствия в UI: список может поменяться без явного обновления state, соседний компонент увидит уже мутированные данные, а мемоизация по ссылке перестанет защищать от лишней работы.

function Products({ products }) {
  // Плохо: мутирует props
  const sorted = products.sort((a, b) => a.price - b.price);

  return sorted.map((product) => <ProductCard key={product.id} product={product} />);
}

Исправление простое. Сначала создайте новый массив. Если сортировка тяжелая и зависит от одних и тех же входных данных, результат можно мемоизировать.

function Products({ products }) {
  const sorted = useMemo(
    () => [...products].sort((a, b) => a.price - b.price),
    [products]
  );

  return sorted.map((product) => <ProductCard key={product.id} product={product} />);
}

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

Где сложность превращается в проблему UX

O(n log n) может звучать нормально, пока массив небольшой. Но сортировка идет синхронно в основном потоке. Если элементов много или comparator дорогой, пользователь увидит лаги. Это может быть задержка ввода, рывки скролла или зависание фильтров.

Частая ошибка: делать тяжелую работу внутри comparator.

// Плохо: Date.parse вызывается много раз во время сортировки
const sorted = [...events].sort(
  (a, b) => Date.parse(a.startedAt) - Date.parse(b.startedAt)
);

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

const withSortKey = events.map((event) => ({
  event,
  startedAtMs: Date.parse(event.startedAt),
}));

const sorted = withSortKey
  .sort((a, b) => a.startedAtMs - b.startedAtMs)
  .map(({ event }) => event);

Это не отменяет сложность сортировки, но снижает цену каждого сравнения. Для очень больших списков стоит думать о серверной сортировке, пагинации, виртуализации или Web Worker. Не переносите в comparator запросы за дополнительными данными: он может вызваться десятки или тысячи раз, что даст лишнюю нагрузку, race condition и неверную аналитику.

Правильный и опасный flow

Безопасно для UI
  1. 1Определить поле и направление сортировки
  2. 2Скопировать исходный массив
  3. 3Передать явный comparator
  4. 4Мемоизировать результат, если сортировка дорогая
Опасно
  1. 1Вызвать sort прямо на state или props
  2. 2Не передать comparator для чисел
  3. 3Делать тяжелую сортировку на каждом рендере
  4. 4Игнорировать лаги основного потока

Как коротко ответить на интервью

Хороший короткий ответ можно построить так:

Если говорить про Array.prototype.sort, стандарт JavaScript не гарантирует конкретный алгоритм и сложность. В современных движках для обычных случаев обычно ожидают около O(n log n), но я бы не формулировал это как жесткое правило языка. На практике я всегда передаю comparator для чисел, дат и объектов, помню, что sort мутирует массив, и не делаю тяжелую сортировку на каждом рендере.

Если хотите усилить ответ, добавьте одну фразу про стабильность: современная сортировка стабильная, поэтому элементы, равные по comparator, сохраняют относительный порядок.

Частые ошибки

Где обычно ошибаются

Проверьте формулировки, которые звучат уверенно, но на интервью быстро выдают пробелы.

  1. 1

    Называть точный алгоритм как часть стандарта

    Стандарт JavaScript не говорит, что sort обязан использовать TimSort, QuickSort или другой конкретный алгоритм. Если сказать это категорично, вас легко поймать вопросом про разные движки. Безопаснее говорить про поведение метода и типичную практическую оценку.
  2. 2

    Сортировать числа без comparator

    [10, 2, 1].sort() сортирует значения как строки, поэтому результат будет не числовым. В таблице цен, рейтингах или датах это даст неверный порядок. Для чисел используйте (a, b) => a - b, для дат сравнивайте timestamp.
  3. 3

    Мутировать массив из React state

    items.sort() меняет исходный массив. Если это state или props, вы можете получить неочевидные баги, сломать мемоизацию и испортить данные в другом месте. Делайте копию: [...items].sort(compare) или используйте toSorted там, где он поддерживается.
  4. 4

    Писать нестабильный comparator

    Comparator должен возвращать отрицательное число, положительное число или 0 согласованно. Если возвращать случайные значения или только true/false, порядок станет непредсказуемым. Это особенно заметно в UI, где элементы списка могут прыгать между рендерами.
  5. 5

    Игнорировать цену сравнения

    Сложность сортировки считают по числу сравнений, но каждое сравнение тоже может быть дорогим. Если внутри comparator парсить дату, читать локаль или делать сложные вычисления, интерфейс может зависнуть. Лучше подготовить ключи сортировки заранее или кешировать вычисления.
  6. 6

    Делать побочные эффекты внутри comparator

    Comparator вызывается много раз и в порядке, который не стоит использовать как бизнес-логику. Сетевой запрос, запись в store или отправка аналитики внутри comparator дадут лишние запросы, неверные события и непредсказуемый UI. Comparator должен только сравнивать два значения и возвращать число.

Follow-up

Что могут спросить дальше

Короткие ответы на вопросы, которыми проверяют понимание sort, comparator и практических рисков во frontend-коде.

Живые ответы

Видео с похожим вопросом

Если найдем публичные интервью с таким вопросом, добавим их сюда. Их удобно смотреть после теории, чтобы свериться с живыми ответами.

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

Содержание