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

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

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

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

Архитектура и 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 или отдельный сервис.

Попробовать ответить на вопрос AI-тренеру

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

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

Попробовать ответить на вопрос AI-тренеру

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.

Попробовать ответить на вопрос AI-тренеру

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

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

Попробовать ответить на вопрос AI-тренеру

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

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

Попробовать ответить на вопрос AI-тренеру

6Каковы основные фазы цикла событий Node.js?

Node.js использует фазы планирования libuv: pending callbacks, idle/prepare, poll, check и close callbacks, а также обработку таймеров. В poll обрабатываются многие функции обратного вызова ввода-вывода, в check — setImmediate, а таймеры запускают setTimeout/setInterval после достижения порога задержки, когда планирование допускает выполнение. В libuv 1.45 и Node.js 20+ таймеры обрабатываются после poll в каждой итерации, с первоначальным проходом перед входом в цикл ради совместимости. process.nextTick — отдельная очередь Node, а реакции Promise и queueMicrotask используют очередь микрозадач V8; ни одна из них не является фазой libuv. Эти очереди обрабатываются в контрольных точках между функциями обратного вызова. Рекурсивное пополнение очередей может лишать другие задачи возможности выполняться.

Попробовать ответить на вопрос AI-тренеру

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

Синхронность описывает передачу управления: вызов возвращает результат или выбрасывает исключение до продолжения вызывающего кода. Асинхронность означает получение результата позже через функцию обратного вызова, Promise или другое уведомление. Блокирование — ожидание работы или ресурса, при котором текущий поток не может выполнять другую работу; неблокирующие API избегают такого ожидания, например возвращая признак необходимости ожидания либо организуя уведомление. Это разные характеристики: синхронность сама по себе не означает ожидания готовности ввода-вывода, а возврат Promise не делает тяжёлые синхронные вычисления неблокирующими. В Node.js fs.readFileSync — блокирующий синхронный API; fs.promises.readFile выдаёт результат асинхронно, пока основной поток может выполнять другую работу. При обработке запросов следует предпочитать подходящие асинхронные API ввода-вывода.

import fs from 'node:fs';

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

const asyncData = await fs.promises.readFile(
  'file.txt',
  'utf8'
);
Попробовать ответить на вопрос AI-тренеру

8Что такое голодание цикла событий?

Голодание цикла событий возникает, когда синхронный JavaScript или постоянно пополняемые очереди контрольных точек мешают своевременной обработке функций обратного вызова ввода-вывода, таймеров и запросов. Причины — тяжёлые вычисления, операции с большим JSON, рекурсивное планирование микрозадач и чрезмерное использование process.nextTick. Ограничивайте входные данные, профилируйте дорогие операции и переносите вычислительные задачи в worker_threads или другой сервис. При разбиении работы на части передавайте управление следующему обороту цикла событий, например через setImmediate: повторное ожидание уже разрешённых Promise или вызовы queueMicrotask по-прежнему могут мешать обработке ввода-вывода. Избегайте блокирующих синхронных API в часто выполняемых обработчиках запросов.

app.get('/report', (req, res) => {
  const result = performVeryHeavyCalculation();
  res.json(result);
});
Попробовать ответить на вопрос AI-тренеру

Async programming и errors

9Что такое функция обратного вызова и «ад обратных вызовов»?

Функция обратного вызова передаётся другой операции, которая затем её вызывает. Выполнение может быть синхронным, как в Array.map, или асинхронным, как в fs.readFile. Классические асинхронные функции обратного вызова Node.js обычно следуют соглашению «ошибка первым аргументом»: 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);
});
Попробовать ответить на вопрос AI-тренеру

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()
]);
Попробовать ответить на вопрос AI-тренеру

11Чем различаются process.nextTick, queueMicrotask, setImmediate и setTimeout?

process.nextTick планирует работу в очереди next-tick Node; queueMicrotask использует очередь микрозадач V8, в которой выполняются и реакции Promise. В обычной контрольной точке после кода верхнего уровня CommonJS функции nextTick идут перед поставленными в очередь функциями Promise/queueMicrotask. На верхнем уровне ES-модуля выполнение уже происходит в контексте микрозадачи, поэтому микрозадачи могут выполняться перед nextTick. Это зависит от контекста и не является универсальным правилом приоритета, прерывающим работающий JavaScript. setImmediate выполняется в check. setTimeout(fn, 0) ждёт минимального порога таймера и не выполняется немедленно. Их порядок зависит от места планирования; если оба запланированы внутри функции обратного вызова ввода-вывода, первым выполняется setImmediate. Для переносимого откладывания микрозадачи предпочтителен queueMicrotask, если не требуется специфическое поведение nextTick.

// CommonJS file (.cjs): nextTick precedes promise at this top level.
// ES module file (.mjs): promise precedes nextTick at this top level.
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
Promise.resolve().then(() => console.log('promise'));
process.nextTick(() => console.log('nextTick'));
// timeout vs immediate is context-dependent here.
Попробовать ответить на вопрос AI-тренеру

12Как следует обрабатывать ошибки в Node.js?

Для синхронных исключений используйте try/catch. Операции на Promise ожидайте через await внутри try/catch либо назначайте обработчик отклонения; внешний синхронный try/catch не перехватывает исключение из функции обратного вызова, исполняемой позже. В функциях с ошибкой первым аргументом нужно проверять этот аргумент, а события error у EventEmitter требуют подходящих обработчиков. Серверу нужны централизованная обработка ошибок запросов, журналирование полезного контекста и защита внутренних сведений от раскрытия клиенту. После 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;
});
Попробовать ответить на вопрос AI-тренеру

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

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';
Попробовать ответить на вопрос AI-тренеру

14Чем различаются require в CommonJS и import в ES Modules?

CommonJS использует require и module.exports; ES-модули — import и export. require обычно загружает синхронно и может вызываться условно. Статические объявления import открывают граф зависимостей для анализа, а динамический import() возвращает Promise. Современный Node.js также позволяет через require загрузить граф ES-модулей без await верхнего уровня, если соблюдены правила совместимости: ESM не означает обязательного асинхронного выполнения. Режим определяется расширениями .cjs/.mjs, полем type в package.json и применимыми правилами распознавания синтаксиса. ESM поддерживает await верхнего уровня. В CommonJS есть __dirname/__filename; в современном Node.js для файловых ES-модулей доступны import.meta.dirname/filename, а import.meta.url полезен для разрешения путей через URL и совместимости со старыми версиями.

// File: math.cjs (CommonJS)
function calculate(a, b) { return a + b; }
module.exports = { calculate };

// Separate file: math.mjs (ES module)
export function calculate(a, b) { return a + b; }
// These are two separate files, not one combined script.
Попробовать ответить на вопрос AI-тренеру

15Что такое npm, package.json и package-lock.json?

npm — интерфейс командной строки для управления пакетами. package.json описывает метаданные, сценарии и требования к прямым зависимостям, часто диапазонами версий. package-lock.json фиксирует разрешённое дерево зависимостей с версиями пакетов и сведениями для проверки целостности. Файл блокировки приложения следует хранить в системе контроля версий. npm ci полезен в непрерывной интеграции: он устанавливает зависимости по файлу блокировки, удаляет существующий node_modules и завершается ошибкой при расхождении package.json с этим файлом. Файл блокировки уменьшает дрейф зависимостей, но не делает одинаковыми операционные системы, нативные сборки, сценарии жизненного цикла, версии Node и параметры установки. Воспроизводимая сборка также требует контролируемых инструментов и окружения.

{
  "name": "api",
  "type": "module",
  "scripts": {
    "start": "node src/server.js",
    "test": "node --test"
  },
  "dependencies": {},
  "devDependencies": {}
}
Попробовать ответить на вопрос AI-тренеру

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

Переменные окружения доступны через process.env: присутствующие значения являются строками, отсутствующее свойство даёт undefined. При запуске проверяйте обязательные значения и осознанно преобразуйте типы; одного Number для PORT недостаточно, чтобы отклонить пустую строку, NaN или порт за пределами диапазона. При неверной обязательной конфигурации завершайте запуск с понятной ошибкой. Не храните реальные секреты в системе контроля версий; передавайте производственные секреты через платформу развёртывания или хранилище секретов. Пример конфигурации без секретов можно включать в репозиторий. NODE_ENV — соглашение приложения, а не доверенная граница безопасности.

const rawPort = process.env.PORT ?? '3000';
if (!/^\d+$/.test(rawPort)) throw new Error('PORT must be an integer');
const port = Number(rawPort);
if (!Number.isSafeInteger(port) || port < 1 || port > 65535) {
  throw new Error('PORT must be between 1 and 65535');
}
const databaseUrl = process.env.DATABASE_URL;
if (!databaseUrl?.trim()) throw new Error('DATABASE_URL is required');
Попробовать ответить на вопрос AI-тренеру

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 });
Попробовать ответить на вопрос AI-тренеру

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.toString('utf8'));
const safe = Buffer.alloc(1024);
// Prefer alloc for general use. Never expose allocUnsafe bytes before filling them.
const fast = Buffer.allocUnsafe(1024);
fast.fill(0); // All bytes initialized before use.
Попробовать ответить на вопрос AI-тренеру

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';
import { pipeline } from 'node:stream/promises';
try {
  await pipeline(
    fs.createReadStream('large.log'),
    fs.createWriteStream('copy.log')
  );
} catch (error) {
  console.error('Copy failed:', error);
  process.exitCode = 1;
}
Попробовать ответить на вопрос AI-тренеру

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')
);
Попробовать ответить на вопрос AI-тренеру