Node.jsバックエンド面接対策

Node.jsの面接問題と解答 2026

バックエンド開発者向けのNode.js面接問題を厳選したセットです。回答は口頭練習しやすい構成にしてあり、アーキテクチャを説明し、具体例を挙げ、運用上のトレードオフに触れられるようになっています。

Node.jsの模擬面接を開始クレジットカードは不要です。1回分の無料セッションがあります。
英語での技術面接練習非ネイティブ話者が技術面接の合格を目指して練習できるモードです。

アーキテクチャとイベントループ

1Node.jsとは何ですか?また、どのような用途で使用されますか?

Node.jsは、V8 JavaScriptエンジンを基盤とし、ネットワーク、ファイルシステム、プロセス、ストリーム、OSへのアクセス機能などのAPIによって拡張された、ブラウザ外で動作するJavaScript実行環境(ランタイム)です。主にREST API、GraphQLサービス、リアルタイムWebSocketサーバー、マイクロサービス、CLIツール、プロキシ、APIゲートウェイなどに広く利用されています。最大の強みはノンブロッキングI/Oであり、1つのプロセスで大量のネットワーク接続を効率的に処理できます。一方で、CPU負荷の高い長時間の演算を行うと、ワーカースレッドや別プロセス、あるいは別サービスに切り離さない限りイベントループをブロックしてしまうというトレードオフがあります。

AI コーチを使ってこの質問に答えてみる

2Node.js の内部動作の仕組みはどのようになっていますか?

JavaScriptはV8上で実行され、Node.jsのAPIは非同期処理をオペレーティングシステムまたはlibuvのスレッドプールに委任します。メインのJavaScriptスレッドはI/O操作の完了を待たず、他のコードの実行を継続します。処理が完了すると、そのコールバックやPromiseの継続処理がイベントループで実行可能な状態になります。このようにして、ユーザーのJavaScriptが通常1つのメインスレッド上で動作していても、Node.jsは高い並行性を実現しています。

AI コーチを使ってこの質問に答えてみる

3V8、libuv、およびイベントループとは何ですか?

V8はJavaScriptをコンパイルおよび実行し、JavaScriptオブジェクトとメモリを管理します。libuvはクロスプラットフォームなイベントループ、非同期I/Oの統合、および一部のファイルシステム操作、DNS、暗号処理などのためのワーカースレッドプールを提供します。イベントループは、完了した処理のコールバックを実行するタイミングを決定します。Node.js自体はJavaScript APIをネイティブ機能と結び付ける役割を果たしており、V8単体ではHTTPサーバー、ファイルシステムアクセス、プロセスAPIなどは提供されません。

AI コーチを使ってこの質問に答えてみる

4Node.jsがシングルスレッドと呼ばれることが多いのはなぜですか?

単一のNode.jsプロセスは、通常1つのメインスレッド上でユーザーのJavaScriptを実行するため、2つの通常のリクエストハンドラがそのスレッド内でまったく同時にJavaScriptを実行することはありません。しかし、プラットフォーム全体が文字通り1つのスレッドだけで構成されているわけではありません。libuvにはスレッドプールが存在し、OS (Operating System)が非同期I/Oを実行し、V8がガベージコレクションを行い、さらにNode.jsはworker_threadsや子プロセスもサポートしています。そのため、この表現は主にデフォルトのJavaScript実行モデルを指しています。

AI コーチを使ってこの質問に答えてみる

5Node.jsはどのようにして多数のリクエストを並行して処理できるのですか?

Node.jsはリクエストごとにスレッドをブロックするのではなく、I/O処理を開始した後にイベントループへと制御を戻します。たとえば、メインスレッドが新しい接続の受け付けや処理可能なコールバックの実行を継続している間に、1,000件のデータベース呼び出しを並行して処理(in-flight)できます。これはHTTP呼び出し、データベースクエリ、ファイル操作、ソケット通信などのI/Oバウンドなワークロードに適しています。一方で、リクエストハンドラで重い同期的なCPU処理を行うと、イベントループがブロックされて他のすべてのリクエストが遅延するため、パフォーマンスが大きく低下します。

AI コーチを使ってこの質問に答えてみる

6Node.js のイベントループの主なフェーズにはどのようなものがありますか?

libuv のイベントループには、timers、pending callbacks、idle/prepare、poll、check、close callbacks などのフェーズがあります。timers は setTimeout や setInterval のコールバックを実行し、poll は多くの I/O(Input/Output)イベントを処理し、check は setImmediate のコールバックを実行し、close はソケットの切断などのクローズイベントを処理します。また、Node.js には process.nextTick や Promise のマイクロタスクを含むマイクロタスクキューがあり、各コールバックの合間に実行されます。マイクロタスクが多すぎると I/O が飢餓状態(スタベーション)に陥り、サービスが停止したように見える原因になります。

AI コーチを使ってこの質問に答えてみる

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'
);
AI コーチを使ってこの質問に答えてみる

8イベントループのスタベーション(event loop starvation)とは何ですか?

イベントループのスタベーションは、JavaScriptの処理やマイクロタスクがイベントループを占有し続け、I/O(Input/Output)、タイマー、新しいリクエストを迅速に処理できなくなることで発生します。主な原因としては、重い同期計算、巨大なJSON処理、際限のないPromiseチェーン、過剰な`process.nextTick`の使用などが挙げられます。対策としては、処理を細かく分割(チャンク化)する、入力サイズを制限する、CPUプロファイリングを実施する、CPU集約型の処理を`worker_threads`や別サービスへ切り出す、性能上重要なリクエストパスで同期APIを避けることなどが有効です。

app.get('/report', (req, res) => {
  const result = performVeryHeavyCalculation();
  res.json(result);
});
AI コーチを使ってこの質問に答えてみる

非同期プログラミングとエラー

9コールバックとは何ですか?また、コールバック地獄(callback hell)とは何ですか?

コールバックとは、後で(多くは非同期処理の完了後に)実行されるよう引数として渡される関数のことです。従来のNode.jsのコールバックは、一般に「エラー優先(error-first)」スタイル(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 コーチを使ってこの質問に答えてみる

10Promise とは何ですか?また、async/await はどのように動作しますか?

Promise は非同期処理の将来的な結果を表すオブジェクトであり、pending(保留中)、fulfilled(成功)、rejected(拒否)のいずれかの状態を取ります。async 関数は常に Promise を返します。await は対象の Promise が確定(settle)するまで現在の async 関数の実行のみを一時停止し、Node.js プロセス全体をブロックすることはありません。優れた回答では、try/catch によるエラーハンドリング、未処理のまま放置された Promise(floating 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 コーチを使ってこの質問に答えてみる

11process.nextTick、queueMicrotask、setImmediate、setTimeout にはどのような違いがありますか?

process.nextTick は現在の操作の直後、かつイベントループが次のフェーズに進む前に実行されます。queueMicrotask は Promise の後続処理と同様に、標準の JavaScript マイクロタスクをスケジュールします。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'));
AI コーチを使ってこの質問に答えてみる

12Node.jsではどのようにエラーを処理すべきですか?

同期処理のコードには `try/catch` を使用します。Promiseベースのコードでは `try/catch` 内で `await` を使用するか、`.catch` をチェーンします。エラーファーストコールバックでは、結果を利用する前にエラー引数をチェックする必要があります。サーバーアプリケーションでは、中央集約的なエラーミドルウェア、リクエストコンテキストを含むロギング、クライアントへの安全なエラーメッセージ返却、およびグレースフルシャットダウンが重要です。`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 コーチを使ってこの質問に答えてみる

モジュールとプロジェクト管理

13Node.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';
AI コーチを使ってこの質問に答えてみる

14CommonJSのrequireとES Modules (ECMAScript Modules)のimportにはどのような違いがありますか?

CommonJSではrequireとmodule.exportsを使用するのに対し、ES Modulesではimportとexportを使用します。ES Modulesは静的解析やトップレベルawaitをサポートしており、package.jsonのtype: moduleの指定や.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 };
AI コーチを使ってこの質問に答えてみる

15npm(Node Package Manager)、package.json、package-lock.jsonとは何ですか?

`npm` はパッケージマネージャおよびCLI(コマンドラインインターフェイス)です。`package.json` はスクリプト、メタデータ、`dependencies`、および `devDependencies` を記述します。`package-lock.json` はインストールの再現性を保証するために、依存関係ツリーの正確なバージョン構成を記録します。CI(継続的インテグレーション)環境では、ロックファイルに基づいて厳密にインストールを行い、`package.json` とロックファイルに不整合がある場合はエラー終了する `npm ci` の使用が一般的に推奨されます。これにより、意図しない依存関係の乖離(ドリフト)からビルドを保護できます。

{
  "name": "api",
  "type": "module",
  "scripts": {
    "start": "node src/server.js",
    "test": "node --test"
  },
  "dependencies": {},
  "devDependencies": {}
}
AI コーチを使ってこの質問に答えてみる

16設定値や環境変数はどのように管理すべきですか?

Node.jsはprocess.envを通じて環境変数を読み込みますが、取得される値はすべて文字列型であるため、起動時にバリデーションと型変換を行う必要があります。シークレット情報をリポジトリに保存してはならず、.envファイルはGitの管理対象から除外(ignore)し、本番環境のシークレット情報はプラットフォームやシークレット管理ツールから提供すべきです。優れたサービス設計では、必要な設定値が不足している場合にフェイルファスト(即座に起動失敗)させます。また、NODE_ENVは単なる慣例でありセキュリティ境界ではないため、保護機構としてこれに依存したコードを書くべきではありません。

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

if (!databaseUrl) {
  throw new Error('DATABASE_URL is required');
}
AI コーチを使ってこの質問に答えてみる

イベント、バッファ、ストリーム

17EventEmitterとは何ですか?

EventEmitterは、Node.jsプロセス内でパブリッシュ・サブスクライブパターンを実装する仕組みです。onはリスナーを登録し、onceは1回限りのリスナーを登録し、offはリスナーを削除し、emitはリスナーを登録順に同期的に呼び出します。この同期的性質は重要であり、低速なリスナーがあるとemitの処理全体がブロックされます。errorイベントは特別であり、ハンドリングされていない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 });
AI コーチを使ってこの質問に答えてみる

18Bufferは何のために使用されますか?

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);
AI コーチを使ってこの質問に答えてみる

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);
AI コーチを使ってこの質問に答えてみる

20バックプレッシャーとは何ですか?また、なぜpipelineが役立つのですか?

バックプレッシャーとは、データの送信元(ソース)が宛先(デスティネーション)の消費速度を上回るペースでデータを生成するときに発生する現象です。バックプレッシャーを制御しないと、プロセスが低速化したりクラッシュしたりするまでメモリが増大する可能性があります。Streamとpipeでもデータフローを協調させることは可能ですが、node:stream/promisesのpipelineはエラーを適切に伝播させ、関連するストリームを閉じ、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')
);
AI コーチを使ってこの質問に答えてみる