Подготовка к Node.js backend интервью

Вопросы и ответы на Node.js собеседовании 2026

Сфокусированная подборка Node.js вопросов для backend интервью. Ответы построены для устной практики: архитектура, пример и production-компромиссы.

Начать Node.js mock interview на русском Практика технического интервью на английскомРежим, в котором не носители языка могут потренироваться проходить интервью.

Архитектура и event loop

1Что такое Node.js и для чего он используется?

Node.js — среда выполнения JavaScript вне браузера, построенная на V8 и расширенная API для сети, файлов, процессов, потоков и ОС. Его используют для REST API, GraphQL, WebSocket-серверов, микросервисов, CLI, proxy и API Gateway. Сильная сторона — неблокирующий I/O: один процесс эффективно обслуживает много соединений. Компромисс в том, что длительные CPU-heavy вычисления могут заблокировать event loop, если не вынести их в workers, processes или отдельный сервис.

2Как Node.js работает внутри?

JavaScript выполняется в V8, а Node.js API передают асинхронную работу операционной системе или thread pool libuv. Главный JavaScript-поток не ждет завершения I/O и продолжает выполнять другой код. Когда операция завершается, ее callback или Promise continuation становится доступным event loop. Поэтому Node.js достигает высокой конкурентности, хотя пользовательский JavaScript обычно выполняется в одном главном потоке.

3Что такое V8, libuv и event loop?

V8 компилирует и выполняет JavaScript, управляет объектами и памятью. libuv предоставляет кроссплатформенный event loop, интеграцию async I/O и worker thread pool для части filesystem, DNS и crypto операций. Event loop решает, когда запускать callbacks завершенных операций. Node.js связывает JavaScript API с native capabilities; сам V8 не дает HTTP-сервер, filesystem или process API.

4Почему Node.js называют однопоточным?

Один Node.js-процесс обычно выполняет пользовательский JavaScript в одном главном потоке, поэтому два обычных request handler не могут выполнять JavaScript одновременно в одном и том же потоке. Но платформа не буквально однопоточная: есть thread pool libuv, async I/O ОС, garbage collection V8, worker_threads и child processes. Фраза относится прежде всего к default модели исполнения JavaScript.

5Как Node.js обрабатывает много запросов одновременно?

Node.js запускает I/O операции и возвращается в event loop вместо того, чтобы блокировать поток на каждый запрос. Например, 1000 запросов к базе могут быть in flight, пока главный поток принимает соединения и обрабатывает готовые callbacks. Это хорошо для I/O-bound workloads: HTTP, база, файлы, sockets. Но тяжелый синхронный CPU-код в handler блокирует event loop и задерживает все остальные запросы.

6Какие основные фазы есть у event loop?

В libuv есть phases: timers, pending callbacks, idle/prepare, poll, check и close callbacks. Timers запускает setTimeout/setInterval, poll обрабатывает I/O, check запускает setImmediate, close обрабатывает закрытия sockets. Отдельно существуют microtask queues: process.nextTick и Promise microtasks. Если микрозадач слишком много, они могут не дать event loop своевременно перейти к I/O.

7Чем отличаются синхронные, асинхронные и неблокирующие операции?

Синхронная операция возвращает результат до перехода к следующей строке и блокирует главный поток, например fs.readFileSync в request handler. Асинхронная операция возвращает результат позже через callback или Promise. Неблокирующая модель означает, что поток может делать другую работу, пока операция не завершилась. В server handlers обычно выбирают 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 starvation возникает, когда JavaScript-код или microtasks слишком долго занимают event loop и он не успевает обрабатывать I/O, timers и новые запросы. Причины: тяжелые синхронные вычисления, огромный JSON processing, бесконечные Promise chains, чрезмерный process.nextTick. Решения: разбивать работу, ограничивать входные данные, профилировать CPU, использовать worker_threads или отдельный сервис и избегать sync API на горячем пути.

app.get('/report', (req, res) => {
  const result = performVeryHeavyCalculation();
  res.json(result);
});

Async programming и errors

9Что такое callback и callback hell?

Callback — функция, которую передают для последующего выполнения, часто после завершения async операции. Классический Node.js стиль — error-first callback: callback(error, result). Callback hell появляется при глубокой вложенности зависимых операций, из-за чего сложнее читать flow и обрабатывать ошибки. Помогают named functions, Promises, 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 представляет будущий результат async операции и имеет состояния pending, fulfilled или rejected. async-функция всегда возвращает Promise. await приостанавливает только текущую async-функцию, но не весь Node.js процесс. В хорошем ответе важно упомянуть try/catch, обработку rejected Promises, отсутствие висящих или необработанных 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 microtask, похожую на Promise continuation. setImmediate выполняется в check phase. setTimeout(fn, 0) выполняется в timers phase после минимальной задержки. Обычно nextTick идет раньше Promise microtasks, а порядок setTimeout(0) и setImmediate зависит от контекста; после I/O callback 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. Promise-based код обрабатывают через await в try/catch или .catch. Error-first callbacks сначала проверяют error. В серверных приложениях нужны central error middleware, request-context logging, безопасные сообщения клиенту и 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 есть built-in modules вроде node:fs и node:http, локальные модули проекта, npm packages, CommonJS и ECMAScript modules. Модули помогают разделять ответственность и переиспользовать код. Production-компромисс — coupling: глубокие циклические импорты и side effects при import усложняют тестирование и startup behavior.

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

// app.js
import { add } from './math.js';

14Чем CommonJS require отличается от ES Modules import?

CommonJS использует require и module.exports, ES Modules используют import и export. ESM поддерживает static analysis и top-level await; режим включается через package.json type: module или .mjs. CommonJS часто загружается синхронно и может обозначаться .cjs. В современных проектах встречаются оба формата, поэтому важно учитывать file extension, 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 — package manager и CLI. package.json описывает scripts, metadata, dependencies и devDependencies. package-lock.json фиксирует точное дерево зависимостей, чтобы установка была воспроизводимой. В CI обычно используют npm ci: он ставит строго по lock file и падает, если package.json и lock не согласованы. Это защищает build от случайного drift зависимостей.

{
  "name": "api",
  "type": "module",
  "scripts": {
    "start": "node src/server.js",
    "test": "node --test"
  },
  "dependencies": {},
  "devDependencies": {}
}

16Как управлять конфигурацией и переменными окружения?

Node.js читает env vars через process.env, но значения являются строками, поэтому их нужно валидировать и приводить к типам при startup. Secrets нельзя хранить в репозитории, .env должен быть в .gitignore, production secrets должны приходить из платформы или secret manager. Сервис должен fail fast, если обязательная config отсутствует. NODE_ENV — соглашение, а не защищенный 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, Buffer и streams

17Что такое EventEmitter?

EventEmitter реализует publish-subscribe внутри Node.js процесса. on регистрирует listener, once — listener на одно событие, off удаляет listener, emit синхронно вызывает listeners в порядке регистрации. Синхронность важна: медленный listener блокирует emitter. Событие error особое: необработанный error event может завершить процесс. В долгоживущих системах нужно удалять ненужные listeners, чтобы не создавать memory leaks.

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 sockets, HTTP bodies, images, crypto и binary protocols. Buffer.from создает buffer из данных, Buffer.alloc выделяет zero-filled память, Buffer.allocUnsafe быстрее, но может содержать остаточные данные из памяти до перезаписи. allocUnsafe безопасен только если код сразу полностью заполняет buffer и не раскрывает stale data.

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 и какие виды существуют?

Streams обрабатывают данные частями, не загружая все в память. Основные виды: Readable, Writable, Duplex и Transform. Примеры: файлы, HTTP request/response, TCP sockets, gzip streams. Streams полезны для больших файлов и сетевого трафика, потому что снижают memory usage и latency. В production важно корректно обрабатывать errors по всей цепочке.

import fs from 'node:fs';

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

input.pipe(output);

20Что такое backpressure и зачем нужен pipeline?

Backpressure возникает, когда источник генерирует chunks быстрее, чем получатель может их обработать. Без backpressure память может расти до замедления или падения процесса. Streams и pipe умеют координировать flow, но pipeline из node:stream/promises безопаснее для сложных цепочек: он пробрасывает errors, закрывает связанные streams и возвращает Promise. Это хороший default для compression, uploads, downloads и transformations.

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')
);