2026년 자바스크립트 면접 질문 50선 & 완벽 답변

2026년 자바스크립트 면접 질문 50선 & 완벽 답변

자바스크립트 기술 면접을 완벽하게 준비하세요. ES2026, async/await, 클로저, React, Node.js, 시스템 설계를 다루는 50개의 핵심 질문과 상세한 답변.

2026년 3월 17일10분 소요

2026년 자바스크립트 면접 완벽 준비

자바스크립트는 여전히 가장 수요가 높은 프로그래밍 언어 중 하나이며, 기술 면접은 그 어느 때보다 엄격해졌습니다. 이 가이드는 2026년 자바스크립트 면접에서 가장 자주 묻는 50개의 질문과 현재 모범 사례를 반영한 상세한 답변을 담고 있습니다.


핵심 자바스크립트 기초

1. var, let, const의 차이점은?

var 는 함수 스코프(또는 함수 외부에서 선언된 경우 전역 스코프)를 가지며 undefined 초기값으로 스코프 상단에 호이스팅됩니다.

let 은 블록 스코프를 가지며 호이스팅되지만 초기화되지 않습니다(일시적 사각지대). 재할당 가능합니다.

const 는 블록 스코프를 가지며 호이스팅되지만 초기화되지 않고, 재할당할 수 없습니다. 주의: const는 객체를 불변으로 만들지 않습니다 — 객체 속성은 여전히 변경할 수 있습니다.

var x = 1;
let y = 2;
const z = 3;

function example() {
  console.log(a); // undefined (호이스팅)
  var a = 5;
  // console.log(b); // ReferenceError: 초기화 전에 'b'에 접근 불가
  let b = 6;
}

const obj = { value: 1 };
obj.value = 2; // 허용됨
// obj = {}; // 오류 발생

원칙: 기본적으로 const 사용, 재할당이 필요할 때 let, 현대 코드에서는 절대 var 사용 금지.

2. 클로저를 실용적인 예시로 설명하세요.

클로저는 외부 함수가 반환된 후에도 외부 스코프의 변수에 접근을 유지하는 함수입니다.

function createCounter(initialValue = 0) {
  let count = initialValue;

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

const counter = createCounter(10);
console.log(counter.increment()); // 11
console.log(counter.increment()); // 12
console.log(counter.decrement()); // 11
// count 변수는 외부에서 접근 불가

클로저는 주로 다음 용도로 사용됩니다:

  • 데이터 프라이버시와 캡슐화
  • 함수 팩토리
  • 메모이제이션
  • 콜백에서 상태 유지

3. 이벤트 위임이란 무엇이고 왜 사용하나요?

이벤트 위임은 여러 자식 요소에 여러 리스너를 붙이는 대신 하나의 이벤트 리스너를 부모 요소에 붙이는 것입니다.

// 위임 없이 (동적 목록에 부적합)
document.querySelectorAll('.item').forEach(item => {
  item.addEventListener('click', handleClick);
});

// 위임 사용 (현재 및 미래 요소에 작동)
document.querySelector('.list').addEventListener('click', (event) => {
  if (event.target.classList.contains('item')) {
    handleClick(event.target);
  }
});

장점: 많은 요소에 더 나은 성능, 동적으로 추가된 요소에 작동, 더 간단한 메모리 관리.

4. 자바스크립트 이벤트 루프를 설명하세요.

자바스크립트는 단일 스레드이지만 이벤트 루프를 통해 비동기 작업을 처리합니다.

구성 요소:

  • 콜 스택: 동기 코드가 실행되는 곳
  • 웹 API: 스택 외부에서 실행되는 브라우저 기능 (setTimeout, fetch, DOM 이벤트)
  • 콜백 큐(태스크 큐): 실행을 기다리는 웹 API의 콜백
  • 마이크로태스크 큐: Promise와 MutationObserver 콜백 (콜백 큐보다 우선순위 높음)
  • 이벤트 루프: 콜 스택이 비어있는지 지속적으로 확인하고 큐에서 태스크를 이동
console.log('1 - 시작');

setTimeout(() => console.log('4 - setTimeout'), 0);

Promise.resolve()
  .then(() => console.log('3 - Promise 마이크로태스크'));

console.log('2 - 끝');

// 출력: 1, 2, 3, 4
// 마이크로태스크(Promise)는 항상 매크로태스크(setTimeout) 전에 실행

5. =====의 차이점은?

==는 비교 전에 타입 강제 변환을 수행합니다. ===는 강제 변환 없이 값과 타입을 모두 비교합니다.

0 == false    // true  (false가 0으로 강제 변환)
0 === false   // false (타입이 다름)
'' == false   // true  (둘 다 0으로 강제 변환)
null == undefined  // true (특별한 경우)
null === undefined // false

// 타입 강제 변환이 특별히 필요하지 않으면 항상 === 사용

비동기 자바스크립트

6. Promise와 세 가지 상태를 설명하세요.

Promise는 비동기 작업의 최종 결과를 나타냅니다. 세 가지 상태:

  • 대기(Pending): 초기 상태, 이행도 거부도 아님
  • 이행(Fulfilled): 작업이 성공적으로 완료, 값이 있음
  • 거부(Rejected): 작업이 실패, 이유(오류)가 있음
const fetchUser = (id) => new Promise((resolve, reject) => {
  if (id <= 0) {
    reject(new Error('유효하지 않은 ID'));
    return;
  }
  setTimeout(() => resolve({ id, name: '김철수' }), 100);
});

fetchUser(1)
  .then(user => console.log(user))
  .catch(error => console.error(error))
  .finally(() => console.log('완료'));

7. async/await란 무엇이고 Promise와 어떤 관계인가요?

async/await는 Promise 위의 문법적 설탕으로, 비동기 코드가 동기 코드처럼 보이고 동작하게 합니다.

// Promise 체인
function getUserData(userId) {
  return fetchUser(userId)
    .then(user => fetchUserPosts(user.id))
    .then(posts => ({ user, posts }))
    .catch(error => { throw new Error(`실패: ${error.message}`); });
}

// async/await 동등 표현
async function getUserData(userId) {
  try {
    const user = await fetchUser(userId);
    const posts = await fetchUserPosts(user.id);
    return { user, posts };
  } catch (error) {
    throw new Error(`실패: ${error.message}`);
  }
}

8. 여러 Promise를 동시에 처리하는 방법은?

// 순차적 (느림 - 각각을 기다림)
const user = await fetchUser(id);
const profile = await fetchProfile(id); // user를 먼저 기다림

// 동시 (빠름 - 병렬로 실행)
const [user, profile] = await Promise.all([
  fetchUser(id),
  fetchProfile(id)
]);

// Promise.allSettled - 모두 기다리고 실패 포함
const results = await Promise.allSettled([
  fetchUser(id),
  fetchProfile(id),
  fetchSettings(id)
]);
results.forEach(result => {
  if (result.status === 'fulfilled') console.log(result.value);
  else console.error(result.reason);
});

객체지향 자바스크립트

9. 프로토타입 상속을 설명하세요.

모든 자바스크립트 객체는 프로토타입이라는 다른 객체에 대한 내부 링크를 가지고 있습니다. 속성에 접근할 때 자바스크립트는 먼저 해당 객체를 보고, 그 다음 프로토타입을 보고, 그 다음 프로토타입의 프로토타입을 보는 식으로 프로토타입 체인을 형성합니다.

const animal = {
  speak() {
    return `${this.name}이(가) 소리를 냅니다`;
  }
};

const dog = Object.create(animal);
dog.name = '멍멍이';
dog.bark = function() { return `${this.name}이(가) 짖습니다`; };

console.log(dog.speak()); // "멍멍이이(가) 소리를 냅니다" (프로토타입에서)
console.log(dog.bark());  // "멍멍이이(가) 짖습니다" (자체 속성)

10. ES6 클래스와 프로토타입 기반 패턴의 차이점은?

ES6 클래스는 프로토타입 상속 위의 문법적 설탕입니다. 내부적으로 여전히 프로토타입을 사용합니다.

class Animal {
  #name; // 비공개 필드 (ES2022)

  constructor(name) {
    this.#name = name;
  }

  get name() { return this.#name; }

  speak() {
    return `${this.#name}이(가) 소리를 냅니다`;
  }

  static create(name) {
    return new Animal(name);
  }
}

class Dog extends Animal {
  #breed;

  constructor(name, breed) {
    super(name);
    this.#breed = breed;
  }

  speak() {
    return `${super.speak()} - 멍멍!`;
  }
}

함수형 프로그래밍

11. map, filter, reduce를 설명하세요.

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// map: 각 요소 변환
const doubled = numbers.map(n => n * 2);
// [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

// filter: 조건에 맞는 요소 선택
const evens = numbers.filter(n => n % 2 === 0);
// [2, 4, 6, 8, 10]

// reduce: 단일 값으로 누적
const sum = numbers.reduce((acc, n) => acc + n, 0);
// 55

// 조합: 짝수의 제곱의 합
const result = numbers
  .filter(n => n % 2 === 0)
  .map(n => n ** 2)
  .reduce((acc, n) => acc + n, 0);
// 220

12. 커링이란 무엇인가요?

커링은 여러 인수를 가진 함수를 각각 단일 인수를 취하는 함수들의 시퀀스로 변환합니다.

// 일반 함수
const add = (a, b, c) => a + b + c;
console.log(add(1, 2, 3)); // 6

// 커링된 버전
const curriedAdd = a => b => c => a + b + c;
console.log(curriedAdd(1)(2)(3)); // 6

// 실용적 사용: 특화된 함수 생성
const multiply = a => b => a * b;
const double = multiply(2);
const triple = multiply(3);

console.log(double(5)); // 10
console.log(triple(5)); // 15

모던 자바스크립트 (ES2020-2026)

13. 옵셔널 체이닝(?.)이란?

옵셔널 체이닝을 사용하면 각 레벨을 null 또는 undefined로 명시적으로 확인하지 않고 깊이 중첩된 속성에 접근할 수 있습니다.

const user = {
  profile: {
    address: {
      city: '서울'
    }
  }
};

// 이전 방식
const city = user && user.profile && user.profile.address && user.profile.address.city;

// 옵셔널 체이닝
const city = user?.profile?.address?.city; // '서울'
const zip = user?.profile?.address?.zip;   // undefined (오류 없음)

14. null 병합 연산자(??)란?

??는 왼쪽 값이 null 또는 undefined인 경우에만 오른쪽 값을 반환합니다(다른 falsy 값은 해당 없음).

const config = {
  timeout: 0,     // 유효한 값
  retries: null,  // 명시적 null
  name: '',       // 빈 문자열, 유효
};

// || 는 0과 ''를 falsy로 처리 (잘못된 결과!)
console.log(config.timeout || 5000); // 5000 (잘못됨!)

// ?? 는 null/undefined에 대해서만 기본값 설정
console.log(config.timeout ?? 5000);  // 0 (올바름!)
console.log(config.name ?? '기본값'); // '' (올바름!)
console.log(config.retries ?? 3);     // 3 (올바름!)

15. structuredClone이란?

structuredCloneJSON.parse(JSON.stringify())가 처리할 수 없는 순환 참조와 복잡한 타입을 처리하며 객체의 깊은 복제를 만듭니다.

const original = {
  date: new Date(),
  map: new Map([['key', 'value']]),
  set: new Set([1, 2, 3]),
  array: [1, [2, 3]],
};

// JSON 방식은 이러한 타입에 실패
const badClone = JSON.parse(JSON.stringify(original));
// date가 문자열이 되고, Map/Set이 {}가 됨

// structuredClone이 올바르게 처리
const goodClone = structuredClone(original);
goodClone.array[1].push(4);
console.log(original.array[1]); // [2, 3] — 영향 없음

React 관련 질문

16. useEffect의 의존성 배열 차이를 설명하세요.

// 매 렌더링 후 실행
useEffect(() => {
  document.title = `${count}번 클릭`;
});

// 최초 렌더링 후 한 번만 실행
useEffect(() => {
  initialData 가져오기();
  return () => cleanup();
}, []);

// count나 userId가 변경될 때 실행
useEffect(() => {
  fetchUserData(userId);
}, [userId, count]);

17. useMemouseCallback의 차이점은?

// useMemo: 계산된 값을 메모이제이션
const sortedList = useMemo(() => {
  return [...items].sort((a, b) => a.name.localeCompare(b.name));
}, [items]); // items가 변경될 때만 재정렬

// useCallback: 함수 참조를 메모이제이션
const handleClick = useCallback((id) => {
  setSelected(id);
  onSelect(id);
}, [onSelect]); // onSelect가 변경되지 않으면 안정적인 참조

TypeScript 질문

18. interfacetype의 차이점은?

// interface: 객체 형태에 적합, 확장 가능, 병합 가능
interface User {
  name: string;
  age: number;
}
interface User { // 선언 병합
  email: string;
}
interface Admin extends User {
  role: string;
}

// type: 더 유연, 모든 타입 표현 가능, 병합 불가
type StringOrNumber = string | number;
type Point = [number, number];

19. 제네릭을 실용적인 예시로 설명하세요.

// 제네릭 없이 - 타입 안전하지 않음
function getFirst(arr: any[]): any {
  return arr[0];
}

// 제네릭 사용 - 타입 정보 유지
function getFirst<T>(arr: T[]): T | undefined {
  return arr[0];
}

const firstNum = getFirst([1, 2, 3]); // 타입: number
const firstStr = getFirst(['a', 'b']); // 타입: string

// 제약이 있는 제네릭
function sortBy<T, K extends keyof T>(arr: T[], key: K): T[] {
  return [...arr].sort((a, b) => {
    if (a[key] < b[key]) return -1;
    if (a[key] > b[key]) return 1;
    return 0;
  });
}

성능 최적화

20. 자바스크립트 성능을 향상시키는 기법은?

메모리 관리:

  • 클로저와 이벤트 리스너의 메모리 누수 방지
  • 객체 키 캐시에 WeakMapWeakSet 사용
  • 컴포넌트 언마운트 시 인터벌과 타임아웃 제거

실행 효율성:

  • 비용이 많이 드는 이벤트 핸들러를 디바운스 및 스로틀
  • CPU 집약적 작업에 Web Workers 사용
  • 애니메이션에 requestAnimationFrame 선호
  • DOM 작업 일괄 처리
// 디바운스: 사용자가 타이핑을 멈출 때까지 대기
const handleSearch = debounce((query) => {
  fetchResults(query);
}, 300);

// 스로틀: 100ms마다 한 번으로 제한
const handleScroll = throttle((event) => {
  updateScrollPosition(event);
}, 100);

시스템 설계

21. 속도 제한기를 자바스크립트로 어떻게 설계하겠습니까?

class RateLimiter {
  constructor(maxRequests, windowMs) {
    this.maxRequests = maxRequests;
    this.windowMs = windowMs;
    this.requests = new Map(); // userId -> [타임스탬프]
  }

  isAllowed(userId) {
    const now = Date.now();
    const windowStart = now - this.windowMs;

    const userRequests = this.requests.get(userId) || [];
    const validRequests = userRequests.filter(time => time > windowStart);

    if (validRequests.length >= this.maxRequests) {
      this.requests.set(userId, validRequests);
      return false;
    }

    validRequests.push(now);
    this.requests.set(userId, validRequests);
    return true;
  }
}

const limiter = new RateLimiter(10, 60000); // 분당 10개 요청

22. LRU 캐시를 구현하는 방법은?

class LRUCache {
  constructor(capacity) {
    this.capacity = capacity;
    this.cache = new Map(); // Map은 삽입 순서 유지
  }

  get(key) {
    if (!this.cache.has(key)) return -1;

    // 끝으로 이동 (가장 최근 사용)
    const value = this.cache.get(key);
    this.cache.delete(key);
    this.cache.set(key, value);
    return value;
  }

  put(key, value) {
    if (this.cache.has(key)) {
      this.cache.delete(key);
    } else if (this.cache.size >= this.capacity) {
      // 가장 오래 전에 사용된 것 삭제 (Map의 첫 번째 항목)
      this.cache.delete(this.cache.keys().next().value);
    }
    this.cache.set(key, value);
  }
}

면접 팁

문제 해결 과정

  1. 코드 작성 전 요구사항 명확화 — 엣지 케이스와 제약 조건 확인
  2. 구현 전 접근 방식 설명
  3. 단순하게 시작하고 최적화
  4. 예시로 테스트 — 코드를 수동으로 단계별 확인
  5. 트레이드오프 논의 — 완벽한 해결책은 거의 없음

피해야 할 일반적인 실수

  • 엣지 케이스 무시 (빈 배열, null 입력, 정수 오버플로우)
  • 잘못된 자료 구조 선택 (O(1)인 Map 대신 O(n)인 배열)
  • 비동기 오류 처리 안 함
  • 입력 변경 (변경 가능 여부를 항상 명확히)

이 개념들을 연습하세요

관련 글