Підготовка до бекенд-співбесіди з Node.js

Питання та відповіді для співбесіди з Node.js 2026

Цілеспрямована добірка питань для співбесіди з Node.js для Backend-розробників. Відповіді структуровані для усної практики: поясніть архітектуру, наведіть приклад і згадайте про компроміси в продакшені.

Почати тестову співбесіду з Node.jsБанківська картка не потрібна. Доступна 1 безкоштовна сесія.
Практика технічних співбесід англійськоюРежим для тих, для кого англійська не є рідною, щоб тренувати технічні співбесіди.

Архітектура та цикл подій (Event Loop)

1Що таке Node.js і для чого його використовують?

Node.js — це середовище виконання JavaScript поза браузером, побудоване на V8 і розширене API (Application Programming Interface) для роботи з мережею, файлами, процесами, потоками даних (streams) і доступом до операційної системи. Його зазвичай використовують для REST API, сервісів GraphQL, серверів WebSocket у реальному часі, мікросервісів, інструментів командного рядка (CLI), проксі та API-шлюзів. Головна перевага — неблокуючий ввід-вивід: один процес може ефективно обслуговувати багато мережевих з’єднань. Компроміс у тому, що тривалі обчислювально інтенсивні операції можуть блокувати цикл подій (event loop), якщо їх не винести в робочі потоки, окремі процеси або інший сервіс.

Відповісти на це запитання з ШІ-тренером

2Як Node.js працює внутрішньо?

JavaScript виконується у V8, а API (Application Programming Interface) Node.js делегують асинхронну роботу операційній системі або пулу потоків libuv. Головний потік JavaScript не чекає завершення операції введення-виведення (I/O); він продовжує виконувати інший код. Коли операція завершується, її зворотний виклик (callback) або продовження Promise стає готовим для циклу подій (event loop). Так Node.js досягає високої конкурентності, хоча користувацький JavaScript зазвичай виконується в одному головному потоці.

Відповісти на це запитання з ШІ-тренером

3Що таке V8, libuv і цикл подій (event loop)?

V8 компілює й виконує JavaScript, а також керує об'єктами JavaScript і пам'яттю. libuv надає кросплатформний цикл подій, інтеграцію асинхронного I/O та робочий пул потоків для операцій на кшталт частини роботи з файловою системою, DNS і криптографії. Цикл подій визначає, коли мають виконуватися зворотні виклики завершених операцій. Сам Node.js з'єднує JavaScript API з нативними можливостями; сам по собі V8 не надає HTTP-сервер, доступ до файлової системи чи API процесів.

Відповісти на це запитання з ШІ-тренером

4Чому Node.js часто називають однопотоковим?

Окремий процес Node.js зазвичай виконує користувацький код JavaScript в одному головному потоці, тому два звичайні обробники запитів не виконують JavaScript в один і той самий час у цьому потоці. Однак платформа не є буквально однопотоковою: `libuv` має пул потоків, операційна система виконує асинхронне введення-виведення (I/O - Input/Output), V8 виконує роботу зі збирання сміття, а Node.js також підтримує `worker_threads` і дочірні процеси. Ця фраза переважно описує стандартну модель виконання JavaScript.

Відповісти на це запитання з ШІ-тренером

5Як Node.js може одночасно обробляти багато запитів?

Node.js ініціює операції вводу-виводу (I/O - Input/Output), а потім повертається в цикл подій (event loop), замість того, щоб блокувати потік для кожного запиту. Наприклад, 1000 викликів до бази даних можуть виконуватися паралельно, поки головний потік продовжує приймати нові з'єднання та обробляти готові зворотні виклики (callbacks). Це добре працює для навантажень, орієнтованих на ввід-вивід (I/O-bound), таких як HTTP-запити, запити до баз даних, робота з файлами та сокетами. Це працює погано, коли обробники запитів виконують важку синхронну роботу, що навантажує центральний процесор (CPU - Central Processing Unit), оскільки це блокує цикл подій і затримує всі інші запити.

Відповісти на це запитання з ШІ-тренером

6Які основні фази циклу подій (event loop) у Node.js?

Цикл подій бібліотеки libuv включає такі фази: timers, pending callbacks, idle/prepare, poll, check та close callbacks. На етапі timers виконуються зворотні виклики (callbacks) `setTimeout` та `setInterval`; poll обробляє багато подій I/O (Input/Output); check виконує зворотні виклики `setImmediate`; close обробляє події закриття, наприклад, закриття сокетів. Node.js також має черги мікрозадач, включно з `process.nextTick` та мікрозадачами `Promise`, які виконуються між зворотними викликами. Завелика кількість мікрозадач може призвести до блокування вводу-виводу (I/O starvation), через що сервіс виглядатиме завислим.

Відповісти на це запитання з ШІ-тренером

7Чим відрізняються синхронні, асинхронні та неблокуючі (non-blocking) операції?

Синхронна операція повертає результат до виконання наступного рядка коду та блокує головний потік, наприклад, `fs.readFileSync` в обробнику запитів. Асинхронна операція повертає свій результат пізніше через зворотний виклик (callback) або `Promise`. Неблокуюча операція означає, що потік може продовжувати виконувати іншу роботу, поки операція очікує завершення. Ці терміни пов'язані, але не ідентичні: асинхронність (async) описує спосіб доставки результату, тоді як неблокуючий характер (non-blocking) описує, чи очікує потік. Обробники запитів на бекенді, як правило, повинні віддавати перевагу асинхронним неблокуючим API.

import fs from 'node:fs';

const syncData = fs.readFileSync('file.txt', 'utf8');

const asyncData = await fs.promises.readFile(
  'file.txt',
  'utf8'
);
Відповісти на це запитання з ШІ-тренером

8Що таке "голодування" циклу подій (event loop starvation)?

"Голодування" циклу подій (event loop) виникає, коли завдання JavaScript або мікрозадачі тримають цикл подій зайнятим, через що він не може своєчасно обробляти I/O (введення-виведення), таймери або нові запити. Поширені причини включають важкі синхронні обчислення, обробку величезних файлів JSON (JavaScript Object Notation), нескінченні ланцюжки `Promise` та надмірне використання `process.nextTick`. Способи вирішення проблеми включають розбиття роботи на частини, обмеження розміру вхідних даних, профілювання CPU (Central Processing Unit), перенесення завдань, що навантажують процесор, до `worker_threads` або іншого сервісу, а також уникнення використання синхронних API на критичних за продуктивністю ділянках обробки запитів.

app.get('/report', (req, res) => {
  const result = performVeryHeavyCalculation();
  res.json(result);
});
Відповісти на це запитання з ШІ-тренером

Асинхронне програмування та обробка помилок

9Що таке зворотний виклик (callback) і що таке пекло зворотних викликів (callback hell)?

Зворотний виклик (callback) — це функція, яка передається для виконання пізніше, часто після завершення асинхронної операції. Класичні зворотні виклики в Node.js зазвичай дотримуються стилю, де помилка йде першою (error-first): `callback(error, result)`. Пекло зворотних викликів виникає, коли залежні операції мають глибоку вкладеність, що ускладнює читання обробки помилок та керування потоком виконання. Рішення полягає у декомпозиції на іменовані функції, використанні `Promise` або `async/await`, та запуску незалежних операцій за допомогою `Promise.all` замість їхнього вкладення.

import fs from 'node:fs';

fs.readFile('file.txt', 'utf8', (err, data) => {
  if (err) {
    console.error(err);
    return;
  }

  console.log(data);
});
Відповісти на це запитання з ШІ-тренером

10Що таке Promise та як працює async/await?

`Promise` представляє майбутній результат асинхронної операції та може перебувати у стані очікування (pending), виконання (fulfilled) або відхилення (rejected). Функція `async` завжди повертає `Promise`. Ключове слово `await` призупиняє лише поточну функцію `async`, поки `Promise` не завершиться (settled); воно не блокує весь процес Node.js. Хороша відповідь повинна згадати обробку помилок за допомогою `try/catch`, уникнення "завислих" (floating) `Promise` та використання `Promise.all` для незалежних завдань, щоб операції не виконувалися послідовно без потреби.

async function loadUser(id) {
  try {
    const user = await repository.findById(id);
    return user;
  } catch (error) {
    console.error(error);
    throw error;
  }
}

const [user, settings] = await Promise.all([
  getUser(),
  getSettings()
]);
Відповісти на це запитання з ШІ-тренером

11Чим відрізняються `process.nextTick`, `queueMicrotask`, `setImmediate` та `setTimeout`?

`process.nextTick` виконується після завершення поточної операції та до того, як цикл подій (event loop) перейде до наступного етапу. `queueMicrotask` планує стандартне мікрозавдання JavaScript, подібно до продовжень `Promise`. `setImmediate` виконується на фазі перевірки (check phase). `setTimeout(fn, 0)` виконується на фазі таймерів (timers phase) після мінімальної затримки. Зазвичай `nextTick` виконується перед мікрозавданнями `Promise`, а порядок виконання `setTimeout(0)` та `setImmediate` залежить від контексту; після зворотних викликів (callbacks) вводу-виводу `setImmediate` часто виконується першим.

setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
Promise.resolve().then(() => console.log('promise'));
process.nextTick(() => console.log('nextTick'));
Відповісти на це запитання з ШІ-тренером

12Як слід обробляти помилки в Node.js?

Синхронний код використовує `try/catch`. Код на основі промісів (promises) повинен використовувати `await` всередині `try/catch` або прикріплювати `.catch`. Зворотні виклики (callbacks) за шаблоном 'error-first' повинні перевіряти аргумент помилки перед використанням результату. У серверних застосунках важливими є централізоване проміжне програмне забезпечення (middleware) для обробки помилок, логування в контексті запиту, безпечні повідомлення клієнту та коректне завершення роботи (graceful shutdown). Подію `uncaughtException` не слід використовувати для підтримки роботи пошкодженого процесу; зазвичай це сигнал для логування, очищення та перезапуску.

try {
  JSON.parse(input);
} catch (error) {
  handleError(error);
}

try {
  await operation();
} catch (error) {
  handleError(error);
}

operation((error, result) => {
  if (error) return handleError(error);
  return result;
});
Відповісти на це запитання з ШІ-тренером

Модулі та керування проєктами

13Що таке модулі в Node.js?

Модуль — це ізольована одиниця коду з явно експортованим інтерфейсом. Node.js підтримує вбудовані модулі, такі як node:fs та node:http, локальні модулі проєкту, npm-пакети, модулі CommonJS та модулі ECMAScript. Модулі роблять межі відповідальності чіткішими та дозволяють повторне використання коду. Компромісом у робочому середовищі є зв'язність: якщо модулі занадто глибоко імпортують один одного або приховують побічні ефекти під час імпорту, тестування та поведінка під час запуску стають складнішими для аналізу.

// math.js
export function add(a, b) {
  return a + b;
}

// app.js
import { add } from './math.js';
Відповісти на це запитання з ШІ-тренером

14Чим відрізняються `require` у CommonJS та `import` у ES Modules?

CommonJS використовує `require` та `module.exports`, тоді як ES Modules використовують `import` та `export`. ES Modules підтримують статичний аналіз та `await` на верхньому рівні, і вони обираються за допомогою `type: module` у `package.json` або файлів з розширенням `.mjs`. CommonJS часто завантажується синхронно і може бути позначений розширенням `.cjs`. У сучасних проєктах на Node.js існують обидві системи, тому хороша відповідь уникає надмірного спрощення та пояснює, що розширення файлу, `package.json` і контекст проєкту визначають режим модулів.

// CommonJS
const fs = require('node:fs');
module.exports = { calculate };

// ES Modules
import fs from 'node:fs';
export { calculate };
Відповісти на це запитання з ШІ-тренером

15Що таке npm, `package.json` та `package-lock.json`?

npm — це менеджер пакетів та CLI (Command Line Interface). Файл `package.json` описує скрипти, метадані, залежності та залежності для розробки (devDependencies). Файл `package-lock.json` фіксує точне дерево залежностей, щоб встановлення були відтворюваними. В системах CI (Continuous Integration) зазвичай надають перевагу команді `npm ci`, оскільки вона виконує встановлення строго з lock-файлу та завершується з помилкою, якщо `package.json` і lock-файл відрізняються. Це захищає збірки від випадкового відхилення версій залежностей (dependency drift).

{
  "name": "api",
  "type": "module",
  "scripts": {
    "start": "node src/server.js",
    "test": "node --test"
  },
  "dependencies": {},
  "devDependencies": {}
}
Відповісти на це запитання з ШІ-тренером

16Як слід керувати конфігурацією та змінними середовища?

Node.js зчитує змінні середовища через `process.env`, але їхні значення є рядками, тому вони повинні перевірятися та конвертуватися під час запуску. Секрети не повинні зберігатися в репозиторії, файл `.env` має ігноруватися, а секрети продуктового середовища повинні надходити від платформи або менеджера секретів. Надійні сервіси швидко завершують роботу з помилкою (fail fast), якщо відсутня обов'язкова конфігурація. `NODE_ENV` є загальноприйнятою угодою (convention), а не межею безпеки (security boundary), тому код не повинен покладатися на нього як на захищений механізм.

const port = Number(process.env.PORT ?? 3000);
const databaseUrl = process.env.DATABASE_URL;

if (!databaseUrl) {
  throw new Error('DATABASE_URL is required');
}
Відповісти на це запитання з ШІ-тренером

Події (Events), буфери (Buffers) та потоки (Streams)

17Що таке `EventEmitter`?

`EventEmitter` реалізує патерн "видавець-підписник" (publish-subscribe) всередині процесу Node.js. Метод `on` реєструє слухача, `once` реєструє слухача для однієї події, `off` видаляє слухача, а `emit` синхронно викликає слухачів у порядку їхньої реєстрації. Синхронна природа є важливою: повільний слухач блокує генератор подій. Подія `error` є особливою, оскільки необроблена подія помилки може призвести до аварійного завершення процесу. Системи з тривалим часом роботи також повинні видаляти невикористовуваних слухачів, щоб уникнути витоків пам'яті.

import { EventEmitter } from 'node:events';

const emitter = new EventEmitter();

emitter.on('orderCreated', order => {
  console.log(`Order ${order.id} created`);
});

emitter.emit('orderCreated', { id: 42 });
Відповісти на це запитання з ШІ-тренером

18Для чого використовується Buffer?

Buffer представляє послідовність байтів фіксованого розміру. Він використовується для роботи з файлами, сокетами TCP (Transmission Control Protocol), тілами запитів або відповідей HTTP (Hypertext Transfer Protocol), зображеннями, криптографією та бінарними протоколами. Buffer.from створює буфер з наявних даних, Buffer.alloc створює пам'ять, заповнену нулями, а Buffer.allocUnsafe працює швидше, але може містити стару пам'ять, поки її не буде перезаписано. Це робить allocUnsafe доречним лише тоді, коли код негайно заповнює буфер і ніколи не розкриває застарілі дані.

const buffer = Buffer.from('Hello', 'utf8');

console.log(buffer);
console.log(buffer.toString('utf8'));

const safe = Buffer.alloc(1024);
const fast = Buffer.allocUnsafe(1024);
Відповісти на це запитання з ШІ-тренером

19Що таке потоки даних (streams) і які існують їх типи?

Потоки даних обробляють дані частинами (chunks), замість того, щоб завантажувати все в пам'ять. Основними типами є `Readable`, `Writable`, `Duplex` та `Transform`. Файли, об'єкти HTTP-запитів та відповідей, TCP-сокети та потоки стиснення gzip є типовими прикладами. Потоки даних є корисними для роботи з великими файлами та мережевим трафіком, оскільки вони зменшують використання пам'яті та затримку. У відповіді рівня продакшену слід зазначити, що помилки в потоках повинні коректно оброблятися по всьому ланцюжку.

import fs from 'node:fs';

const input = fs.createReadStream('large.log');
const output = fs.createWriteStream('copy.log');

input.pipe(output);
Відповісти на це запитання з ШІ-тренером

20Що таке зворотний тиск (backpressure) і чому корисний метод pipeline?

Зворотний тиск виникає, коли джерело даних створює фрагменти (chunks) швидше, ніж приймач може їх споживати. Без зворотного тиску споживання пам'яті може зростати, доки процес не сповільниться або не завершиться аварійно. Потоки даних та метод `pipe` можуть координувати потік, але метод `pipeline` з модуля `node:stream/promises` є безпечнішим для багатоетапних ланцюжків, оскільки він поширює помилки, закриває пов'язані потоки та повертає `Promise`. Це хороший варіант за замовчуванням для стиснення файлів, завантажень, скачувань та трансформацій потоків.

import fs from 'node:fs';
import zlib from 'node:zlib';
import { pipeline } from 'node:stream/promises';

await pipeline(
  fs.createReadStream('input.txt'),
  zlib.createGzip(),
  fs.createWriteStream('input.txt.gz')
);
Відповісти на це запитання з ШІ-тренером