Подготовка за бекенд интервю по 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?

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. Тези опашки се изпълняват в контролни точки между обратните извиквания. Рекурсивното им попълване може да лиши останалата работа от време за изпълнение.

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

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

Синхронността описва управлението: извикването връща резултат или хвърля изключение, преди извикващият код да продължи. Асинхронността описва резултат, доставен по-късно чрез обратно извикване, Promise или друго уведомяване. Блокирането е чакане за работа или ресурс, докато текущата нишка не може да върши друго. Неблокиращите интерфейси избягват това, например като връщат „операцията би блокирала“ или организират уведомяване. Това са различни характеристики: синхронност сама по себе си не означава чакане за входно-изходна готовност, а връщането на Promise не прави тежките синхронни изчисления неблокиращи. fs.readFileSync е блокиращ синхронен интерфейс; fs.promises.readFile доставя резултата асинхронно, докато главната нишка може да обработва друга работа. В обработката на заявки предпочитайте подходящи асинхронни входно-изходни интерфейси.

import fs from 'node:fs';

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

const asyncData = await fs.promises.readFile(
  'file.txt',
  'utf8'
);
Опитайте да отговорите на този въпрос с треньор с ИИ

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

То възниква, когато синхронен JavaScript или непрекъснато попълвани опашки в контролните точки пречат на навременната обработка на входно-изходни обратни извиквания, таймери и заявки. Причини са тежки изчисления, големи JSON операции, рекурсивно планирани микрозадачи и прекомерна употреба на process.nextTick. Ограничете входните размери, профилирайте скъпата работа и преместете тежките изчисления в worker_threads или друга услуга. При разделяне на работата отстъпвайте управлението до следващ оборот на цикъла, например с setImmediate; многократно очакване на вече разрешени Promise или планиране на queueMicrotask все още може да лишава входа/изхода от изпълнение. Избягвайте блокиращи синхронни интерфейси в често изпълняваните пътища на заявките.

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

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

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);
});
Опитайте да отговорите на този въпрос с треньор с ИИ

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 планира работа в 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.
Опитайте да отговорите на този въпрос с треньор с ИИ

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;
});
Опитайте да отговорите на този въпрос с треньор с ИИ

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

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 модулите?

CommonJS използва require и module.exports, а ES модулите — import и export. require обикновено зарежда синхронно и може да бъде извикан условно. Статичните import декларации разкриват графа на зависимостите за анализ; динамичният import() връща Promise. Съвременният Node.js може с require да зареди и граф от ES модули без await на най-горното ниво според поддържаните правила за съвместна работа; ES модулите не означават винаги асинхронно изпълнение. Режимът се определя от .cjs/.mjs, type в package.json и приложимите правила за разпознаване на синтаксиса. ES модулите поддържат await на най-горното ниво. CommonJS предоставя __dirname/__filename; актуалният Node.js предоставя import.meta.dirname/filename за файлови ES модули. 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.
Опитайте да отговорите на този въпрос с треньор с ИИ

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": {}
}
Опитайте да отговорите на този въпрос с треньор с ИИ

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');
Опитайте да отговорите на този въпрос с треньор с ИИ

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

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.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.
Опитайте да отговорите на този въпрос с треньор с ИИ

19Какво представляват потоците и какви видове съществуват?

Потоците обработват данните на порции, вместо да зареждат всичко в паметта. Основните видове са Readable, Writable, Duplex и Transform. Файловете, обектите за HTTP заявки и отговори, TCP сокетите и gzip потоците са често срещани примери. Потоците са изключително полезни при работа с големи файлове и мрежов трафик, защото намаляват използването на паметта и латентността. Един отговор на ниво продукционна среда трябва да спомене, че грешките в потоците трябва да се обработват правилно по цялата верига.

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;
}
Опитайте да отговорите на този въпрос с треньор с ИИ

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')
);
Опитайте да отговорите на този въпрос с треньор с ИИ