10 задач с собеседований по JavaScript — разбор решений

На каждом собеседовании фронтенд-разработчика sooner или later просят написать код. Не на доске, не в документации, а прямо в редакторе — быстро, чисто и с объяснением.

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

А чтобы знания не оставались теорией — попробуйте решить эти задачи на практике на Девстанции. Там же можно потренироваться на десятках других задач с автоматической проверкой и AI-ревью кода.


Содержание

  1. FizzBuzz
  2. Разворот строки
  3. Проверка палиндрома
  4. Найти пропущенное число
  5. Плоский массив
  6. Debounce
  7. Реализация Promise.all
  8. Глубокое клонирование объекта
  9. Разбиение массива на чанки
  10. Транспонирование матрицы

FizzBuzz

Задача-классика. Звучит просто: вывести числа от 1 до 100, но вместо числа, кратного 3, вывести «Fizz», вместо кратного 5 — «Buzz», а если число кратно обоим — «FizzBuzz».

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

Решение:

function fizzBuzz(n) {
  const result = [];
  for (let i = 1; i <= n; i++) {
    if (i % 15 === 0) {
      result.push("FizzBuzz");
    } else if (i % 3 === 0) {
      result.push("Fizz");
    } else if (i % 5 === 0) {
      result.push("Buzz");
    } else {
      result.push(i);
    }
  }
  return result;
}

Совет на собеседовании: сначала спросите про граничные случаи — что делать с 0, отрицательными числами, нечисловым входом. Это покажет, что вы думаете о edge cases, а не просто кодите.


Разворот строки

Дана строка. Нужно вернуть её в обратном порядке. Казалось бы — split, reverse, join. Но на собеседовании часто просят решить без встроенных методов или написать наивную реализацию.

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

Решение:

function reverseString(str) {
  let result = "";
  for (let i = str.length - 1; i >= 0; i--) {
    result += str[i];
  }
  return result;
}

Альтернатива через рекурсию:

function reverseString(str) {
  if (str.length <= 1) return str;
  return reverseString(str.slice(1)) + str[0];
}

Совет на собеседовании: сначала напишите простое решение, затем предложите оптимизацию. Покажите, что умеете думать итеративно и рекурсивно.


Проверка палиндрома

Строка — палиндром, если она читается одинаково в обоих направлениях. Нужно написать функцию, которая это проверяет.

Почему спрашивают: задача на строки, циклы и понимание того, что считать «равным» (учитываем ли регистр, пробелы, знаки препинания).

Решение:

function isPalindrome(str) {
  const cleaned = str.toLowerCase().replace(/[^a-z0-9]/g, "");
  const len = cleaned.length;
  for (let i = 0; i < len / 2; i++) {
    if (cleaned[i] !== cleaned[len - 1 - i]) {
      return false;
    }
  }
  return true;
}

Совет на собеседовании: уточните условия — «racecar» и «Racecar» это одинаково? А «A man, a plan, a canal: Panama»? Покажите, что вы работаете с требованиями, а не гадаете.


Найти пропущенное число

Дан массив из n чисел от 1 до n+1. Одно число пропущено. Нужно его найти.

Почему спрашивают: проверяют знание математики, множеств и умение выбирать оптимальный алгоритм.

Решение через сумму:

function findMissing(arr) {
  const n = arr.length + 1;
  const expectedSum = (n * (n + 1)) / 2;
  const actualSum = arr.reduce((sum, num) => sum + num, 0);
  return expectedSum - actualSum;
}

Решение через Set:

function findMissing(arr) {
  const full = new Set(Array.from({ length: arr.length + 1 }, (_, i) => i + 1));
  for (const num of arr) full.delete(num);
  return [...full][0];
}

Совет на собеседовании: объясните сложность каждого решения. Первое — O(n) по времени и O(1) по памяти. Второе — O(n) по обоим. Это покажет понимание Big O.


Плоский массив

Дан вложенный массив: [1, [2, [3, 4], 5], 6]. Нужно превратить его в плоский: [1, 2, 3, 4, 5, 6].

Почему спрашивают: задача на рекурсию и понимание структур данных. Часто просят решить без flat().

Рекурсивное решение:

function flatten(arr) {
  const result = [];
  for (const item of arr) {
    if (Array.isArray(item)) {
      result.push(...flatten(item));
    } else {
      result.push(item);
    }
  }
  return result;
}

Решение через стек:

function flatten(arr) {
  const stack = [...arr];
  const result = [];
  while (stack.length) {
    const item = stack.pop();
    if (Array.isArray(item)) {
      stack.push(...item);
    } else {
      result.unshift(item);
    }
  }
  return result;
}

Совет на собеседовании: спросите про глубину вложенности. Если она гарантированно мелкая — рекурсия безопасна. Если нет — итеративное решение надёжнее.


Debounce

Реализовать функцию debounce, которая откладывает вызов функции до тех пор, пока не пройдёт задержка после последнего вызова.

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

Решение:

function debounce(fn, delay) {
  let timerId = null;
  return function (...args) {
    clearTimeout(timerId);
    timerId = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}

Совет на собеседовании: обсудите применение — поиск с debounce, обработку ресайза, отправку аналитики. Покажите, что знаете, зачем это нужно в реальном проекте.


Реализация Promise.all

Написать свою функцию myPromiseAll, которая принимает массив промисов и возвращает один промис, который резолвится массивом результатов.

Почему спрашивают: глубокое понимание промисов, асинхронного JavaScript и обработки ошибок.

Решение:

function myPromiseAll(promises) {
  return new Promise((resolve, reject) => {
    const results = [];
    let completed = 0;

    if (promises.length === 0) {
      resolve(results);
      return;
    }

    promises.forEach((promise, index) => {
      Promise.resolve(promise).then(
        (value) => {
          results[index] = value;
          completed++;
          if (completed === promises.length) {
            resolve(results);
          }
        },
        (error) => {
          reject(error);
        },
      );
    });
  });
}

Совет на собеседовании: объясните, почему важен индекс — промисы могут завершаться в любом порядке, но результаты должны сохранить исходный порядок. Также обсудите, что делает Promise.resolve — обёртка для не-промисов.


Глубокое клонирование объекта

Написать функцию, которая создаёт глубокую копию объекта. Вложенность произвольная, могут быть массивы, даты, null.

Почему спрашивают: проверяют понимание ссылочных типов, рекурсии и edge cases (циклические ссылки, специальные объекты).

Решение:

function deepClone(obj, seen = new WeakMap()) {
  if (obj === null || typeof obj !== "object") return obj;
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  if (seen.has(obj)) return seen.get(obj);

  const clone = Array.isArray(obj) ? [] : {};
  seen.set(obj, clone);

  for (const key of Object.keys(obj)) {
    clone[key] = deepClone(obj[key], seen);
  }
  return clone;
}

Совет на собеседовании: упомяните structuredClone — в современных браузерах это встроенный способ. Но объясните, почему знание ручной реализации важно — для понимания принципов и для поддержки старых сред.


Разбиение массива на чанки

Дан массив и число size. Разбить массив на подмассивы по size элементов.

Почему спрашивают: простая задача, но показывает умение работать с индексами и понимать паттерны итерации.

Решение:

function chunk(arr, size) {
  const result = [];
  for (let i = 0; i < arr.length; i += size) {
    result.push(arr.slice(i, i + size));
  }
  return result;
}

Решение через reduce:

function chunk(arr, size) {
  return arr.reduce((result, _, i) => {
    if (i % size === 0) {
      result.push(arr.slice(i, i + size));
    }
    return result;
  }, []);
}

Совет на собеседовании: обсудите edge cases — что если size больше длины массива? Что если size равен 0 или отрицательный? Покажите, что думаете о валидации.


Транспонирование матрицы

Дана матрица (массив массивов). Нужно транспонировать её — строки становятся столбцами.

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

Решение:

function transpose(matrix) {
  const rows = matrix.length;
  const cols = matrix[0].length;
  const result = [];
  for (let j = 0; j < cols; j++) {
    result[j] = [];
    for (let i = 0; i < rows; i++) {
      result[j][i] = matrix[i][j];
    }
  }
  return result;
}

Однострочник (для красоты):

const transpose = (m) => m[0].map((_, i) => m.map((row) => row[i]));

Совет на собеседовании: начните с классического решения с циклами — оно понятнее и демонстрирует понимание. Однострочник покажите как альтернативу, но объясните, почему классическое решение лучше для читаемости.


Как готовиться дальше

Эти 10 задач — лишь верхушка айсберга. На реальном собеседовании могут спросить что угодно от реализации виртуального скролла до написания собственного хука.

Лучший способ подготовиться — решать задачи регулярно, а не зубрить ответы. Попробуйте задачи на Девстанции — здесь есть десятки задач разной сложности с автоматической проверкой и AI-ревью кода, которое подскажет, как написать лучше.

И помните: на собеседовании важен не только ответ, но и как вы к нему пришли. Объясняйте мысли, задавайте вопросы, обсуждайте альтернативы. Это и есть профессиональный подход.