JavaScript面接問題50選【2026年版】:コード例と解説付き完全ガイド

JavaScript面接問題50選【2026年版】:コード例と解説付き完全ガイド

2026年の最新JavaScript面接でよく聞かれる50の質問と詳細な回答。クロージャ、Promise、イベントループから最新のES2025機能まで、コード例付きで解説。

2026年3月17日10分で読了

はじめに:2026年のJavaScript面接とは

JavaScript面接は、フロントエンド・バックエンドを問わず、多くの開発者ポジションで避けて通れないステップです。2026年の面接では、基本的な文法知識だけでなく、非同期処理の深い理解、パフォーマンス最適化、最新ECMAScript仕様への対応が求められます。

本記事では、実際の面接でよく聞かれる50の質問を難易度別に紹介します。面接官の視点から「何を評価しているか」も含めて解説するので、答えを暗記するだけでなく、概念の本質を理解することを目指してください。

正規表現テスターを活用して、文中の正規表現パターンを実際に試しながら学習することをお勧めします。


基礎レベル(問1〜15)

問1:varletconstの違いを説明してください

回答:

項目varletconst
スコープ関数スコープブロックスコープブロックスコープ
巻き上げ(Hoisting)される(undefined)される(TDZ)される(TDZ)
再宣言可能不可能不可能
再代入可能可能不可能
// varの問題:関数スコープ
function example() {
  if (true) {
    var x = 10;
  }
  console.log(x); // 10(ブロック外からアクセス可能)
}

// letのブロックスコープ
function example2() {
  if (true) {
    let y = 10;
  }
  console.log(y); // ReferenceError: y is not defined
}

// constは再代入不可(ただしオブジェクトのプロパティは変更可能)
const obj = { name: "田中" };
obj.name = "鈴木"; // 可能
obj = {}; // TypeError: Assignment to constant variable

面接官が見ているポイント: TDZ(Temporal Dead Zone)についても言及できると高評価です。


問2:クロージャとは何ですか?実用的な例を挙げてください

回答:

クロージャとは、関数が自身が宣言されたスコープ(レキシカルスコープ)の変数を、そのスコープの外から呼ばれた後も保持し続ける仕組みです。

// カウンターファクトリー関数
function createCounter(initialValue = 0) {
  let count = initialValue; // クロージャで保持される変数

  return {
    increment() {
      count++;
      return count;
    },
    decrement() {
      count--;
      return count;
    },
    getCount() {
      return count;
    },
    reset() {
      count = initialValue;
    }
  };
}

const counter1 = createCounter(10);
const counter2 = createCounter(0);

counter1.increment(); // 11
counter1.increment(); // 12
counter2.increment(); // 1

// counter1とcounter2はそれぞれ独立したcountを保持
console.log(counter1.getCount()); // 12
console.log(counter2.getCount()); // 1

実用的な応用:プライベート変数の実現、部分適用(カリー化)、イベントハンドラーのファクトリー関数など。


問3:=====の違いを説明してください

回答:

==は型強制(Type Coercion)を行う比較演算子で、異なる型の値を比較する際に自動的に型変換を行います。===は厳密な等値比較で、型変換を行いません。

console.log(0 == false);   // true(型強制)
console.log(0 === false);  // false(型が異なる)
console.log("" == false);  // true(型強制)
console.log("" === false); // false

console.log(null == undefined);  // true(特別なケース)
console.log(null === undefined); // false

// 罠:NaNは自分自身と等しくない
console.log(NaN == NaN);  // false
console.log(NaN === NaN); // false
// NaNのチェックにはNumber.isNaN()を使う
console.log(Number.isNaN(NaN)); // true

ベストプラクティス: 常に===を使用し、意図的な型強制が必要な場合のみ==を使用する。


問4:JavaScriptのデータ型をすべて挙げてください

回答:

プリミティブ型(7種類):

  • stringnumberbigintbooleanundefinednullsymbol

参照型:

  • object(配列、関数、日付なども含む)
// typeofによる確認
typeof "hello"      // "string"
typeof 42           // "number"
typeof 42n          // "bigint"
typeof true         // "boolean"
typeof undefined    // "undefined"
typeof null         // "object"(JavaScriptの有名なバグ)
typeof Symbol()     // "symbol"
typeof {}           // "object"
typeof []           // "object"(配列もオブジェクト)
typeof function(){} // "function"

// 配列の正しいチェック方法
Array.isArray([]); // true

問5:イベントループを説明してください

回答:

JavaScriptはシングルスレッドですが、イベントループの仕組みにより非同期処理が可能です。

console.log("1. 同期コード開始");

setTimeout(() => {
  console.log("4. macrotask(setTimeout)");
}, 0);

Promise.resolve().then(() => {
  console.log("3. microtask(Promise)");
});

console.log("2. 同期コード終了");

// 出力順序:
// 1. 同期コード開始
// 2. 同期コード終了
// 3. microtask(Promise)
// 4. macrotask(setTimeout)

実行順序:

  1. コールスタックの同期コードを実行
  2. コールスタックが空になったらマイクロタスクキュー(Promise、queueMicrotask)を処理
  3. マイクロタスクキューが空になったらマクロタスクキュー(setTimeout、setInterval)から1つ取り出して実行
  4. 2に戻る

問6:プロトタイプ継承とは何ですか?

回答:

JavaScriptはプロトタイプベースの継承を採用しており、すべてのオブジェクトは__proto__(またはObject.getPrototypeOf())を通じて別のオブジェクト(プロトタイプ)を参照します。

// コンストラクター関数
function Animal(name) {
  this.name = name;
}

Animal.prototype.speak = function() {
  return `${this.name}が鳴いています`;
};

function Dog(name, breed) {
  Animal.call(this, name); // 親コンストラクターを呼び出す
  this.breed = breed;
}

// プロトタイプチェーンを設定
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.fetch = function() {
  return `${this.name}がボールを取ってきました!`;
};

const dog = new Dog("ポチ", "柴犬");
console.log(dog.speak());  // "ポチが鳴いています"
console.log(dog.fetch());  // "ポチがボールを取ってきました!"

// クラス構文(内部的には同じプロトタイプ継承)
class AnimalClass {
  constructor(name) {
    this.name = name;
  }
  speak() {
    return `${this.name}が鳴いています`;
  }
}

問7:thisキーワードの動作を説明してください

回答:

thisの値は関数がどのように呼ばれるかによって決まります。

// 1. メソッドとして呼ばれる場合
const obj = {
  name: "田中",
  greet() {
    console.log(this.name); // "田中"
  }
};
obj.greet();

// 2. 通常の関数として呼ばれる場合(非strict)
function regular() {
  console.log(this); // グローバルオブジェクト(window / global)
}

// 3. アロー関数:thisを持たず、外側のスコープのthisを使う
const obj2 = {
  name: "鈴木",
  greet: () => {
    console.log(this.name); // undefined(アロー関数はthisを持たない)
  },
  greetWithMethod() {
    const inner = () => {
      console.log(this.name); // "鈴木"(外側のメソッドのthis)
    };
    inner();
  }
};

// 4. bind、call、applyで明示的にthisを指定
function greet() {
  return `こんにちは、${this.name}さん`;
}
const boundGreet = greet.bind({ name: "佐藤" });
console.log(boundGreet()); // "こんにちは、佐藤さん"

問8:スプレッド演算子とレスト演算子の違いを説明してください

回答:

両方とも...構文を使いますが、使用する文脈が異なります。

// スプレッド演算子:配列やオブジェクトを展開する
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]

const original = { name: "田中", age: 30 };
const updated = { ...original, age: 31, email: "tanaka@example.com" };

// レスト演算子:残りの要素をまとめる
function sum(...numbers) { // 関数のパラメーターでの使用
  return numbers.reduce((acc, num) => acc + num, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15

// 分割代入でのレスト
const [first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(rest);  // [3, 4, 5]

const { name, ...others } = { name: "田中", age: 30, city: "東京" };
console.log(others); // { age: 30, city: "東京" }

問9:デストラクチャリングとは何ですか?

// 配列のデストラクチャリング
const [x, y, z = 0] = [1, 2]; // デフォルト値付き
console.log(x, y, z); // 1 2 0

// オブジェクトのデストラクチャリング
const { name: userName, age = 25, address: { city } = {} } = {
  name: "田中",
  address: { city: "東京" }
};
console.log(userName); // "田中"
console.log(age);     // 25(デフォルト値)
console.log(city);    // "東京"(ネストされたデストラクチャリング)

// 関数パラメーターでのデストラクチャリング
function displayUser({ name, age, role = "ユーザー" }) {
  console.log(`${name}${age}歳)- ${role}`);
}
displayUser({ name: "鈴木", age: 28 });

問10〜15:追加の基礎問題

問10:nullundefinedの違い

  • undefined:変数が宣言されたが値が代入されていない状態
  • null:意図的に「値がない」ことを示す値

問11:配列メソッドの違い(mapfilterreduce

const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);        // [2, 4, 6, 8, 10]
const evens = numbers.filter(n => n % 2 === 0); // [2, 4]
const sum = numbers.reduce((acc, n) => acc + n, 0); // 15

問12:Array.from()の使い方

Array.from("hello");         // ['h', 'e', 'l', 'l', 'o']
Array.from({ length: 5 }, (_, i) => i + 1); // [1, 2, 3, 4, 5]
Array.from(new Set([1, 2, 2, 3])); // [1, 2, 3](重複排除)

問13:オプショナルチェーニング(?.

const user = null;
console.log(user?.name);           // undefined(エラーにならない)
console.log(user?.address?.city);  // undefined
console.log(user?.getAge?.());     // undefined

問14:Nullish合体演算子(??

const value = null ?? "デフォルト値"; // "デフォルト値"
const count = 0 ?? 10; // 0(nullとundefinedのみデフォルトを返す)
const count2 = 0 || 10; // 10(||はfalsyなすべてにデフォルトを返す)

問15:typeofinstanceofの違い

typeof "hello";      // "string"(プリミティブ型の確認)
[] instanceof Array; // true(参照型の確認)

中級レベル(問16〜35)

問16:Promiseを使った非同期処理を説明してください

// Promiseの基本
const fetchUser = (id) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (id > 0) {
        resolve({ id, name: "田中太郎" });
      } else {
        reject(new Error("無効なIDです"));
      }
    }, 1000);
  });
};

// Promise チェーン
fetchUser(1)
  .then(user => {
    console.log(user.name);
    return fetchUser(2); // 次のPromiseを返す
  })
  .then(user2 => console.log(user2.name))
  .catch(error => console.error(error.message))
  .finally(() => console.log("処理完了"));

// Promise.all:すべてが完了するまで待つ
Promise.all([fetchUser(1), fetchUser(2), fetchUser(3)])
  .then(users => console.log(users));

// Promise.allSettled:すべての結果を取得(失敗も含む)
Promise.allSettled([fetchUser(1), fetchUser(-1)])
  .then(results => {
    results.forEach(result => {
      if (result.status === "fulfilled") {
        console.log("成功:", result.value);
      } else {
        console.log("失敗:", result.reason.message);
      }
    });
  });

問17:async/awaitの仕組みと注意点

// async/awaitの基本
async function loadUserData(userId) {
  try {
    const user = await fetchUser(userId);
    const posts = await fetchPosts(user.id); // 順次実行
    return { user, posts };
  } catch (error) {
    console.error("データ取得エラー:", error);
    throw error;
  }
}

// 並列実行(パフォーマンス最適化)
async function loadUserDataParallel(userId) {
  const [user, settings] = await Promise.all([
    fetchUser(userId),
    fetchUserSettings(userId)
  ]);
  return { user, settings };
}

// よくある間違い:ループ内での await
// 悪い例(順次実行になる)
async function badExample(ids) {
  const results = [];
  for (const id of ids) {
    results.push(await fetchUser(id)); // 一つずつ待つ
  }
  return results;
}

// 良い例(並列実行)
async function goodExample(ids) {
  return Promise.all(ids.map(id => fetchUser(id)));
}

問18:メモ化(Memoization)を実装してください

function memoize(fn) {
  const cache = new Map();

  return function(...args) {
    const key = JSON.stringify(args);

    if (cache.has(key)) {
      console.log("キャッシュから返します");
      return cache.get(key);
    }

    const result = fn.apply(this, args);
    cache.set(key, result);
    return result;
  };
}

// 使用例:フィボナッチ数列
const fibonacci = memoize(function(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
});

console.log(fibonacci(40)); // 高速に計算

問19:カリー化(Currying)とは何ですか?

// カリー化の実装
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    }
    return function(...args2) {
      return curried.apply(this, args.concat(args2));
    };
  };
}

// 使用例
const add = curry((a, b, c) => a + b + c);

add(1)(2)(3);    // 6
add(1, 2)(3);    // 6
add(1)(2, 3);    // 6
add(1, 2, 3);    // 6

// 実用的な例:バリデーション関数の部分適用
const isGreaterThan = curry((min, value) => value > min);
const isAdult = isGreaterThan(18);
const isElderly = isGreaterThan(65);

console.log(isAdult(20));  // true
console.log(isAdult(15));  // false

問20:WeakMapとMapの違いを説明してください

// Map:任意の値をキーとして使用可能
const map = new Map();
map.set("key", "value");
map.set(42, "数値キー");
map.set({}, "オブジェクトキー");

// WeakMap:キーはオブジェクトのみ、弱参照
const weakMap = new WeakMap();
let user = { name: "田中" };
weakMap.set(user, { sessionData: "..." });

user = null; // userが参照されなくなるとGCで自動的にメモリ解放
// WeakMapのサイズは確認できない(.sizeがない)

// WeakMapの実用例:DOMノードへのプライベートデータ付与
const privateData = new WeakMap();

class SecureUser {
  constructor(name, password) {
    privateData.set(this, { password });
    this.name = name;
  }

  authenticate(input) {
    return privateData.get(this).password === input;
  }
}

問21〜35:中級問題サマリー

問21:ジェネレーター関数の説明と実装

function* range(start, end, step = 1) {
  for (let i = start; i < end; i += step) {
    yield i;
  }
}
const gen = range(0, 10, 2);
console.log([...gen]); // [0, 2, 4, 6, 8]

問22:Proxy と Reflectの使い方

const handler = {
  get(target, prop) {
    return prop in target ? target[prop] : `${prop}は存在しません`;
  },
  set(target, prop, value) {
    if (typeof value !== "number") throw new TypeError("数値のみ許可");
    target[prop] = value;
    return true;
  }
};
const proxy = new Proxy({}, handler);

問23:Symbol の用途

  • オブジェクトのプライベートプロパティキー
  • イテラブルの実装(Symbol.iterator
  • Well-known Symbols(Symbol.hasInstanceなど)

問24:イミュータブルなデータ操作パターン

// オブジェクトの不変更新
const updateUser = (user, updates) => ({ ...user, ...updates });
const addItem = (list, item) => [...list, item];
const removeItem = (list, index) => list.filter((_, i) => i !== index);

問25:デバウンスとスロットリングの実装

function debounce(fn, delay) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}

function throttle(fn, limit) {
  let lastCall = 0;
  return (...args) => {
    const now = Date.now();
    if (now - lastCall >= limit) {
      lastCall = now;
      return fn(...args);
    }
  };
}

問26:エラー処理のベストプラクティス(カスタムエラークラス)

問27:モジュールシステム(ESM vs CommonJS)

問28:イテレーターとイテラブルの実装

問29:Tagged Template Literals

問30:Object.definePropertyによるゲッター・セッター

問31:structuredCloneによるディープコピー(ES2022)

問32:Array groupBy(ES2024)

問33:Promise.any と Promise.race の違い

問34:SharedArrayBufferとAtomics

問35:AbortControllerによるフェッチのキャンセル

const controller = new AbortController();
const { signal } = controller;

fetch("/api/data", { signal })
  .catch(err => {
    if (err.name === "AbortError") console.log("リクエストがキャンセルされました");
  });

setTimeout(() => controller.abort(), 5000); // 5秒後にキャンセル

上級レベル(問36〜50)

問36:JavaScriptのメモリ管理とガベージコレクション

// メモリリークの典型的なパターン

// 1. 削除されないイベントリスナー
class Component {
  constructor() {
    this.handleClick = this.handleClick.bind(this);
    document.addEventListener("click", this.handleClick);
  }
  handleClick() { /* ... */ }

  // クリーンアップを忘れずに
  destroy() {
    document.removeEventListener("click", this.handleClick);
  }
}

// 2. クロージャによるメモリリーク
function createLeak() {
  const largeData = new Array(1000000).fill("data");

  return function() {
    // largeDataへの参照が保持されつづける
    return largeData.length;
  };
}

// WeakRefを使った解決策(ES2021)
let user = { name: "田中", data: new Array(10000) };
const ref = new WeakRef(user);

// 後でuserが不要になった場合
user = null;

// GC後にrefは無効になる可能性がある
const derefUser = ref.deref();
if (derefUser) {
  console.log(derefUser.name);
}

問37:Virtual DOMの仕組みと実装

// シンプルなVirtual DOM実装
function createElement(type, props, ...children) {
  return { type, props: props || {}, children: children.flat() };
}

function createRealDOM(vNode) {
  if (typeof vNode === "string" || typeof vNode === "number") {
    return document.createTextNode(String(vNode));
  }

  const element = document.createElement(vNode.type);

  // プロパティの設定
  Object.entries(vNode.props).forEach(([key, value]) => {
    if (key.startsWith("on")) {
      element.addEventListener(key.slice(2).toLowerCase(), value);
    } else {
      element.setAttribute(key, value);
    }
  });

  // 子要素の追加
  vNode.children.forEach(child => {
    element.appendChild(createRealDOM(child));
  });

  return element;
}

// 使用例
const vdom = createElement("div", { class: "container" },
  createElement("h1", null, "タイトル"),
  createElement("p", null, "本文テキスト")
);

問38:Web Workersによる並列処理

// メインスレッド
const worker = new Worker("worker.js");

worker.postMessage({ type: "COMPUTE", data: [1, 2, 3, 4, 5] });

worker.onmessage = (event) => {
  console.log("計算結果:", event.data.result);
};

worker.onerror = (error) => {
  console.error("Workerエラー:", error.message);
};

// worker.js(別ファイル)
self.onmessage = (event) => {
  if (event.data.type === "COMPUTE") {
    const result = event.data.data.reduce((sum, n) => sum + n, 0);
    self.postMessage({ result });
  }
};

問39:Service Workerとオフライン対応

// Service Workerの登録
if ("serviceWorker" in navigator) {
  navigator.serviceWorker.register("/sw.js")
    .then(reg => console.log("SW登録完了:", reg.scope))
    .catch(err => console.error("SW登録失敗:", err));
}

// sw.js
const CACHE_NAME = "app-cache-v1";
const ASSETS = ["/", "/index.html", "/styles.css", "/app.js"];

self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then(cache => cache.addAll(ASSETS))
  );
});

self.addEventListener("fetch", (event) => {
  event.respondWith(
    caches.match(event.request).then(response => {
      return response || fetch(event.request);
    })
  );
});

問40〜50:上級問題サマリー

問40:コンパイラーパターンとAST操作

問41:リアクティブプログラミング(RxJS Observer パターン)

class Observable {
  constructor(subscriber) {
    this._subscriber = subscriber;
  }
  subscribe(observer) {
    return this._subscriber(observer);
  }
  map(fn) {
    return new Observable(observer => {
      return this.subscribe({
        next: value => observer.next(fn(value)),
        error: err => observer.error(err),
        complete: () => observer.complete()
      });
    });
  }
}

問42:型システムとTypeScriptの高度な型(ConditionType、Mapped Types)

問43:正規表現の高度な使用

// 名前付きキャプチャグループ(ES2018)
const datePattern = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const { groups: { year, month, day } } = "2026-03-17".match(datePattern);

問44:JavaScriptエンジンの最適化(V8)(Hidden Classes、Inline Cache)

問45:セキュリティとXSS対策

// DOMを安全に更新する
function sanitizeHTML(str) {
  const div = document.createElement("div");
  div.textContent = str; // textContentはXSS安全
  return div.innerHTML;
}

問46:パフォーマンスAPI(Performance Observer)

問47:WebAssemblyとの連携

問48:Stream APIとパイプライン処理

const readable = new ReadableStream({ /* ... */ });
const writable = new WritableStream({ /* ... */ });
await readable.pipeTo(writable);

問49:Temporal API(ES2025)によるタイムゾーン対応

// Temporal APIは日時処理の新標準
const now = Temporal.Now.plainDateTimeISO();
const tokyo = now.toZonedDateTime("Asia/Tokyo");

問50:JavaScriptのセキュリティベストプラクティスとCSP


面接対策のまとめ

JavaScript面接で合格するためには、概念の暗記だけでなく、実際に手を動かしてコードを書く練習が不可欠です。

おすすめの準備方法:

  1. 毎日LeetCodeやAtCoderの問題を1〜3問解く
  2. 読んだコードを実際にタイピングして動かす
  3. 面接で「なぜそのアプローチを選んだか」を説明できるよう準備する
  4. 実際のプロジェクトで学んだことを具体的に話せるよう整理する

コードのデバッグには正規表現テスターなどのツールを積極的に活用し、実務で役立つスキルを身につけましょう。

2026年のJavaScript面接では、ES2024〜2025の新機能(Temporal、Array groupBy、Record and Tupleなど)への理解も問われるようになっています。常に最新の仕様をキャッチアップし、自信を持って面接に臨んでください。

関連記事