Коротка відповідь. Списки «топ-100 питань» не працюють, бо інтерв'юер оцінює не завчену відповідь, а розуміння. Ефективніше розібрати сім тем, з яких складається практично будь-яка JavaScript-співбесіда: типи й coercion, scope і замикання, event loop, асинхронність, структури даних, обробка помилок і live coding. Нижче – кожна тема з типовими питаннями, кодом і поясненням, чого насправді чекає інтерв'юер.
Типи й coercion: звідки беруться «дивацтва» JS
Базове питання: «які типи є в JavaScript?» Відповідь: сім примітивів (string, number, bigint, boolean, undefined, symbol, null) плюс object. Але цікаве починається далі – з неявного приведення типів:
console.log(1 + '2'); // '12' – число приводиться до рядка
console.log('5' - 2); // 3 – рядок приводиться до числа
console.log([] + []); // '' – обидва масиви → порожні рядки
console.log(0.1 + 0.2 === 0.3); // false – двійкова арифметика з плаваючою комоюЧого чекає інтерв'юер: не завчених прикладів, а правила. Оператор + за наявності рядка конкатенує, решта арифметичних операторів приводять до числа. Порівняння == виконує приведення типів, === – ні, тому в реальному коді за замовчуванням використовують строге порівняння. Деталі алгоритмів приведення – у довіднику MDN з JavaScript.
Питання-пастка: «чому typeof null === 'object'?» Це історична помилка першої реалізації мови, яку не виправили заради сумісності. Знати такі речі корисно, але важливіше показати, що ти розумієш наслідок: перевірка на null робиться через value === null, а не через typeof.
Scope і замикання
Замикання – функція разом із лексичним оточенням, у якому її створили. Класична перевірка:
function createCounter() {
let count = 0;
return {
increment: () => ++count,
current: () => count,
};
}
const counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.current()); // 2Питання «як зробити приватну змінну без класів» – саме про це: count недоступний ззовні, але живе між викликами. Якщо тема дається важко, у нас є окремий детальний розбір із питаннями-пастками: closures, scope і hoisting.
Event loop: чому Promise раніше за setTimeout
Мінімум, який треба вміти пояснити словами: синхронний код виконується в call stack; колбеки таймерів потрапляють у чергу макрозадач; .then/await – у чергу мікрозадач; мікрозадачі виконуються всі до останньої перед наступною макрозадачею.
console.log('a');
setTimeout(() => console.log('b'), 0);
Promise.resolve().then(() => console.log('c'));
console.log('d');
// a, d, c, bЦе питання ставлять майже завжди, і на ньому ж найчастіше «сипляться». Покроковий розбір механіки з трьома класичними задачами – в окремій статті про event loop у JavaScript.
Асинхронність: async/await і помилки
Типове завдання: «перепиши промісовий код на async/await і додай обробку помилок».
async function loadUser(id: string): Promise<User | null> {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return (await response.json()) as User;
} catch (error) {
console.error('loadUser failed:', error);
return null;
}
}На що дивиться інтерв'юер: чи перевіряєш ти response.ok (fetch не reject-ить на 404), чи не ковтаєш помилку мовчки, чи розумієш різницю між послідовним await у циклі та паралельним Promise.all. Останнє – улюблене уточнення: «а якщо треба завантажити 10 користувачів одночасно?»
Масиви, Map і Set: обери структуру свідомо
Питання рівня Middle: «чим Map кращий за об'єкт і коли достатньо масиву?» Показова задача – унікальні елементи масиву. Два розв'язки:
// Варіант 1: через Set – O(n)
function uniqueWithSet(items: number[]): number[] {
return [...new Set(items)];
}
// Варіант 2: без Set – O(n²), бо includes лінійний
function uniqueNaive(items: number[]): number[] {
const result: number[] = [];
for (const item of items) {
if (!result.includes(item)) result.push(item);
}
return result;
}Тест-кейси, які варто назвати вголос: порожній масив, масив без дублікатів, усі елементи однакові, NaN (Set вважає NaN рівним собі, includes – теж, а от indexOf – ні). Пояснення складності важить більше за сам код: перший варіант проходить по масиву один раз, другий на кожен елемент шукає лінійно, тому на 10 000 елементів різниця вже помітна. Глибше про складність операцій – у статті про структури даних JavaScript.
this і стрілочні функції
Друга за частотою тема після замикань. Правило коротке: у звичайній функції this визначається способом виклику; стрілочна функція свого this не має і бере його з місця, де була створена.
const user = {
name: 'Оля',
hiRegular() {
console.log(`Привіт, ${this.name}`);
},
hiArrow: () => {
console.log(`Привіт, ${this?.name}`);
},
};
user.hiRegular(); // 'Привіт, Оля' – виклик через крапку
user.hiArrow(); // 'Привіт, undefined' – стрілка взяла зовнішній this
const detached = user.hiRegular;
detached(); // 'Привіт, undefined' – втратили контекстПитання «як полагодити detached» має три відповіді, і сильний кандидат називає всі: bind, обгортка-стрілка, або виклик через об'єкт. А питання «чому стрілка зручна в колбеках класових компонентів і таймерах» перевіряє, чи розумієш ти, що вона не втрачає зовнішній this, бо просто його не має.
Обробка помилок
Коротка рамка для відповіді: синхронні помилки ловить try/catch; помилки в промісах – .catch або try/catch навколо await; викидай екземпляри Error (а не рядки), бо вони мають stack trace; на межах системи (API-запити, парсинг JSON, робота зі сховищем) помилки очікувані, тому обробляй їх явно, а не глобальним «catch усього».
class ApiError extends Error {
constructor(
message: string,
public readonly status: number
) {
super(message);
this.name = 'ApiError';
}
}Питання «навіщо власний клас помилки» – перевірка на досвід: тип помилки дозволяє в одному місці відрізнити «немає мережі» від «токен протух» і відреагувати по-різному.
Live coding: код пишеться інакше, ніж розповідається
Теоретичні відповіді не рятують, коли перед тобою порожній редактор і таймер. Робочий процес на будь-якій задачі: проговори вимоги → напиши найпростішу робочу версію → назви edge cases → покращуй. Добірка типових задач із повними розборами (debounce, groupBy, flatten, EventEmitter) – у статті JavaScript live coding: задачі з розбором.
Як пояснювати складність алгоритму
Не треба бути олімпіадником – достатньо трьох чесних речень: скільки разів проходимо по даних, чи є вкладені цикли по тих самих даних, скільки додаткової пам'яті використовуємо. «Тут O(n), бо один прохід і Set з доступом за константу; пам'ять теж O(n) під Set» – цього формулювання вистачає для більшості frontend-співбесід.
Чекліст самоперевірки за рівнями
- Junior: пояснити типи і coercion своїми словами; написати функцію із замиканням; знати різницю
==/===,let/const/var; розв'язати задачу на масиви без підказок. - Middle: передбачити порядок виводу з setTimeout/Promise; пояснити, коли Map/Set замість об'єкта/масиву і чому; впевнено писати async/await з обробкою помилок; оцінити складність свого рішення.
- Senior: усе вище + аргументувати компроміси: чому тут достатньо простого рішення, де межа оптимізації, як це рішення тестувати й підтримувати.
Кожен пункт перевіряється однаково: не «читав і зрозумів», а «написав код і пояснив уголос». Якщо готуєшся системно, вбудуй ці теми в тижневий план зі статті як підготуватися до технічної співбесіди за 30 днів.