Подготовка за бекенд интервю по Node.js

Въпроси и отговори за интервю по Node.js 2026

Фокусиран набор от въпроси за интервю по Node.js за бекенд разработчици. Отговорите са структурирани за устна подготовка: обяснете архитектурата, дайте пример и споменете предимствата и недостатъците в продукционна среда.

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

Архитектура и цикъл на събитията

1Какво е Node.js и за какво се използва?

Node.js е среда за изпълнение на JavaScript извън браузъра, изградена върху V8 и разширена с програмни интерфейси (API) за мрежова комуникация, файлове, процеси, потоци и достъп до операционната система. Често се използва за REST API интерфейси, GraphQL услуги, WebSocket сървъри в реално време, микроуслуги, CLI инструменти, прокси сървъри и API шлюзове. Основното му предимство е неблокиращият вход/изход (I/O): един процес може ефективно да обработва множество мрежови връзки. Недостатъкът е, че дългите и натоварващи процесора изчисления могат да блокират цикъла на събитията, освен ако не бъдат преместени в работни нишки, отделни процеси или друга услуга.

Опитайте да отговорите на този въпрос с треньор с ИИ

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

JavaScript се изпълнява във V8, докато програмните интерфейси на Node.js делегират асинхронната работа на операционната система или на пула от нишки на `libuv`. Главната JavaScript нишка не чака завършването на входно-изходните (I/O) операции; тя продължава да изпълнява друг код. Когато операцията приключи, нейната обратна функция или `Promise` продължение стават готови за цикъла на събитията. По този начин Node.js постига висока паралелност, въпреки че потребителският JavaScript код обикновено се изпълнява в една главна нишка.

Опитайте да отговорите на този въпрос с треньор с ИИ

3Какво представляват V8, `libuv` и цикълът на събитията?

V8 компилира и изпълнява JavaScript и управлява JavaScript обектите и паметта. `libuv` предоставя междуплатформен цикъл на събитията, интеграция на асинхронен вход/изход (I/O) и пул от работни нишки за операции като работа с файловата система, DNS заявки и криптография. Цикълът на събитията решава кога трябва да се изпълнят обратните функции (callbacks) на завършените операции. Самото Node.js свързва JavaScript API интерфейсите с нативни възможности на операционната система; V8 сам по себе си не предоставя HTTP сървър, достъп до файловата система или API интерфейси за управление на процеси.

Опитайте да отговорите на този въпрос с треньор с ИИ

4Защо Node.js често се нарича еднонишков?

Един процес на Node.js обикновено изпълнява потребителския JavaScript код в една главна нишка, поради което два стандартни обработчика на заявки не изпълняват JavaScript код едновременно в тази нишка. Въпреки това, платформата не се състои буквално от една нишка: libuv разполага с пул от нишки, операционната система извършва асинхронни входно-изходни операции, V8 изпълнява задачи по почистване на паметта, а Node.js също така поддържа `worker_threads` и дъщерни процеси. Този израз описва главно стандартния модел на изпълнение на JavaScript.

Опитайте да отговорите на този въпрос с треньор с ИИ

5Как Node.js може да обработва много заявки едновременно?

Node.js стартира входно-изходните операции и след това се връща към цикъла на събитията, вместо да блокира по една нишка за всяка заявка. Например, 1000 извиквания към базата данни могат да бъдат в процес на изпълнение, докато главната нишка продължава да приема връзки и да обработва готовите обратни извиквания. Това работи добре за натоварвания, зависими от входно-изходни операции, като HTTP извиквания, заявки към бази данни, файлове и сокети. Работи лошо, когато обработчиците на заявки извършват тежка синхронна работа на процесора, защото това блокира цикъла на събитията и забавя всички останали заявки.

Опитайте да отговорите на този въпрос с треньор с ИИ

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

Цикълът на събитията в libuv включва фази като `timers`, `pending callbacks`, `idle/prepare`, `poll`, `check` и `close callbacks`. Фазата `timers` изпълнява обратните извиквания на `setTimeout` и `setInterval`; `poll` обработва множество входно-изходни (I/O) събития; `check` изпълнява обратните извиквания на `setImmediate`; `close` обработва събития за затваряне, като например затваряне на сокети. Node.js разполага и с опашки за микрозадачи, включително `process.nextTick` и микрозадачи от тип `Promise`, които се изпълняват между обратните извиквания. Твърде много микрозадачи могат да блокират изпълнението на I/O операциите и да накарат услугата да изглежда замръзнала.

Опитайте да отговорите на този въпрос с треньор с ИИ

7По какво се различават синхронните, асинхронните и неблокиращите операции?

Синхронната операция връща резултат преди изпълнението на следващия ред и блокира главната нишка, като например `fs.readFileSync` в обработчик на заявки. Асинхронната операция връща своя резултат по-късно чрез обратна функция или `Promise`. Неблокираща означава, че нишката може да продължи да върши друга работа, докато операцията е в ход. Термините са свързани, но не са идентични: асинхронността описва как се доставя резултатът, докато неблокиращото поведение описва дали нишката изчаква. При обработчици на заявки в бекенда по принцип трябва да се предпочитат асинхронни неблокиращи 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)?

Гладуването на цикъла на събитията се случва, когато изпълнението на JavaScript код или микрозадачи държи цикъла на събитията зает, така че той не може своевременно да обработва входно-изходни операции, таймери или нови заявки. Честите причини включват тежки синхронни изчисления, обработка на огромни обеми JSON данни, безкрайни вериги от `Promise` обекти и прекомерна употреба на `process.nextTick`. Решенията включват разделяне на работата на части, ограничаване на размера на входните данни, профилиране на процесора, преместване на натоварващите процесора задачи към `worker_threads` или друга услуга, както и избягване на синхронни програмни интерфейси в критичните за производителността части от кода.

app.get('/report', (req, res) => {
  const result = performVeryHeavyCalculation();
  res.json(result);
});
Опитайте да отговорите на този въпрос с треньор с ИИ

Асинхронно програмиране и грешки

9Какво представлява обратната функция и какво е "ад на обратните функции" (callback hell)?

Обратната функция е функция, която се подава като аргумент, за да бъде изпълнена по-късно, често след приключване на асинхронна операция. Класическите обратни функции в Node.js обикновено следват стила "първо грешката" (error-first): callback(error, result). "Адът на обратните функции" (callback hell) се получава, когато зависими операции са дълбоко вложени, което прави обработката на грешки и управлението на потока трудни за четене. Решението е декомпозиране в именувани функции, използване на 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 представлява бъдещия резултат от асинхронна операция и може да бъде в състояние на изчакване (pending), изпълнен (fulfilled) или отхвърлен (rejected). async функция винаги връща Promise. await спира временно само текущата async функция, докато Promise не се установи (settles); това не блокира целия процес на Node.js. Добрият отговор трябва да спомене обработката на грешки с try/catch, избягването на висящи 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` се изпълнява след текущата операция и преди цикълът на събитията да продължи напред. `queueMicrotask` планира стандартна JavaScript микрозадача, подобно на продълженията на `Promise`. `setImmediate` се изпълнява във фазата `check`. `setTimeout(fn, 0)` се изпълнява във фазата `timers` след минимално забавяне. Обикновено `nextTick` се изпълнява преди микрозадачите на `Promise`, а редът на изпълнение на `setTimeout(0)` и `setImmediate` зависи от контекста; след I/O обратни извиквания, `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`) код трябва да използва `await` вътре в `try/catch` или да прикрепи `.catch`. Обратните извиквания, при които грешката е първи аргумент (error-first callbacks), трябва да проверяват аргумента за грешка, преди да използват резултата. В сървърните приложения важни са централизираният `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). Файлът `package.json` описва скриптовете, метаданните, зависимостите (`dependencies`) и зависимостите за разработка (`devDependencies`). Файлът `package-lock.json` записва точното дърво на зависимостите, така че инсталациите да бъдат възпроизводими. В среди за непрекъсната интеграция (CI) обикновено се предпочита командата `npm ci`, тъй като тя инсталира пакетите стриктно от lock файла и завършва с грешка, ако има разминаване между него и `package.json`. Това предпазва билдовете от случайно отклонение във версиите на зависимостите.

{
  "name": "api",
  "type": "module",
  "scripts": {
    "start": "node src/server.js",
    "test": "node --test"
  },
  "dependencies": {},
  "devDependencies": {}
}
Опитайте да отговорите на този въпрос с треньор с ИИ

16Как трябва да се управляват конфигурацията и променливите на средата?

Node.js чете променливите на средата чрез `process.env`, но техните стойности са низове и трябва да бъдат валидирани и конвертирани при стартиране. Тайните не трябва да се съхраняват в хранилището с код, файлът `.env` трябва да бъде игнориран, а тайните за продукционна среда трябва да се предоставят от платформата или от система за управление на тайни. Добре проектираните услуги приключват с грешка максимално рано, когато липсва задължителна конфигурация. `NODE_ENV` е конвенция, а не граница на сигурност, затова кодът не трябва да разчита на нея като на защитен механизъм.

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

if (!databaseUrl) {
  throw new Error('DATABASE_URL is required');
}
Опитайте да отговорите на този въпрос с треньор с ИИ

Събития, буфери и потоци

17Какво е EventEmitter?

EventEmitter реализира шаблона издател-абонат в рамките на един 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 сокети, тела на HTTP съобщения, изображения, криптография и двоични протоколи. `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Какво представляват потоците и какви видове съществуват?

Потоците обработват данните на порции, вместо да зареждат всичко в паметта. Основните видове са 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 е полезна?

Обратното налягане възниква, когато източник на данни произвежда порции данни по-бързо, отколкото местоназначението може да ги обработи. Без управление на обратното налягане, използваната памет може да нарасне до степен, в която процесът се забавя или срива. Потоците и `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')
);
Опитайте да отговорите на този въпрос с треньор с ИИ