Коротка відповідь. Node.js-співбесіда майже завжди складається з трьох шарів: як працює сам Node (event loop, асинхронність), як ти будуєш HTTP-сервіси (маршрути, middleware, обробка помилок) і як працюєш із даними (база, транзакції, кеш). Готуйся до кожного шару окремо: спочатку впевнене пояснення, потім робочий код. Нижче – маршрут і типові запитання з відповідями.
Що реально перевіряють
Інтерв'юер на backend-позицію хоче зрозуміти три речі:
- Чи розумієш ти модель виконання Node.js. Один потік, event loop, неблокуючий I/O. Без цього решта відповідей – завчені слова.
- Чи вмієш ти будувати надійні API. Валідація вхідних даних, помилки, статуси, структура коду.
- Чи розумієш ти дані. Коли транзакція обов'язкова, чому N+1 запитів – проблема, що кешувати.
Шар 1 – модель виконання
Мінімум, який треба пояснювати без запинки:
- Event loop. Node виконує JavaScript в одному потоці; довгі операції (мережа, диск) віддаються системі, а колбеки повертаються в чергу. Тому сервер обробляє тисячі з'єднань без потоку на кожне.
- Блокування. Синхронний код (
JSON.parseвеличезного файла, важкий цикл) зупиняє ВЕСЬ сервер, а не один запит. Це найчастіша пастка в запитаннях «чому сервіс завис». - Microtasks vs macrotasks.
Promise.thenвиконується раніше заsetTimeout(fn, 0). Загальна механіка та сама, що в браузері – детальний розбір є в статті про Event Loop у JavaScript. - Streams – на рівні ідеї. Читати файл на 2 ГБ через
fs.readFileозначає покласти 2 ГБ у пам'ять; stream обробляє дані шматками. Достатньо вміти пояснити навіщо і навести приклад pipe з файла у відповідь HTTP. - Модулі. Різниця CommonJS (
require) і ESM (import), і чому в сучасних проєктах ESM. Першоджерело: офіційні гайди Node.js.
Шар 2 – HTTP-сервіси
Middleware в Express
Middleware – це функції, через які проходить запит до того, як дійде до обробника. Логування, auth, парсинг тіла – усе це ланцюжок:
import express from 'express';
const app = express();
app.use(express.json());
// Простий middleware авторизації
app.use((req, res, next) => {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: 'Unauthorized' });
}
next();
});На співбесіді питають не «що таке middleware», а «в якому порядку вони виконуються» (у порядку реєстрації) і «що станеться, якщо забути next()» (запит зависне).
Nest на рівні концепцій
Якщо вакансія на Nest, треба розуміти dependency injection: клас оголошує залежності в конструкторі, фреймворк їх створює і підставляє. Це спрощує тестування – замість реального сервісу підставляєш mock:
@Injectable()
export class OrdersService {
constructor(private readonly repository: OrdersRepository) {}
findByUser(userId: string) {
return this.repository.findMany({ userId });
}
}Достатньо пояснити навіщо DI (слабка зв'язаність, тестованість), не переказуючи документацію.
Шар 3 – дані
- Транзакції. Коли дві операції мають виконатися разом або не виконатися взагалі (списати кошти + створити замовлення), потрібна транзакція. Вміти навести приклад, де без неї дані розсипаються.
- N+1. Отримати 100 замовлень одним запитом, а потім по одному запиту на кожного покупця – це 101 запит. Рішення: JOIN або batch-завантаження.
- Вибір бази. Мати аргументовану позицію, а не «Mongo, бо я його знаю». Розгорнутий розбір: SQL vs NoSQL: як пояснити вибір на співбесіді.
- Кеш. Коли варто, а коли ні: Redis і кешування.
Типові запитання з відповідями
Чому Node.js однопотоковий, але обробляє багато запитів одночасно? JavaScript виконується в одному потоці, але I/O-операції делегуються системі через libuv. Поки база відповідає на запит A, Node обробляє запит B. Паралельний тут I/O, а не JavaScript.
Що станеться, якщо в обробнику виконати важке синхронне обчислення? Заблокується event loop: усі інші запити чекатимуть. Рішення: worker threads, черга задач або винесення обчислення в окремий сервіс.
Чим process.nextTick відрізняється від setImmediate?
nextTick виконується до наступної фази event loop (раніше за проміси), setImmediate – в окремій фазі після I/O. Практичне правило: обидва рідко потрібні в прикладному коді, і вміння сказати це вголос теж цінується.
Як обробляти помилки в асинхронному коді?
try/catch навколо await, централізований error-middleware в Express, обов'язковий обробник unhandledRejection на рівні процесу. Помилка без обробки в промісі може покласти процес.
Навіщо змінні середовища, а не конфіг у коді? Секрети не потрапляють у git, конфігурація змінюється між середовищами без перезбирання. Питання-маркер: перевіряють гігієну, а не знання.
Що таке ідемпотентність і чому вона важлива для API? Повторний виклик дає той самий результат. PUT ідемпотентний, POST зазвичай ні. Важливо для retry: клієнт може безпечно повторити запит після таймаута.
Як би ти зробив rate limiting? На рівні ідеї: лічильник запитів на ключ (IP/користувач) з вікном часу, зберігання в Redis, відповідь 429 при перевищенні. Плюс згадати, що на практиці часто це робить API gateway.
Live coding: endpoint з валідацією і помилками
Типова задача рівня Junior/Middle: «зроби POST /orders». Сильне рішення відрізняється не фічами, а охайністю країв:
import express from 'express';
const app = express();
app.use(express.json());
type CreateOrderBody = {
productId: string;
quantity: number;
};
function validateOrder(body: unknown): CreateOrderBody | null {
if (typeof body !== 'object' || body === null) return null;
const { productId, quantity } = body as Record<string, unknown>;
if (typeof productId !== 'string' || productId.length === 0) return null;
if (typeof quantity !== 'number' || !Number.isInteger(quantity) || quantity < 1) {
return null;
}
return { productId, quantity };
}
app.post('/orders', async (req, res, next) => {
try {
const order = validateOrder(req.body);
if (!order) {
return res.status(400).json({ error: 'productId і quantity (ціле ≥ 1) обов’язкові' });
}
const created = await ordersService.create(order);
res.status(201).json(created);
} catch (error) {
next(error);
}
});
// Централізований обробник помилок – останнім
app.use((error: Error, _req: express.Request, res: express.Response, _next: express.NextFunction) => {
console.error(error);
res.status(500).json({ error: 'Internal server error' });
});Коментуй уголос: чому 400 vs 500, чому валідація до сервісу, що б ти покрив тестами (валідатор і обидві гілки обробника).
Чекліст за рівнями
Junior:
- пояснити event loop і навести приклад блокування;
- зробити CRUD-endpoint з валідацією і статусами;
- знати різницю GET/POST/PUT/DELETE і коди 200/201/400/401/404/500;
- базовий SQL: SELECT з JOIN, INSERT.
Middle:
- усе вище + транзакції і рівні ізоляції на рівні розуміння;
- аргументований вибір бази і кешу;
- структура застосунку: шари, DI, тестованість;
- retry, ідемпотентність, rate limiting, graceful shutdown.
Типові помилки кандидатів
- Завчений event loop без застосування. Кандидат переказує фази циклу, але не може відповісти, чому конкретний endpoint «підвішує» сервер. Тренуй пояснення на прикладах, а не на схемах.
asyncбез обробки помилок. Код на співбесіді без жодногоtry/catchі без error-middleware читається як «у production це впаде». Обробка помилок – не бонус, а базова частина рішення.- Статуси навмання. 200 на створення, 500 на невалідне тіло. Матриця «яка ситуація – який код» вчиться за вечір і сильно впливає на враження.
- «Я просто використовую ORM». ORM – нормально, але за ним треба бачити SQL: що таке індекс, чому запит у циклі – проблема, коли транзакція обов'язкова.
- Мовчазне кодування. Backend-задачі повні неозвучених рішень (валідація, помилки, межі). Якщо не коментуєш хід думок, інтерв'юер бачить лише фінальний код, а оцінює він мислення.
Практичне завдання
Візьми свій pet-проєкт або зроби новий endpoint за прикладом вище і доведи його до «співбесідного» стану: валідація, коректні статуси, централізовані помилки, один тест на happy path і один на невалідний вхід. Потім поясни рішення вголос за 5 хвилин, ніби поруч сидить інтерв'юер. Ця вправа дає більше, ніж ще один перегляд списку питань.
Перед реальною співбесідою пройди хоча б одну пробну: формат сильно відрізняється від самостійного розв'язування. Як це працює – у статті про mock interview.