CS 기초 면접 질문 정리 (네트워크/알고리즘/OS 중심)
🌐 1. 네트워크 (Network) - 8문항
Q1. OSI 7계층과 각 계층의 역할은?
답변:
- 물리 계층: 비트 전송 (케이블, 허브)
- 데이터링크 계층: 프레임 전송, 오류 검출 (스위치, MAC)
- 네트워크 계층: 패킷 라우팅 (IP, 라우터)
- 전송 계층: 종단 간 통신, 흐름 제어 (TCP, UDP)
- 세션 계층: 세션 관리, 동기화
- 표현 계층: 데이터 인코딩, 암호화
- 응용 계층: 사용자 인터페이스 (HTTP, FTP, DNS)
개념 설명: 각 계층은 독립적으로 동작하며, 하위 계층의 서비스를 이용합니다. 문제 발생 시 해당 계층만 수정하면 되어 유지보수가 용이합니다.
Q2. TCP와 UDP의 차이점과 실제 사용 사례는?
답변:
| 특징 | TCP | UDP |
|---|---|---|
| 연결 | 연결 지향 | 비연결 |
| 신뢰성 | 보장 (재전송) | 보장 안 됨 |
| 순서 | 보장 | 보장 안 됨 |
| 속도 | 느림 | 빠름 |
| 헤더 크기 | 20바이트 | 8바이트 |
사용 사례:
- TCP: HTTP/HTTPS, 이메일(SMTP), 파일 전송(FTP), SSH
- UDP: 실시간 스트리밍, 온라인 게임, DNS, VoIP
개념 설명: TCP는 3-way handshake로 연결을 수립하고, 확인 응답(ACK)을 통해 신뢰성을 보장합니다. UDP는 오버헤드가 적어 실시간 통신에 적합합니다.
Q3. HTTP와 HTTPS의 차이, SSL/TLS 동작 원리는?
답변:
- HTTP: 평문 통신, 포트 80, 중간자 공격 취약
- HTTPS: SSL/TLS 암호화, 포트 443, 데이터 기밀성/무결성 보장
SSL/TLS Handshake 과정:
- 클라이언트 → 서버: Client Hello (지원 암호화 방식)
- 서버 → 클라이언트: Server Hello (인증서, 공개키)
- 클라이언트: 인증서 검증 후 대칭키 생성, 공개키로 암호화하여 전송
- 서버: 비밀키로 복호화하여 대칭키 획득
- 이후 대칭키로 데이터 암호화 통신
개념 설명: 비대칭키(공개키/비밀키)는 대칭키 교환에만 사용하고, 실제 데이터는 빠른 대칭키로 암호화합니다.
Q4. HTTP 상태 코드의 종류와 의미는?
답변:
- 1xx (정보): 요청 처리 중
- 100 Continue
- 2xx (성공): 요청 성공
- 200 OK, 201 Created, 204 No Content
- 3xx (리다이렉션): 다른 위치로 이동
- 301 Moved Permanently, 302 Found, 304 Not Modified
- 4xx (클라이언트 오류): 클라이언트 요청 오류
- 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 429 Too Many Requests
- 5xx (서버 오류): 서버 처리 오류
- 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable
개념 설명: 프론트엔드 개발 시 상태 코드에 따라 적절한 에러 핸들링과 사용자 피드백을 제공해야 합니다.
Q5. REST API와 RESTful 설계 원칙은?
답변: REST 6가지 원칙:
- Stateless: 각 요청은 독립적, 세션 정보를 서버에 저장하지 않음
- Client-Server: 클라이언트와 서버 분리
- Cacheable: 응답은 캐시 가능 여부 명시
- Uniform Interface: 일관된 인터페이스
- Layered System: 계층 구조
- Code on Demand (선택): 서버가 코드 전송 가능
RESTful 설계:
- 리소스는 명사로 표현 (동사 X)
- URI는 계층 구조로 표현
- HTTP 메서드로 행위 표현
GET /users # 사용자 목록 조회
GET /users/123 # 특정 사용자 조회
POST /users # 사용자 생성
PUT /users/123 # 사용자 전체 수정
PATCH /users/123 # 사용자 부분 수정
DELETE /users/123 # 사용자 삭제
개념 설명: RESTful API는 직관적이고 이해하기 쉬워 프론트엔드-백엔드 협업에 효과적입니다.
Q6. 쿠키(Cookie), 세션(Session), 토큰(JWT)의 차이는?
답변: 쿠키:
- 클라이언트에 저장되는 작은 데이터 (4KB 제한)
- 만료 기간 설정 가능
- HttpOnly, Secure, SameSite 속성으로 보안 강화
세션:
- 서버에 저장, 세션 ID를 쿠키로 전달
- 서버 메모리 사용, 확장성 제한
- 보안성 높음
토큰(JWT):
- 클라이언트에 저장, Stateless
- Header.Payload.Signature 구조
- 서버 확장에 유리, 토큰 크기가 큼
- 한번 발급하면 만료 전까지 무효화 어려움 (Refresh Token으로 해결)
개념 설명: 프론트엔드에서는 JWT를 localStorage나 sessionStorage에 저장하기보다 HttpOnly 쿠키에 저장하는 것이 XSS 공격 방지에 유리합니다.
Q7. CORS(Cross-Origin Resource Sharing)란?
답변: 브라우저가 다른 출처(origin)의 리소스 요청을 제한하는 보안 정책입니다.
출처(Origin): 프로토콜 + 도메인 + 포트
http://example.com:80≠https://example.com:443
동작 방식:
- Simple Request: GET, HEAD, POST (특정 조건)
- Preflight Request: OPTIONS 메서드로 사전 확인 후 본 요청
서버 설정:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Credentials: true
개념 설명: 프론트엔드 개발 시 자주 마주치는 이슈입니다. 개발 환경에서는 프록시 설정으로 우회 가능합니다.
Q8. DNS(Domain Name System) 동작 원리는?
답변: 도메인 이름을 IP 주소로 변환하는 시스템입니다.
DNS 조회 과정:
- 브라우저 캐시 확인
- OS 캐시 확인
- 로컬 DNS 서버(ISP)에 질의
- Root DNS 서버 → TLD DNS 서버(.com) → 권한 있는 DNS 서버 순으로 재귀 질의
- IP 주소 반환 및 캐싱
DNS 레코드 타입:
- A: 도메인 → IPv4
- AAAA: 도메인 → IPv6
- CNAME: 도메인 별칭
- MX: 메일 서버
- TXT: 텍스트 정보 (SPF, DKIM)
개념 설명: DNS 캐싱으로 반복 조회를 줄여 성능을 향상시킵니다. TTL(Time To Live)로 캐시 유효 기간을 설정합니다.
🎯 2. 알고리즘 (Algorithm) - 8문항
Q1. 시간 복잡도와 Big-O 표기법이란?
답변: 알고리즘의 실행 시간을 입력 크기 n의 함수로 표현하는 방법입니다.
주요 시간 복잡도:
- O(1): 상수 시간 - 배열 인덱스 접근
- O(log n): 로그 시간 - 이진 탐색
- O(n): 선형 시간 - 배열 순회
- O(n log n): - 병합 정렬, 퀵 정렬
- O(n²): 이차 시간 - 이중 for문, 버블 정렬
- O(2ⁿ): 지수 시간 - 피보나치 재귀
- O(n!): 팩토리얼 시간 - 순열 생성
개념 설명: Big-O는 최악의 경우를 나타내며, 입력이 커질수록 성능 차이가 극명해집니다. 실무에서는 O(n²) 이상은 피해야 합니다.
Q2. 정렬 알고리즘 비교 (버블, 선택, 삽입, 병합, 퀵, 힙)
답변:
| 알고리즘 | 평균 | 최악 | 공간 | 안정성 |
|---|---|---|---|---|
| 버블 정렬 | O(n²) | O(n²) | O(1) | O |
| 선택 정렬 | O(n²) | O(n²) | O(1) | X |
| 삽입 정렬 | O(n²) | O(n²) | O(1) | O |
| 병합 정렬 | O(n log n) | O(n log n) | O(n) | O |
| 퀵 정렬 | O(n log n) | O(n²) | O(log n) | X |
| 힙 정렬 | O(n log n) | O(n log n) | O(1) | X |
특징:
- 삽입 정렬: 거의 정렬된 데이터에 효율적
- 병합 정렬: 안정 정렬 필요 시 사용
- 퀵 정렬: 평균적으로 가장 빠름, 실무에서 많이 사용
- 힙 정렬: 추가 메모리 불필요
개념 설명: JavaScript의 Array.sort()는 엔진마다 다르지만, V8은 TimSort(삽입+병합 정렬)를 사용합니다.
Q3. 이진 탐색(Binary Search) 알고리즘과 구현은?
답변: 정렬된 배열에서 중간값과 비교하여 탐색 범위를 절반씩 줄이는 알고리즘입니다.
시간 복잡도: O(log n)
구현 (JavaScript):
function binarySearch(arr, target) {
let left = 0;
let right = arr.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) {
return mid;
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1; // 찾지 못함
}
개념 설명: 선형 탐색 O(n)보다 훨씬 빠르지만, 반드시 정렬된 배열이어야 합니다. 재귀로도 구현 가능합니다.
Q4. DFS와 BFS의 구현과 차이점은?
답변: DFS (깊이 우선 탐색):
// 재귀 방식
function dfs(graph, node, visited = new Set()) {
visited.add(node);
console.log(node);
for (const neighbor of graph[node]) {
if (!visited.has(neighbor)) {
dfs(graph, neighbor, visited);
}
}
}
// 스택 방식
function dfsIterative(graph, start) {
const visited = new Set();
const stack = [start];
while (stack.length > 0) {
const node = stack.pop();
if (!visited.has(node)) {
visited.add(node);
console.log(node);
for (const neighbor of graph[node].reverse()) {
if (!visited.has(neighbor)) {
stack.push(neighbor);
}
}
}
}
}
BFS (너비 우선 탐색):
function bfs(graph, start) {
const visited = new Set();
const queue = [start];
visited.add(start);
while (queue.length > 0) {
const node = queue.shift();
console.log(node);
for (const neighbor of graph[node]) {
if (!visited.has(neighbor)) {
visited.add(neighbor);
queue.push(neighbor);
}
}
}
}
비교:
- DFS: 스택/재귀, 경로 찾기, 사이클 검출, 백트래킹
- BFS: 큐, 최단 경로, 레벨별 탐색
개념 설명: 그래프 탐색의 기본입니다. 프론트엔드에서 DOM 트리 탐색, 상태 관리 등에 활용됩니다.
Q5. 동적 계획법(DP) vs 분할 정복 vs 탐욕 알고리즘
답변: 동적 계획법(DP):
- 중복되는 부분 문제를 한 번만 계산하여 저장(메모이제이션)
- 예: 피보나치, 배낭 문제, LCS
// 피보나치 DP
function fibonacci(n, memo = {}) {
if (n <= 1) return n;
if (memo[n]) return memo[n];
memo[n] = fibonacci(n - 1, memo) + fibonacci(n - 2, memo);
return memo[n];
}
분할 정복:
- 문제를 독립적인 부분 문제로 분할하여 해결
- 예: 병합 정렬, 퀵 정렬, 이진 탐색
탐욕 알고리즘:
- 각 단계에서 최선의 선택, 항상 최적해 보장은 아님
- 예: 거스름돈, 다익스트라, 크루스칼
개념 설명: DP는 중복 부분 문제가 있을 때, 분할 정복은 독립적인 부분 문제, 탐욕은 지역 최적해가 전역 최적해인 경우 사용합니다.
Q6. 투 포인터(Two Pointer) 알고리즘이란?
답변: 배열에서 두 개의 포인터를 이용하여 O(n) 시간에 문제를 해결하는 기법입니다.
예시 1: 두 수의 합 (정렬된 배열)
function twoSum(arr, target) {
let left = 0;
let right = arr.length - 1;
while (left < right) {
const sum = arr[left] + arr[right];
if (sum === target) {
return [left, right];
} else if (sum < target) {
left++;
} else {
right--;
}
}
return [-1, -1];
}
예시 2: 중복 제거
function removeDuplicates(arr) {
if (arr.length === 0) return 0;
let slow = 0;
for (let fast = 1; fast < arr.length; fast++) {
if (arr[fast] !== arr[slow]) {
slow++;
arr[slow] = arr[fast];
}
}
return slow + 1;
}
개념 설명: O(n²)를 O(n)으로 최적화할 수 있는 강력한 기법입니다. 슬라이딩 윈도우 기법과 함께 자주 사용됩니다.
Q7. 해시맵을 활용한 문제 해결 (Two Sum)
답변: 해시맵을 사용하면 O(n²)를 O(n)으로 개선할 수 있습니다.
Two Sum 문제:
// O(n²) 방법 - 이중 for문
function twoSumBrute(nums, target) {
for (let i = 0; i < nums.length; i++) {
for (let j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] === target) {
return [i, j];
}
}
}
return [];
}
// O(n) 방법 - 해시맵
function twoSum(nums, target) {
const map = new Map();
for (let i = 0; i < nums.length; i++) {
const complement = target - nums[i];
if (map.has(complement)) {
return [map.get(complement), i];
}
map.set(nums[i], i);
}
return [];
}
개념 설명: 해시맵은 키-값 쌍을 O(1)에 조회할 수 있어 많은 알고리즘 문제에서 최적화의 핵심입니다.
Q8. 재귀(Recursion)와 꼬리 재귀 최적화란?
답변: 재귀: 함수가 자기 자신을 호출하는 방식
일반 재귀:
// 팩토리얼
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1); // 호출 후 연산 필요
}
꼬리 재귀 (Tail Recursion):
function factorialTail(n, acc = 1) {
if (n <= 1) return acc;
return factorialTail(n - 1, n * acc); // 마지막이 재귀 호출
}
장점:
- 꼬리 재귀는 컴파일러가 반복문으로 최적화 가능
- 스택 오버플로우 방지
단점:
- JavaScript는 꼬리 재귀 최적화가 일부 엔진에서만 지원됨
개념 설명: 재귀는 코드가 간결하지만 스택 메모리를 많이 사용합니다. 깊은 재귀는 반복문으로 변환하는 것이 안전합니다.
💻 3. 운영체제 (Operating System) - 8문항
Q1. 프로세스와 스레드의 차이, 멀티프로세스 vs 멀티스레드
답변: 프로세스:
- 실행 중인 프로그램
- 독립적인 메모리 공간 (Code, Data, Heap, Stack)
- 프로세스 간 통신(IPC) 필요
- 안정적이지만 무거움
스레드:
- 프로세스 내 실행 단위
- Code, Data, Heap 공유, Stack만 독립적
- 직접 통신 가능
- 가볍지만 동기화 문제
비교:
| 특징 | 멀티프로세스 | 멀티스레드 |
|---|---|---|
| 메모리 | 독립적 | 공유 |
| 통신 | IPC (복잡) | 간단 |
| 생성 비용 | 큼 | 작음 |
| 안정성 | 높음 (격리) | 낮음 (공유) |
| Context Switching | 느림 | 빠름 |
개념 설명: Chrome은 탭마다 별도 프로세스 사용(안정성), Node.js는 단일 스레드 이벤트 루프 사용(효율성). 프론트엔드에서는 Web Worker로 멀티스레드 활용 가능합니다.
Q2. 동기(Synchronous)와 비동기(Asynchronous), 블로킹과 논블로킹
답변: 동기 vs 비동기:
- 동기: 작업 완료까지 대기, 순차적 실행
- 비동기: 작업 완료를 기다리지 않고 다음 작업 실행
블로킹 vs 논블로킹:
- 블로킹: 제어권을 넘기고 대기
- 논블로킹: 제어권을 바로 반환
조합:
- 동기 + 블로킹: 일반적인 함수 호출
- 비동기 + 논블로킹: fetch, setTimeout (가장 많이 사용)
- 동기 + 논블로킹: 폴링(주기적 확인)
- 비동기 + 블로킹: 드물게 사용
// 동기 블로킹
const result = syncFunction();
console.log(result);
// 비동기 논블로킹
fetch("/api/data")
.then((response) => response.json())
.then((data) => console.log(data));
console.log("바로 실행됨");
개념 설명: 프론트엔드 개발의 핵심 개념입니다. JavaScript는 싱글 스레드이지만 이벤트 루프를 통해 비동기 처리를 효율적으로 수행합니다.
Q3. 교착상태(Deadlock)의 발생 조건과 해결 방법
답변: 발생 조건 (4가지 모두 충족 시):
- 상호 배제(Mutual Exclusion): 자원을 한 번에 하나의 프로세스만 사용
- 점유와 대기(Hold and Wait): 자원을 가진 채로 다른 자원 대기
- 비선점(No Preemption): 강제로 자원을 빼앗을 수 없음
- 순환 대기(Circular Wait): 프로세스들이 원형으로 대기
해결 방법:
- 예방(Prevention): 4가지 조건 중 하나를 제거
- 자원 순서화로 순환 대기 방지
- 회피(Avoidance): 안전 상태 유지 (은행원 알고리즘)
- 탐지 및 회복(Detection & Recovery): 주기적 탐지 후 프로세스 종료
- 무시: 발생 확률이 낮으면 무시 (UNIX/Linux 방식)
실제 예시:
// JavaScript에서의 Deadlock
let lock1 = new Promise((resolve) => {});
let lock2 = new Promise((resolve) => {});
// Thread 1
async function task1() {
await lock1;
await lock2; // 영원히 대기
}
// Thread 2
async function task2() {
await lock2;
await lock1; // 영원히 대기
}
개념 설명: 데이터베이스 트랜잭션, 멀티스레드 프로그래밍에서 자주 발생합니다. 자원 획득 순서를 일관되게 유지하면 예방 가능합니다.
Q4. 컨텍스트 스위칭(Context Switching)이란?
답변: CPU가 실행 중인 프로세스/스레드를 전환하는 과정입니다.
과정:
- 현재 프로세스 상태 저장
- PC(Program Counter), 레지스터, 스택 포인터를 PCB에 저장
- 다음 프로세스 상태 복원
- PCB에서 상태 정보를 CPU 레지스터로 복원
- CPU 제어권 이전
- 새 프로세스 실행 시작
발생 시점:
- 인터럽트 발생 (I/O 요청, 타이머)
- 시스템 콜
- 스케줄러에 의한 CPU 재할당
오버헤드:
- 레지스터 저장/복원 시간
- 캐시 무효화 (Cache Miss 증가)
- TLB 플러시
프로세스 vs 스레드 Context Switching:
- 프로세스: 메모리 맵 전환 필요 (느림)
- 스레드: 같은 주소 공간 사용 (빠름)
개념 설명: 너무 자주 발생하면 오버헤드로 인해 전체 성능이 저하됩니다. 타임 슬라이스를 적절히 설정해야 합니다.
Q5. 메모리 관리: 페이징과 세그멘테이션
답변: 페이징(Paging):
- 프로세스를 고정 크기 페이지로 분할
- 물리 메모리를 같은 크기의 프레임으로 분할
- 페이지 테이블로 논리 주소 → 물리 주소 변환
- 장점: 외부 단편화 없음
- 단점: 내부 단편화 발생 가능
세그멘테이션(Segmentation):
- 프로세스를 논리적 단위(코드, 데이터, 스택)로 분할
- 가변 크기 세그먼트
- 장점: 논리적 구분 명확
- 단점: 외부 단편화 발생
페이지 크기:
- 너무 작으면: 페이지 테이블 커짐
- 너무 크면: 내부 단편화 증가
- 일반적으로 4KB 사용
개념 설명: 현대 OS는 페이징과 세그멘테이션을 결합한 세그멘테이션-페이징 기법을 사용합니다.
Q6. 가상 메모리와 페이지 교체 알고리즘
답변: 가상 메모리:
- 물리 메모리보다 큰 프로그램 실행 가능
- 디스크를 메모리처럼 사용
- 페이지 폴트 발생 시 디스크에서 로드
페이지 교체 알고리즘:
FIFO (First In First Out)
- 가장 먼저 들어온 페이지 교체
- 구현 간단, 성능 낮음
LRU (Least Recently Used)
- 가장 오래 사용되지 않은 페이지 교체
- 좋은 성능, 구현 복잡 (연결 리스트 + 해시맵)
LFU (Least Frequently Used)
- 사용 빈도가 가장 낮은 페이지 교체
Optimal (최적)
- 미래에 가장 오래 사용되지 않을 페이지 교체
- 이론상 최적, 실제 구현 불가능
Clock (Second Chance)
- FIFO + 참조 비트
- LRU의 근사 알고리즘
Thrashing (스래싱): 페이지 폴트가 너무 자주 발생하여 실제 작업보다 페이지 교체에 시간을 더 쓰는 현상
개념 설명: LRU는 웹 브라우저 캐시, Redis 등에서 널리 사용됩니다. JavaScript의 Map/Set과 유사한 자료구조로 구현 가능합니다.
Q7. CPU 스케줄링 알고리즘의 종류
답변: 비선점형(Non-preemptive):
FCFS (First Come First Served)
- 먼저 온 프로세스 먼저 처리
- Convoy Effect: 긴 프로세스 뒤에 짧은 프로세스들이 대기
SJF (Shortest Job First)
- 실행 시간이 짧은 프로세스 우선
- 최소 평균 대기 시간
- 실행 시간 예측 어려움, 기아(Starvation) 발생 가능
선점형(Preemptive):
SRTF (Shortest Remaining Time First)
- SJF의 선점형 버전
Round Robin (RR)
- 타임 퀀텀(Time Quantum) 단위로 순환
- 공평하지만 컨텍스트 스위칭 오버헤드
Priority Scheduling
- 우선순위가 높은 프로세스 먼저 실행
- 기아 문제 → Aging으로 해결
Multilevel Queue
- 여러 큐를 우선순위별로 관리
- 각 큐는 다른 스케줄링 알고리즘 사용
성능 지표:
- CPU 이용률: CPU가 일하는 시간 비율
- 처리량: 단위 시간당 완료된 프로세스 수
- 대기 시간: Ready Queue에서 대기한 시간
- 응답 시간: 첫 응답까지 걸린 시간
- 반환 시간: 완료까지 총 시간
개념 설명: 현대 OS는 대부분 Priority + Round Robin 조합을 사용합니다. 프론트엔드에서는 브라우저의 Task Queue 스케줄링과 유사한 개념입니다.
Q8. 동기화 문제: Race Condition, 세마포어, 뮤텍스
답변: Race Condition (경쟁 상태): 여러 프로세스/스레드가 공유 자원에 동시 접근할 때 실행 순서에 따라 결과가 달라지는 문제
임계 영역(Critical Section): 공유 자원에 접근하는 코드 영역
해결 방법:
- 뮤텍스(Mutex)
- 상호 배제(Mutual Exclusion)
- 한 번에 하나의 스레드만 접근
- Lock/Unlock 사용
// JavaScript 예시 (Atomic operation)
let lock = false;
function criticalSection() {
while (lock) {
/* 대기 */
}
lock = true;
// 임계 영역
sharedResource++;
lock = false;
}
세마포어(Semaphore)
- 카운터 기반 (S = 2이면 2개까지 동시 접근)
- P(wait): S--, V(signal): S++
- 이진 세마포어(S=1)는 뮤텍스와 유사
모니터(Monitor)
- 고수준 동기화 도구
- Java의 synchronized, wait(), notify()
뮤텍스 vs 세마포어:
- 뮤텍스: 소유권 개념, 락을 건 스레드만 해제 가능
- 세마포어: 소유권 없음, 다른 스레드도 해제 가능
개념 설명: 프론트엔드에서는 Web Worker 간 통신, SharedArrayBuffer 사용 시 동기화가 필요합니다. Atomics API로 원자적 연산 가능합니다.
📚 4. 자료구조 (Data Structure) - 3문항
Q1. 배열 vs 연결 리스트 vs 해시 테이블 비교
답변:
| 연산 | 배열 | 연결 리스트 | 해시 테이블 |
|---|---|---|---|
| 접근 | O(1) | O(n) | O(1) 평균 |
| 검색 | O(n) | O(n) | O(1) 평균 |
| 삽입 | O(n) | O(1) | O(1) 평균 |
| 삭제 | O(n) | O(1) | O(1) 평균 |
| 메모리 | 연속적 | 비연속적 | 비연속적 |
JavaScript에서:
// 배열
const arr = [1, 2, 3];
arr.push(4); // O(1)
arr.unshift(0); // O(n)
// 연결 리스트 (구현 필요)
class Node {
constructor(value) {
this.value = value;
this.next = null;
}
}
// 해시 테이블
const map = new Map();
map.set("key", "value"); // O(1)
const obj = {}; // Object도 해시 테이블
개념 설명: 상황에 따라 적절한 자료구조를 선택해야 합니다. 빠른 접근이 필요하면 배열, 잦은 삽입/삭제는 연결 리스트, 키-값 매핑은 해시 테이블을 사용합니다.
Q2. 스택과 큐의 활용 사례 및 구현
답변: 스택 (LIFO):
- 함수 호출 스택
- 브라우저 뒤로가기
- Undo/Redo 기능
- 괄호 짝 맞추기
- DFS
class Stack {
constructor() {
this.items = [];
}
push(item) {
this.items.push(item);
}
pop() {
return this.items.pop();
}
peek() {
return this.items[this.items.length - 1];
}
isEmpty() {
return this.items.length === 0;
}
}
큐 (FIFO):
- BFS
- 프린터 대기열
- 콜백 큐 (이벤트 루프)
- 메시지 큐
class Queue {
constructor() {
this.items = [];
}
enqueue(item) {
this.items.push(item);
}
dequeue() {
return this.items.shift(); // O(n), 개선 필요 시 연결 리스트 사용
}
front() {
return this.items[0];
}
isEmpty() {
return this.items.length === 0;
}
}
개념 설명: 프론트엔드에서 이벤트 루프의 Task Queue가 대표적인 큐 구조입니다.
Q3. 트리 구조 (이진 트리, BST, 힙)
답변: 이진 트리 (Binary Tree):
- 각 노드가 최대 2개의 자식
이진 탐색 트리 (BST):
- 왼쪽 < 부모 < 오른쪽
- 평균 O(log n), 최악 O(n)
트리 순회:
// 전위 순회 (Preorder): 부모 → 왼쪽 → 오른쪽
function preorder(node) {
if (!node) return;
console.log(node.value);
preorder(node.left);
preorder(node.right);
}
// 중위 순회 (Inorder): 왼쪽 → 부모 → 오른쪽
function inorder(node) {
if (!node) return;
inorder(node.left);
console.log(node.value);
inorder(node.right);
}
// 후위 순회 (Postorder): 왼쪽 → 오른쪽 → 부모
function postorder(node) {
if (!node) return;
postorder(node.left);
postorder(node.right);
console.log(node.value);
}
힙 (Heap):
- 완전 이진 트리
- 최대 힙: 부모 ≥ 자식
- 최소 힙: 부모 ≤ 자식
- 우선순위 큐 구현에 사용
개념 설명: React의 Virtual DOM은 트리 구조입니다. DOM 조작 최적화를 위해 트리 순회와 비교 알고리즘을 사용합니다.
🗄️ 5. 데이터베이스 (Database) - 3문항
Q1. 인덱스의 동작 원리와 B-Tree 구조
답변: 인덱스: 테이블의 검색 속도를 향상시키는 자료구조 (책의 색인과 유사)
B-Tree (Balanced Tree):
- 대부분의 DBMS가 사용하는 구조
- 노드에 여러 개의 키와 자식 포인터
- 균형 유지로 항상 O(log n) 보장
장점:
- SELECT WHERE 절 성능 향상
- ORDER BY, GROUP BY 성능 향상
- JOIN 성능 향상
단점:
- INSERT, UPDATE, DELETE 시 인덱스도 갱신 (오버헤드)
- 추가 저장 공간 (테이블 크기의 10%)
- 너무 많으면 오히려 성능 저하
인덱스 설계 원칙:
- WHERE 절에 자주 사용되는 컬럼
- 카디널리티가 높은 컬럼 (중복도가 낮음)
- 복합 인덱스는 순서 중요 (선택도가 높은 컬럼을 앞에)
개념 설명: 프론트엔드 개발자도 쿼리 최적화를 위해 인덱스 개념을 이해해야 합니다.
Q2. 트랜잭션 ACID와 격리 수준
답변: ACID:
- Atomicity: 전체 성공 or 전체 실패
- Consistency: 일관성 유지
- Isolation: 독립적 실행
- Durability: 영구 저장
격리 수준 문제:
- Dirty Read: 커밋 안 된 데이터 읽기
- Non-Repeatable Read: 같은 쿼리의 다른 결과
- Phantom Read: 새로운 행이 나타남
격리 수준:
| 수준 | Dirty Read | Non-Repeatable | Phantom |
|---|---|---|---|
| READ UNCOMMITTED | O | O | O |
| READ COMMITTED | X | O | O |
| REPEATABLE READ | X | X | O |
| SERIALIZABLE | X | X | X |
개념 설명: MySQL InnoDB는 기본적으로 REPEATABLE READ를 사용합니다. 동시성과 일관성의 트레이드오프를 고려해야 합니다.
Q3. SQL JOIN의 종류와 성능 최적화
답변: JOIN 종류:
-- INNER JOIN: 양쪽 모두 존재
SELECT * FROM users u
INNER JOIN orders o ON u.id = o.user_id;
-- LEFT JOIN: 왼쪽 전체 + 오른쪽 매칭
SELECT * FROM users u
LEFT JOIN orders o ON u.id = o.user_id;
-- RIGHT JOIN
SELECT * FROM users u
RIGHT JOIN orders o ON u.id = o.user_id;
-- FULL OUTER JOIN: 양쪽 전체
SELECT * FROM users u
FULL OUTER JOIN orders o ON u.id = o.user_id;
성능 최적화:
- 인덱스 활용: JOIN 키에 인덱스 생성
- 작은 테이블을 먼저: Driving Table 선택
- 불필요한 컬럼 제거: SELECT * 지양
- 서브쿼리 대신 JOIN: 대부분 JOIN이 빠름
- EXIST 사용: IN보다 효율적인 경우 많음
개념 설명: N+1 문제를 피하기 위해 Eager Loading을 사용하거나, GraphQL DataLoader로 해결할 수 있습니다.
프론트엔드 개발자로서 네트워크(HTTP, REST API, CORS), 알고리즘(시간 복잡도, 정렬, 해시맵), OS(프로세스/스레드, 동기/비동기) 개념을 잘 이해하시면 백엔드와의 협업과 성능 최적화에 큰 도움이 됩니다!
추가로 궁금하신 부분이나 더 깊이 알고 싶은 주제가 있으면 말씀해주세요! 🚀