Skip to content

naliferov/NodeJS-Interview-Questions

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 

Repository files navigation

NodeJS ✨🐢🚀✨ Вопросы и рекомендации для собеседований

55 вопросов для собеседования на прикладного бекенд программиста

Прикладной программист пишет продуктовый код, занимается моделированием предметной области и автоматизацией процессов в ней. Прикладному программисту нужно знать node.js как инструмент, его возможности, концепции, преимущества и недостатки, но не нужно глубоко погружаться в код платформы, не нужно строить прослойку между node.js и прикладным кодом, не нужно изобретать фреймворки (внутри продукта), изобретать обобщенные инструменты и библиотеки, не имеющие отношение к предметной области. Если это происходит, то он выполняет две роли - системную и прикладную, они должны быть максимально отделены: отдельные репозитории, отдельное рабочее время и должность, отдельные цели и задачи. Чтобы писать системные вещи смотри вопросы в следующем разделе.

  1. Что можно сделать с помощью for await с объектом request: IncomingMessage?

Прочитать стрим:

let body = [];
for await (const chunk of request) {
  body.push(chunk);
}
  1. Чем в ноде нативно хешировать пароли и в каких случаях для этого нужны зависимости?

crypto.createHash, crypto.scrypt; Bcrypt за свою историю был и в базах уязвимостей и долго маинтейнился. Внешнюю либу имеет смысл тянуть только argon2, если вам нужно лучшую криптостойкость, если ее от вас требуют для сертификации, например. Но crypto/scrypt достаточен для 99% проектов. (Взято из чата https://t.me/nodeua/103239)

  1. Какое API реализует nodejs/undici? Fetch API

  2. Чем современным заменить node:domain API?

Похоже, что это AsyncLocalStorage.

  1. Когда мы можем использовать синхронные версии операций с файлами из node:fs вместо асинхронных и на что обращать внимание, принимая такое решение?

Если от чтения какого-то файла зависит запуск приложения. Когда у нас небольшой скрипт и не критично, что поток заблокируется при чтении файла. Или есть какой-то воркер тред, который служит для последовательного чтения файлов

  1. Приведите лучшие практики для обработки ошибок в асинхронном коде.
  • Возврат ошибки в Error-First Callback.
  • Try catch
  • Централизованная обработка исключение и ошибок с помощью глобальных обработчиков.
  1. Как в проектах на ноде могут появиться уязвимости? Объясните на выбор XSS, Path traversal, SQL injection, CSRF? Как от них защищаться?

Если сайт позволяет выводить пользовательский код как html, то возможен XSS. Чтобы защититься можно выводить код от пользователя как текст, а не как html или использовать библиотеки для очистки и html-экранирования. Также как предосторожность можно для кукис добавить атрибуты HttpOnly и Secure.

  1. Как возможно состояние гонки (race condition) в асинхронном программировании? И как от этого защищаться? Например мы запускаем подряд несколько http-запросов и результат записываем в какую-то переменную. В данном случае мы не знаем какой запрос будет выполнен первым, а какой последним, потому что создается состояние гонки. И соответственно не знаем результат какого запроса будет записать в переменную. В этом контексте можно вспомнить Promise.race и другие методы

    Чтобы это решить мы можем например последовательно выполнять запросы и использоватеть отдельные контексты для записи результатов.

    Тут еще напрашивается пример с SharedArrayBuffer, но он больше к параллельному программированию относится

  2. В чем плюсы и минусы разделения кода на .js и отдельно тайпинги .d.ts?

Плюсы. Позволяет указать типизацию, контракты для js файлов. Позволяет не переписывать js-библиотеку на typescript.

Минусы Нужно менять и .js файлы и .d.ts файлы, когда интерфейс меняется.

  1. Приведите несколько типичных для Node.js паттернов проектирования (по GoF и не только) с примерами.

Factory

class AnimalFactory {
  createAnimal(type) {
    if (type === "dog") {
      return new Dog();
    } else if (type === "cat") {
      return new Cat();
    } else {
      throw new Error("Unknown animal type");
    }
  }
}

Strategy

class Logger {
  constructor(strategy) {
    this.strategy = strategy;
  }

  log(message) {
    this.strategy.log(message);
  }
}

Observer

class EventEmitter {
  constructor() {
    this.events = {};
  }

  on(event, listener) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(listener);
  }

  emit(event, ...args) {
    if (this.events[event]) {
      this.events[event].forEach((listener) => listener(...args));
    }
  }
}
  1. В чем заключается проблема толстых контролеров? (с примерами на ноде)

Это происходит например когда в контроллерах начинают писать много логики и не выносят в сервисы, модели.

В качестве примера можно привести контроллер в котором несколько методов как метод ниже.

router.post("/register", async (req, res) => {
  const { username, password, bday, city, profileInfoFields } = req.body;

  const existingUser = await User.findOne({ username });
  if (existingUser) {
    return res.status(400).json({ message: "User already exists" });
  }

  //code of validation for bday, city, and profileInfoFields

  const hashedPassword = await bcrypt.hash(password, 10);

  const user = new User({
    username,
    password: hashedPassword,
    bday,
  });
  await user.save();

  const userGeo = new UserGeo({
    user_id: user.id,
    city,
  });
  await userGeo.save();

  const profileInfo = new ProfileInfo(profileInfoFields);
  await profileInfo.save();

  const token = jwt.sign({ userId: user._id }, "jwt_secret", {
    expiresIn: "1h",
  });

  res.status(201).json({ token, message: "User registered successfully" });
});
  1. Приведите примеры протекания абстракций (типичных для ноды).

Не уверен, что знаю как правильно ответить на этот вопрос.

В данном случае протекание абстракций вероятно связано с побочными эффектами нодовских абстракций. Например при обработке http-запроса может всплывать неожиданные ошибки связанные с тем, что клиент закрыл соединение раньше времени.

Или при быстром изменении объекта (его свойств) сборщик мусора может не успевать освободить память связанную с этим объектом, что создаст утечку.

Если в асинхронной функции случается ошибка и эта функция не в блоке try, то процесс остановится если не повесить глобальный обработчик unhandledRejection и не обработать это.

  1. Как можно создать Singleton с помощью системы модульности в ноде?

https://www.youtube.com/watch?v=qdJ5yikZnfE

Модули в целом итак синглтоны, то есть если мы имспортируем два модуля с одинаковым названием, то на самом деле это будет один модуль из-за того, что модуль будет взять из кэша.

Я создал небольшой пример:

//модуль test.js
let val = 0;

export function incr() {
  val++;
}
export function getVal() {
  return val;
}

//клиентский код импортирующий test.js модуль два раза
import * as testModule from "./test.js";
import * as otherTestModule from "./test.js";

testModule.incr();
testModule.incr();
console.log(otherTestModule.getVal()); //тут будет значение 2 поскольку это тот же модуль
  1. Как проще всего реализовать паттерн Strategy на JavaScript (и где его использовать в ноде)? https://www.youtube.com/watch?v=hO8VSVv0NqM выбор взаимозаменяемого поведения

Можно использовать там, где есть различные реализации похожего функционала. Можно использовать для логиварония, для уточнения какой data storage использовать, разных способов рендеринга данных.

class RenderStrategy {
  render(data) {
    throw new Error("RenderStrategy должен быть переопределен");
  }
}

class HTMLRenderStrategy extends RenderStrategy {
  render(data) {
    let html = "<table>";
    data.forEach((row) => {
      html += "<tr>";
      row.forEach((cell) => {
        html += `<td>${cell}</td>`;
      });
      html += "</tr>";
    });
    html += "</table>";
    return html;
  }
}

class ConsoleRenderStrategy extends RenderStrategy {
  render(data) {
    data.forEach((row) => {
      console.log(row.join(" | "));
    });
  }
}
class JSONRenderStrategy extends RenderStrategy {
  render(data) {
    return JSON.stringify(data, null, 2);
  }
}

const data = [
  ["Name", "Age", "City"],
  ["Alice", 30, "New York"],
  ["Bob", 25, "Los Angeles"],
  ["Charlie", 35, "Chicago"],
];
const renderer = new Renderer(new HTMLRenderStrategy());
console.log(renderer.render(data));
  1. Приведите пример паттерна Adapter из встроенных библиотек ноды (есть несколько). https://www.youtube.com/watch?v=cA65McLQrR8 Паттерн достижения совместимости
  • util.promisify, util.callbackify
  • переделать какой-то источник данных чтобы можно его было читать как Stream
  1. Какой паттерн проектирования реализует EventEmitter? Observer. EventEmitter генерирует события на которые можно подписаться

  2. Как связаны контракты EventEmitter и Readable? Readable служит для чтения потока. Он наследует EventEmitter, что позволяет потоку Readable генерировать события и подписываться на них.

  3. Какие вы можете привести антипаттерны (или примеры плохого стиля) программирования для node.js? https://www.youtube.com/watch?v=NMUsUiFokr4 Очень короткие или длинные имена. Одно имя, но для разных случаев. На один и тот же случай разные имена.

Применение разных слов для той же категории действия.

const js = {
  setPort: () => {},
  assignAddress: () => {},
  definePath: () => {},
};

Толстые контроллера уже выше упоминали в вопросе.

  1. Зачем нам следующие поля Error: error.cause, error.code, error.message, error.stack? error.cause поле cause используется для указания причины текущей ошибки. Оно позволяет вкладывать ошибки друг в друга, что полезно для понимания цепочки ошибок err.code содержит код ошибки. например ECONNRESET error.message обычно содержит описание ошибки error.stack содержится trace вызовов

  2. Как скопировать папку с вложенными файлами и папками с помощью node:fs? fs.cp(src, dest, { recursive: true }

  3. Можем ли мы делать real-time приложения на Node.js? Можем. Часто можно увидеть пример realtime-чата на node.js + websocket

  4. Какие есть подходы к логированию? Их отличия, плюсы и минусы.

Обычно есть несколько уровней логирования: info, warning, error, and etc.

Также при логировании сериализуется context (обычно в json) с деталями сообщения логивания.

Что касается ресурсов логирования, то можно логировать например в stdout, в файл. Можно логировать в специальные сервисы вроде sentry.io или стораджи вроде Elasticsearch.

Что касается логирования ошибок, то можно сделать глобальный обработчик ошибок и все ошибки логировать в нем.

В своей работы я предпочитаю системную политику ведения логов (и не только). Чтобы был определен формат логирования, когда и что нужно логировать, а когда не нужно. Например нет смысла вручную логировать возникшие ошибки в локальном участке кода, если для этого есть глобальный обработчик который логирует ошибку, и т.д. Можно не логировать аблютные пути stacktrace, а только относительные.

При логировании стоит учитывать насколько важно не потерять логи, через сколько логи должны удаляться, архивироваться. В каком случае уместна будет буферизация.

В работе важно не только как устроена запись логов, а и то насколько эффективно окружение разработки, насколько оперативно разработчики могут найти инфу по ошибке, отслеживать контект ошибки или производить расследования бизнес-кейсов.

  1. Где хранить секреты? (ключи api, точены и пароли от баз данных) Обычно для хранения таких данных используются хранилища вроде Azure Key Vault, Hashicorp Vault, а секреты пробрасываются в приложение через env.

  2. Почему нужно делать return await внутри асинхронных функций и методов а не возвращать промис? Return await, это позволяет правильно обрабатывать ошибки, возникающие внутри асинхронных операций с использованием try-catch. Также это делает стек вызовов при ошибке более информативным.

  3. Как не заблокировать обслуживание других пользователей, обрабатывая запрос от одного из них?

По умолчанию не должно быть блокировок если не использовать блокирующие операции при обработке запроса и не создавать синхронные циклы по длинным цепочкам. Дополнительно можно привлечь воркер треды для обработки.

  1. Что делать если обработка запроса привела к необходимости завершить процесс (ведь он обслуживает много запросов параллельно)?

Это похоже на архитектурный вопрос. Обычно в продакшен среде работают несколько экземпляров приложения и в данном случае должна быть возможность подать сигнал о том, что на этот процесс не нужно слать новые запросы. Далее нужно записать необходимые логи, потом желательно дообработать другие запросы, которые уже успели прийти на процесс и далее остановить процесс.

  1. Какие стили и парадигмы программирования вы используете в node.js приложениях? Почему?

Я не встречал мультипарадигменный подход там где я работал. Обычно это было ООП и GoF-паттерны. Мне самому нравится простота процедурного стиля и возможно хорошо его использовать там где ООП хуже подходит, судя по рассказам эти некоторые вещи из доменной области могут быть. В ФП я слабо разбираюсь. Надо будет ознакомиться https://www.youtube.com/watch?v=pn5myCmpV2U&list=PLHhi8ymDMrQZGa7hYnwBQufg9XXoJr5LP&index=1

  1. В чем слабые стороны node.js? Что на ноде писать плохо или невозможно?

Такие системы, которые требуют максимальной производительности или какие-то специфические системы распределенных вычислений где например Rlang подойдет лучше. Нужно быть осторожным

  1. В чем разница между stateful and stateless подходами для node.js приложений? Как выбрать?
  2. Как ограничить пропускную способность эндпоинта (кол-во запросов в единицу времени)?
  3. В чем опасность примесей (mixins) для прикладного кода? (с типичными примерами на Node.js)
  4. Как реализовать архитектурную границу в приложениях на node.js?
  5. Что такое DI (внедрение зависимостей) и как его реализовать на ноде? (желательно несколько вариантов)
  6. Почему middleware является антипаттерном? И как писать без него?
  7. Как снизить зацепление кода в приложениях на node.js?
  8. Почему нужно добавлять префикс node: при загрузке встроенных модулей?
  9. Зачем нужен AbortController? Приведите примеры API, где он используется.
  10. JSON сериализация и десериализация может работать долго и заблокировать поток, что с этим делать?
  11. Как могут утечь все соединения из пула конекшенов к базе данных и как это предотвратить?
  12. Как вы организовываете слой доступа к данным?
  13. В чем преимущество async/await и промисов перед callback в ноде? Где невозможно обойтись без callback?
  14. Что делать, если в двух частях одного приложения вам нужны разные версии npm зависимостей?
  15. Какие Web API появились в ноде в последнее время и зачем их туда тянут?
  16. Что можно использовать вместо устаревших pm2 и forever в современном мире?
  17. Как сделать бизнес-логику независимой от фреймворка и от протокола, через который приходят запросы?
  18. Почему нам больше не нужны axios, request, node-fetch?
  19. Для чего нам могут быть необходимы очереди внутри приложения и внешние MQ системы?
  20. Чем может быть опасно, если зависимость патчит глобальные объекты, классы и прототипы?
  21. Что такое Node.js LTS и что он нам дает?
  22. Для чего нам Websocket, почему в 2023 брать socket.io плохой вариант и что брать для Websocket?
  23. Что дает флаг --watch?
  24. В каком состоянии сейчас нативный test runner в node.js?
  25. Есть ли возможность в node.js поставлять приложение в виде одного исполняемого файла, как и зачем?
  26. Какие есть способы трекинга асинхронных контекстов и нужны ли они вообще?
  27. Когда и как нужно обновлять версии node.js в проектах?

60 вопросов для собеседования на системного Node.js программиста

Системный (платформенный) программист пишет код, не связанный с предметной областью: фреймворки, сетевые протоколы, транслятор, компиляторы, интерпретаторы, библиотеки, занимается вещами, которые могут быть переиспользованы в сотнях и тысячах разных проектов. Это называется производство средств производства. Систем программисту нужно знать node.js гораздо глубже, не только, его возможности, концепции, преимущества и недостатки, но и недокументированные возможности и даже баги, особенности платформы, которые очень редко используются, потому, что он строит прослойку между node.js и прикладным кодом, а прослойка эта позволяет делать прикладной код более абстрактным и приближенным к предметной области.

  1. Чего не хватает в ESM, но есть (поддерживается) в CJS?
  2. Для чего используется new Error.captureStackTrace?
  3. Почему node.js не однопоточный? Докажите, что даже не был однопоточным.
  4. Как связаны node:async_hooks и AsyncLocalStorage?
  5. Какие в ноде встроенные средства сериализации аналогичны JSON только для бинарной сериализации?
  6. Как следить за изменениями файлов и директорий на диске и какие с этим могут возникать проблемы?
  7. Чем заменить deprecated fs.exists и почему его выпиливают из ноды?
  8. Что такое back pressure для стримов и какая проблема была бы без него?
  9. Как защитить SharedArrayBuffer от записи из разных worker_threads?
  10. Докажите, что любой модуль в ноде при загрузке оборачивается в функцию и создает замыкание?
  11. Где в ноде используется паттерн Revealing constructor (открытый конструктор, есть много таких мест)?
  12. Как сделать переопределение write для экземпляра Writable без создания класса наследника?
  13. В чем причина медленных вызовов из JavaScript кода к аддонам на C, C++ или подключенных через N-API?
  14. Что такое MessagePort и BroadcastChannel?
  15. Чем отличаются fs.stat, fs.fstat, fs.lstat?
  16. Почему важно выполнять правило eslint: consistent-return учитывая оптимизацию v8?
  17. Зачем в ноде есть WASI и какие возможности он нам дает?
  18. Что можно сделать при помощи node:vm (любые примеры)?
  19. Какие вы знаете deprecated API и какова стратегия их вывода из употребления?
  20. Какие вы знаете проблемы, баги и узкие места в node.js?
  21. Объясните, как можно написать (или напишите) адаптеры асинхронности promisify и callbackify?
  22. Почему у event loop есть фазы? Почему мало одной очереди?
  23. Чем отличаются микротаски и макротаски?
  24. В чем особенности обработки uncaught exceptions в Node.js?
  25. Чем отличаются nextTick, setImmediate и setTimeout?
  26. Зачем есть ref() и unref() у таймеров, сокетов, серверов и других подобных классов?
  27. Почему server.connections сделали deprecated и что как теперь получить подключения?
  28. Перечислите основные случаи, приводящие к утечке памяти и как с этим бороться?
  29. Чем отличается node:cluster и node:child_process? И когда cluster может становиться узким местом?
  30. В каких случаях нужно отключать автоматическую сборку мусора и брать ее вызов в свои руки?
  31. Какие есть способы отладки приложений и в каких случаях вы их используете?
  32. Как сбросить кеш require для определенной библиотеки? Как быть в случае ESM?
  33. Откуда берутся идентификаторы __dirname и __filename, require и import, fetch и Array?
  34. Почему следует отказаться от использования библиотеки node:url?
  35. Какие можно предложить стратегии масштабирования для приложений на ноде? Сравните их.
  36. Чем отличаются cpu-intensive, ram-intensive и io-intensive задачи? Приведите примеры.
  37. Почему не нужно использовать process.on('multipleResolves', handler)?
  38. Расскажите, на что влияет опция noDelay у серверов, метод setNoDelay у request и socket?
  39. Как работает keep-alive в http протоколе и как мы можем управлять им из ноды?
  40. Для чего используется модуль node:perf_hooks? И может ли он работать с воркерами?
  41. Что вы думаете про экспериментально API итерируемых методов (filter, map, reduce...) у стримов?
  42. Какие вы знаете способы интернационализации приложений?
  43. Используете ли вы встроенный test runner и библиотеку node:assert?
  44. Какие вы использовали ключи при запуске ноды?
  45. Как сдампить хип процесса и что с ним дальше делать?
  46. Как построить flame graph?
  47. Расскажите про ALPN и SNI и их поддержку в ноде.
  48. Как реализовать автоматическую перезагрузку процесса нативными средствами при изменении кода?
  49. Для чего нам модуль, который называется модуль, а именно node:module?
  50. Как работать с самоподписанными SSL сертификатами? И в чем ограничение их использования?
  51. Для чего в node.js есть Web Crypto API и в чем разница с node:crypto?
  52. Для чего в node.js есть Web Streams API и в чем разница с node:stream?
  53. Для чего нужны классы Blob и File из node:buffer?
  54. В чем разница моделей прав доступа module-based и process-based?
  55. Что и почему было deprecated в node:async_hooks?
  56. Для чего нужен класс AsyncResource и как им пользоваться?
  57. Как найти вызовы всех deprecated API в node.js приложении?
  58. Как работать с зависимостями в single executable?
  59. Знаете ли вы о проблемах с нативным test runner в node.js?
  60. Какие новые возможности JavaScript появились в node.js при обновлении до версий 18 и 20?

Ответы на все эти вопросы

Если вас интересуют ответы, то тут у меня самый большой бесплатный курс по ноде, который обновляется каждый год: https://github.com/HowProgrammingWorks/Index/blob/master/Courses/NodeJS.md а тут новый практически курс с ревью кода, созвонами по 2 раза в неделю, ответами на вопросы, лайвкодингом и большим архивом решений даля практических задач: https://github.com/HowProgrammingWorks/Index/blob/master/Courses/NodeJS-2022-2023.md И там и там есть все ответы.

Заметки по методике проведения собеседований

  • Я сторонник того, чтобы давать людям на собесах возможность листать доки, гуглить и даже спрашивать у нейронок. Что должен проверять собес? Эффективность, способность решать задачи, а не задротство, зубрежку и феноменальную память. Если вы начнете это делать, то внезапно для себя выявите, что даже при этом многие люди не справляются, полный интернет шедевров говнокода, оверинженеринга и архитектурного маразма. Еще мне важно, чтобы человек показал свое субъективное мнение, даже эмоциональную позицию по отношению к конкретным решениям и технологиям, именно это он будет проявлять в работе. А что сейчас на собесах происходит: эффект ивентлупа — люди вызубрили и могут наизусть рассказать фазы и красиво объяснить, а применить для принятия решений в коде не смогут, т.е. оно ничего не дает им в каждодневной работе.
  • Эти вопросы можно задавать любому уровню (джун, мидл, синьор), потому, что эти уровни релевантны только внутри конкретной компании или даже уже - продуктовой команды, а так, каждый из уровней ответит на них с разной степенью глубины.
  • Прикладной и системный программисты - это две разные специальности, как водитель и автомеханик. Оба они знают что такое двигатель, сцепление, тормоза, рессоры, но работа заключается в разном, хоть автомеханик тоже может водить машину, а некоторые водители умеют их чинить.

Как проводить собеседования

  • Время собеседования ограничено, мы не можем позволить себе вести время по 2-3 часа на человека. Писать код на собеседованиях - это обычно долго и неэффективно (но иногда можно, если это всем комфортно и если код концептуальный, иллюстративный и не длинный).
  • Давайте список вопросов заранее, например за неделю или вообще публикуйте список специально для вакансии заранее, вот прям много вопросов, 100-200 и попросите промаркировать соискателя 2-4 символами, например: знаю / не знаю или хорошо знаю, справлюсь, слышал, не знаю. Потом на собеседовании вам останется выяснить, адекватно ли себя оценивает кандидат, проверив всего на нескольких вопросах. А в результате вы получите полную картину.

Заметки для кандидатов

  • Экономьте свое время и время интервьюера, не нужно травить байки и лить воду, говорить намеками. Запишите свой ответ на любой вопрос голосом и прослушайте. Если вы понимаете, что ваша речь невнятна, потренируйтесь, можете взять друга и поговорить с ним. Старайтесь высказываться не заученными выражениями, а поддерживать диалог двух профессионалов.

Links


About

NodeJS ✨🐢🚀✨ вопросы для собеседований

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published