Подготовка к алгоритмическим задачам

Вопросы по JavaScript на собеседовании frontend-разработчика
Коротко
Вопросы по JavaScript на собеседовании группируются в шесть блоков. Ниже — каждый с разбором и объяснением, что именно проверяют.
| Блок | Что проверяют |
|---|---|
| Типы и приведение | понимание основ, внимательность |
| Замыкания и область видимости | ключевая тема, спрашивают почти всегда |
this и контекст | самая частая ловушка |
| Прототипы и наследование | понимание устройства языка |
| Event loop и асинхронность | главная тема для middle+ |
| Промисы и async/await | практика |
Блок 1. Типы и приведение
В чём разница между == и ===
=== сравнивает без приведения типов, == приводит операнды к общему типу.
1 == '1' // true — строка приводится к числу 1 === '1' // false null == undefined // true — специальное правило null === undefined // false NaN == NaN // false — NaN не равен ничему, включая себя
Проверка на NaN: Number.isNaN(x) или x !== x.
Что проверяют: знаете ли вы правила приведения. Практический ответ: используйте === всегда, кроме проверки на null/undefined одним сравнением.
Чем отличаются null и undefined
undefined — переменная объявлена, но значение не присвоено. null — намеренно присвоенное «пустое» значение.
let a; console.log(a); // undefined console.log(typeof a); // 'undefined' let b = null; console.log(typeof b); // 'object' — историческая ошибка языка
typeof null === 'object' — известный баг, оставленный ради обратной совместимости. Про него любят спрашивать.
Что выведет и почему
console.log(0.1 + 0.2 === 0.3); // false console.log(0.1 + 0.2); // 0.30000000000000004
Числа хранятся в формате двойной точности, и 0.1 в двоичном виде — бесконечная дробь. Сравнивать нужно с погрешностью: Math.abs(a - b) < Number.EPSILON.
Решай алгоритмические задачи как профи

Блок 2. Замыкания
Что такое замыкание
Функция вместе с областью видимости, в которой она была создана. Внутренняя функция сохраняет доступ к переменным внешней даже после её завершения.
function counter() { let count = 0; // живёт в замыкании return function () { return ++count; }; } const next = counter(); next(); // 1 next(); // 2
Зачем нужны: приватные переменные, каррирование, мемоизация, обработчики событий с сохранённым состоянием.
Классическая задача про цикл
for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); } // 3, 3, 3
var имеет функциональную область видимости — все три колбэка ссылаются на одну переменную, которая к моменту выполнения равна 3.
Исправление:
for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); } // 0, 1, 2 — let создаёт новую привязку на каждой итерации
Это вопрос-классика. Ответ должен включать объяснение почему, а не только исправление.
Блок 3. Контекст this
Самая частая ловушка на собеседованиях frontend.
Правила определения this при вызове:
| Вызов | Чему равен this |
|---|---|
| Обычная функция | undefined в strict, window иначе |
| Метод объекта | сам объект |
| Стрелочная функция | this из места объявления |
call, apply, bind | что передали |
Конструктор с new | новый объект |
const obj = { name: 'объект', regular() { console.log(this.name); // 'объект' }, arrow: () => { console.log(this.name); // undefined — this внешний }, nested() { setTimeout(function () { console.log(this.name); // undefined — потерян контекст }, 0); setTimeout(() => { console.log(this.name); // 'объект' — стрелка сохранила }, 0); }, };
Ключевая мысль: у стрелочных функций нет собственного this — они берут его из внешней области. Поэтому стрелка не годится как метод объекта, но идеальна для колбэков.
Потеря контекста
const user = { name: 'Аня', greet() { return `Привет, ${this.name}`; }, }; const fn = user.greet; fn(); // ошибка: this не определён const bound = user.greet.bind(user); bound(); // работает
Блок 4. Прототипы
Как работает наследование
Каждый объект имеет ссылку на прототип. При обращении к свойству движок ищет его в самом объекте, затем в прототипе, затем в прототипе прототипа — до null. Это цепочка прототипов.
const animal = { speak() { return 'звук'; }, }; const dog = Object.create(animal); dog.name = 'Рекс'; dog.speak(); // 'звук' — взято из прототипа Object.getPrototypeOf(dog) === animal; // true
Классы в ES6 — синтаксический сахар над прототипами, а не отдельный механизм.
class Animal { speak() { return 'звук'; } } // эквивалентно записи функции-конструктора с методом в prototype
Что проверяют: понимаете ли вы, что под классами лежат прототипы. Ответ «в JS классическое наследование» — красный флаг.
Блок 5. Event loop
Главная тема для позиций middle и выше.
Что выведет
console.log('1'); setTimeout(() => console.log('2'), 0); Promise.resolve().then(() => console.log('3')); console.log('4'); // 1, 4, 3, 2
Разбор:
- Синхронный код выполняется первым:
1,4. - Затем опустошается очередь микрозадач — промисы:
3. - И только потом очередь макрозадач — таймеры:
2.
Ключевое правило: микрозадачи имеют приоритет и выполняются полностью между каждой макрозадачей.
| Микрозадачи | Макрозадачи |
|---|---|
Promise.then | setTimeout |
queueMicrotask | setInterval |
MutationObserver | события DOM |
requestAnimationFrame |
Почему setTimeout(fn, 0) не мгновенный
Он ставит задачу в очередь макрозадач, которая обрабатывается только после завершения синхронного кода и всех микрозадач. Минимальная задержка в браузерах — около 4 мс для вложенных вызовов.
Блок 6. Промисы и async/await
Разница между Promise.all и Promise.allSettled
// all — падает при первой ошибке await Promise.all([ok(), fail(), ok()]); // отклонится // allSettled — всегда дожидается всех const results = await Promise.allSettled([ok(), fail()]); // [{status: 'fulfilled', value}, {status: 'rejected', reason}]
Есть ещё Promise.race (первый завершившийся, любой) и Promise.any (первый успешный).
Частая ошибка: последовательность вместо параллели
// медленно: запросы идут по очереди for (const url of urls) { results.push(await fetch(url)); } // быстро: параллельно const results = await Promise.all(urls.map((u) => fetch(u)));
Это любимый вопрос на код-ревью в рамках интервью.
Обработка ошибок
try { const data = await fetchData(); } catch (error) { // ловит и синхронные ошибки, и отклонённые промисы } finally { // выполнится в любом случае }
Без try/catch отклонённый промис в async-функции даёт необработанное отклонение.
Как готовиться
Разберитесь в event loop. Это тема, которая отличает junior от middle. Задачи «что выведет» на промисах и таймерах дают почти всегда.
Практикуйте this. Напишите десяток примеров с разными способами вызова и предскажите результат до запуска.
Не заучивайте ответы. Спрашивают «почему», а не «что». Ответ «потому что так работает» не засчитывается.
Готовьте примеры из опыта. «Мы ловили утечку памяти из-за замыкания в обработчике» весит больше, чем определение замыкания.
Что запомнить
===вместо==везде, кроме проверки наnull/undefined.- Замыкание — функция плюс её область видимости; классическая задача про
varв цикле. - У стрелочных функций нет своего
this— они берут его из места объявления. - Классы в JavaScript — сахар над прототипами.
- Микрозадачи (промисы) выполняются раньше макрозадач (таймеров).
- Параллельные запросы через
Promise.all, а неawaitв цикле.
