[
  {
    "courseSlug": "ai-tools",
    "question": "이 코스에서 AI 코딩 도구를 크게 두 갈래로 나눈 것은 무엇인가?",
    "option1": "코드 리뷰형과 배포 자동화형",
    "option2": "자동완성형과 챗 어시스턴트형",
    "option3": "검색 엔진형과 문서 번역형",
    "option4": "디버깅 전용형과 테스트 전용형",
    "answerIndex": 1,
    "explanation": "섹션 1에서 AI 코딩 도구를 에디터에서 다음 코드를 이어 써 주는 자동완성형(예: GitHub Copilot)과 대화창에 질문하면 답하는 챗 어시스턴트형(예: ChatGPT, Claude)의 두 갈래로 소개했다."
  },
  {
    "courseSlug": "ai-tools",
    "question": "코스에서 말하는 '좋은 질문의 3요소'로 올바르게 짝지어진 것은?",
    "option1": "맥락 · 속도 · 정확성",
    "option2": "목표 · 일정 · 결과",
    "option3": "예의 · 간결함 · 끈기",
    "option4": "맥락 · 제약 · 예시",
    "answerIndex": 3,
    "explanation": "섹션 2에서 좋은 질문의 3요소는 맥락(어떤 환경에서 뭘 만들다가), 제약(버전·라이브러리 등 조건), 예시(실제 코드·에러 메시지)라고 정리했다. 30분 룰에서 배운 멘토 질문법과 같은 구조다."
  },
  {
    "courseSlug": "ai-tools",
    "question": "다음 중 코스의 'AI 답변 검증 4단계' 체크리스트에 해당하지 않는 것은?",
    "option1": "같은 질문을 여러 AI에게 던져서 답이 많이 겹치는 쪽을 고른다",
    "option2": "한 줄 한 줄 읽고 무슨 코드인지 스스로 설명할 수 있는지 확인한다",
    "option3": "로컬에서 정상 케이스 1개와 이상한 입력 1개로 실제 실행해 본다",
    "option4": "낯선 API나 설정이면 공식 문서를 열어 근거를 교차 확인한다",
    "answerIndex": 0,
    "explanation": "섹션 3의 4단계는 '설명할 수 있는가, 우리 프로젝트에 맞는가, 실제로 돌아가는가, 근거를 교차 확인했는가'다. 여러 AI의 다수결로 고르는 방법은 체크리스트에 없다."
  },
  {
    "courseSlug": "ai-tools",
    "question": "AWESOMEDEV AI 사용 보안 수칙에 따르면, AI 입력창에 넣어도 되는 것은 무엇인가?",
    "option1": "고객사 프로젝트의 소스 코드",
    "option2": "서버 IP와 계정이 함께 적힌 접속 정보",
    "option3": "교재용 학습 플랫폼(mirim-app)처럼 공개 가능한 학습 코드",
    "option4": ".env 파일에 들어 있는 API 키와 토큰 값",
    "answerIndex": 2,
    "explanation": "섹션 5의 보안 수칙에서 공개 가능한 학습 코드, 내가 연습용으로 짠 코드, 경로·키 값을 지운 일반적인 에러 메시지는 넣어도 된다고 했다. 고객사 코드·비밀번호·접속 정보·개인정보는 절대 금지다."
  },
  {
    "courseSlug": "ai-tools",
    "question": "수습 B가 React 화면이 하얗게 나오면서 콘솔에 \"TypeError: Cannot read properties of undefined (reading 'map')\" 에러를 만났다. 코스의 실습 기준으로 가장 잘 쓴 프롬프트는?",
    "option1": "\"에러가 났어요. 급하니까 빨리 고쳐 주세요. 부탁드립니다.\"",
    "option2": "\"React로 코스 목록 화면을 만드는 중인데, 화면이 하얗게 나오고 이 에러 전문이 떠요. 12번째 줄 근처 코드와 제가 시도한 것도 붙일게요. 왜 undefined에서 map을 못 읽는지 원리도 설명해 주세요.\"",
    "option3": "\"TypeError라는 게 정확히 무엇인지 사전적인 정의부터 하나하나 알려 주세요.\"",
    "option4": "\"프로젝트 코드 전체를 붙여넣을 테니 알아서 전부 고친 완성본 파일만 만들어 주세요.\"",
    "answerIndex": 1,
    "explanation": "섹션 7의 실습처럼 맥락(React로 무엇을 만드는지)·증상(에러 전문)·코드(해당 줄 근처)·시도한 것을 담고 원리 설명까지 요청한 프롬프트가 좋은 질문이다. '고쳐줘'만 보내면 AI도 점쟁이가 된다고 했다."
  },
  {
    "courseSlug": "api",
    "question": "코스의 식당 비유에서 '메뉴판'에 해당하는 것은 무엇인가요?",
    "option1": "요청(Request) — 손님이 적어 내는 주문서",
    "option2": "API 문서 — 뭘 시킬 수 있는지 적힌 목록",
    "option3": "백엔드와 DB — 요리를 만드는 주방",
    "option4": "응답(Response) — 주방에서 나온 음식",
    "answerIndex": 1,
    "explanation": "섹션 1의 비유에서 메뉴판은 API 문서에 해당해요. 손님(프론트엔드)은 메뉴판에 있는 것만 주문할 수 있고, 그래서 정해진 요청에 정해진 응답이 오는 예측 가능한 약속이 됩니다."
  },
  {
    "courseSlug": "api",
    "question": "REST 규칙에서 '무엇을(자원)'과 '어떻게(행위)'는 각각 어디에 담나요?",
    "option1": "자원은 HTTP 메서드에, 행위는 URL에 담는다",
    "option2": "자원과 행위를 모두 요청 본문(body)에 담는다",
    "option3": "자원은 URL에, 행위는 HTTP 메서드에 담는다",
    "option4": "자원은 헤더에, 행위는 상태코드에 담는다",
    "answerIndex": 2,
    "explanation": "섹션 2의 핵심 두 줄 요약이에요. 자원은 명사로 URL에(/api/checklists/3), 행위는 GET·POST·PUT·DELETE 같은 메서드에 담아서, URL과 메서드만 봐도 무슨 일이 일어날지 읽히게 합니다."
  },
  {
    "courseSlug": "api",
    "question": "수습 사원이 로그인한 상태에서 관리자 전용 API를 호출했을 때, 서버가 돌려주는 상태코드는 무엇인가요?",
    "option1": "400 — 요청 본문의 문법이 잘못되었다는 뜻",
    "option2": "401 — 토큰이 없거나 만료되었다는 뜻",
    "option3": "404 — 요청한 자원이 존재하지 않는다는 뜻",
    "option4": "403 — 누군지는 알지만 권한이 없다는 뜻",
    "answerIndex": 3,
    "explanation": "섹션 6에서 401은 인증 실패(너 누구야?), 403은 인가 실패(누군진 아는데 자격이 없어)로 구분했어요. 로그인은 했지만 권한이 없는 경우이므로 403 Forbidden입니다."
  },
  {
    "courseSlug": "api",
    "question": "다음 중 코스에서 배운 JSON 문법 규칙으로 올바른 것은 무엇인가요?",
    "option1": "이름표(key)는 항상 큰따옴표로 감싸야 한다",
    "option2": "문자열에는 작은따옴표를 쓰는 것도 허용된다",
    "option3": "마지막 항목 뒤에 쉼표를 붙여도 문제없다",
    "option4": "// 를 사용해 주석을 자유롭게 달 수 있다",
    "answerIndex": 0,
    "explanation": "섹션 3의 규칙 4가지 중 하나로, key는 항상 큰따옴표로 감싸요. 반대로 작은따옴표, 마지막 항목 뒤 쉼표, 주석(//)은 모두 코스에서 '함정 주의'로 짚은 JSON 에러 원인입니다."
  },
  {
    "courseSlug": "api",
    "question": "동료가 체크리스트 삭제 기능을 'GET /api/deleteChecklist?id=3' 으로 만들었습니다. 코스 내용에 비추어 이 설계의 가장 큰 문제는 무엇인가요?",
    "option1": "GET은 응답 본문을 받을 수 없어서 삭제 결과를 확인할 방법이 없다",
    "option2": "GET을 안전하다고 믿는 크롤러가 링크를 따라가며 데이터를 지울 수 있고, 동사는 URL이 아니라 메서드에 담아야 한다",
    "option3": "URL에 물음표(?)와 숫자를 함께 쓰면 REST에서 요청 자체가 거부된다",
    "option4": "삭제는 반드시 POST로만 보내야 하며 DELETE 메서드는 조회 전용이다",
    "answerIndex": 1,
    "explanation": "섹션 2에서 배운 두 가지가 모두 어긋난 설계예요. 브라우저·검색엔진은 GET을 안전한 읽기로 믿고 미리 불러가기도 해서 크롤러가 데이터를 전부 지운 사고가 실제로 있었고, 자원은 명사(URL)·행위는 메서드로 표현해 DELETE /api/checklists/3 처럼 써야 합니다."
  },
  {
    "courseSlug": "assembly",
    "question": "부품 고르기의 '호환성 3대 체크'에서 CPU와 메인보드의 관계로 옳은 것은?",
    "option1": "같은 제조사 제품이면 소켓이 달라도 장착할 수 있다",
    "option2": "소켓 이름이 같은 CPU와 메인보드끼리만 장착할 수 있다",
    "option3": "CPU가 더 최신 제품이면 어떤 메인보드에든 장착할 수 있다",
    "option4": "DDR5를 지원하는 메인보드라면 CPU 종류는 상관없다",
    "answerIndex": 1,
    "explanation": "코스의 호환성 3대 체크 중 첫 번째로, CPU와 보드는 '같은 소켓 이름'끼리만 결혼할 수 있다고 설명해요. 예를 들어 LGA1700 CPU는 AM5 보드에 아예 꽂히지 않습니다."
  },
  {
    "courseSlug": "assembly",
    "question": "파워(PSU) 뒷면 스위치의 I와 O 표시에 대한 설명으로 옳은 것은?",
    "option1": "O가 켜짐이고 I가 꺼짐이다",
    "option2": "I와 O는 전압을 고르는 선택 스위치다",
    "option3": "I가 켜짐이고 O가 꺼짐이다",
    "option4": "I는 절전 모드이고 O는 완전 꺼짐이다",
    "answerIndex": 2,
    "explanation": "코스의 '초보가 자주 하는 실수'에서 파워 뒷면 스위치는 I가 켜짐, O가 꺼짐이라고 알려줘요(이진수의 1과 0). '전원 버튼을 눌러도 반응이 없어요'의 단골 범인입니다."
  },
  {
    "courseSlug": "assembly",
    "question": "첫 부팅에서 팬은 도는데 화면이 안 나올 때, 코스에서 가장 먼저 해보라고 안내하는 조치는?",
    "option1": "부팅 USB를 만들어 윈도우를 다시 설치한다",
    "option2": "CPU 쿨러를 분리해 서멀구리스를 다시 바른다",
    "option3": "파워를 더 높은 용량의 제품으로 교체한다",
    "option4": "RAM을 뽑았다가 \"딸깍\" 소리가 나게 다시 장착한다",
    "answerIndex": 3,
    "explanation": "화면이 안 나오는 원인 1위는 RAM이 덜 꽂힌 상태예요. 코스의 점검 순서에서도 팬은 도는데 화면이 안 나오면 RAM을 뽑았다가 '딸깍' 소리가 나게 재장착하라고 안내합니다."
  },
  {
    "courseSlug": "assembly",
    "question": "조립 마지막 단계인 '선정리'를 하는 진짜 이유로 코스에서 설명하는 것은?",
    "option1": "케이스 안 공기 흐름을 확보해 냉각을 돕기 위해서",
    "option2": "케이블끼리 닿으면 합선이 일어날 수 있어서",
    "option3": "전면 패널 커넥터를 꽂을 공간을 만들기 위해서",
    "option4": "케이블 길이를 줄여 파워 효율을 높이기 위해서",
    "answerIndex": 0,
    "explanation": "표준 9단계의 마지막인 선정리는 예쁘라고 하는 게 아니라 공기 흐름, 즉 냉각을 위해서라고 코스에서 설명해요. 케이블을 케이스 뒷면 공간으로 넘기고 케이블타이로 묶습니다."
  },
  {
    "courseSlug": "assembly",
    "question": "동아리 후배의 PC는 RAM 8GB에 SSD 256GB인데, IntelliJ와 도커를 같이 켜면 버벅인다고 해요. 코스의 '가상 업그레이드 계획' 기준으로 1순위 업그레이드는?",
    "option1": "외장 그래픽카드 추가 — 화면을 그리는 건 GPU의 일이니까",
    "option2": "RAM을 32GB로 증설 — 개발 도구는 메모리 대식가이고 가장 싸고 체감이 크니까",
    "option3": "CPU 교체 — 컴파일 속도가 곧 개발 체감 속도니까",
    "option4": "SSD를 1TB로 교체 — 저장 공간이 커지면 속도도 빨라지니까",
    "answerIndex": 1,
    "explanation": "코스의 업그레이드 계획 예시에서 개발 공부용 PC의 1순위는 RAM 8GB→32GB예요. 개발 도구는 메모리 대식가라서 가장 싸고 체감이 큰 업그레이드이기 때문이고, CPU는 소켓 문제로 사실상 새 PC가 되어 3순위입니다."
  },
  {
    "courseSlug": "aws-intro",
    "question": "코스에서 말하는 클라우드의 핵심 발상 전환으로 가장 알맞은 것은?",
    "option1": "서버 컴퓨터를 더 싸게 대량 구매해서 소유하는 것",
    "option2": "컴퓨터를 소유하는 대신 컴퓨팅 파워를 쓴 만큼 구독하는 것",
    "option3": "모든 소프트웨어를 무료로 내려받아 쓰는 것",
    "option4": "회사마다 데이터센터를 직접 지어 운영하는 것",
    "answerIndex": 1,
    "explanation": "코스 섹션 1은 공장이 발전기를 버리고 전기를 사 쓰게 된 것처럼, '컴퓨터를 소유'하는 시대에서 '컴퓨팅 파워를 구독'하는 시대로 바뀐 것이 클라우드 혁명의 핵심이라고 설명합니다."
  },
  {
    "courseSlug": "aws-intro",
    "question": "코스에서 S3(Simple Storage Service)를 한 줄로 소개한 내용은 무엇인가요?",
    "option1": "DB를 AWS가 대신 설치하고 백업해 주는 관리형 데이터베이스",
    "option2": "클릭 몇 번으로 만드는 가상 서버 인스턴스",
    "option3": "이미지·백업·로그를 파일 단위로 넣고 꺼내는 용량 무제한 웹 창고",
    "option4": "코드만 올리면 실행 환경을 알아서 준비해 주는 서비스",
    "answerIndex": 2,
    "explanation": "섹션 5에서 S3는 '용량 무제한 파일 창고'로, 서버 디스크와 달리 파일 단위로 넣고 꺼내는 웹 창고라고 소개합니다. DB를 대신 관리해 주는 것은 RDS입니다."
  },
  {
    "courseSlug": "aws-intro",
    "question": "인스턴스 이름 t3.medium에서 숫자 '3'이 뜻하는 것은?",
    "option1": "3세대라는 뜻으로, 숫자가 클수록 최신이다",
    "option2": "vCPU가 3개 달려 있다는 뜻이다",
    "option3": "메모리가 3GB라는 뜻이다",
    "option4": "크기 등급 중 3번째로 크다는 뜻이다",
    "answerIndex": 0,
    "explanation": "섹션 3의 '자동차 모델명 읽기' 비유에서 t는 패밀리(차종), 3은 세대(연식), medium은 크기(배기량)입니다. 숫자가 클수록 최신 세대로 보통 더 빠르고 저렴하다고 설명합니다."
  },
  {
    "courseSlug": "aws-intro",
    "question": "EC2 인스턴스를 중지(Stop)했을 때에도 계속 나가는 요금은 무엇인가요?",
    "option1": "인스턴스의 시간당 사용료",
    "option2": "프리 티어 가입 시 내는 기본 요금",
    "option3": "t 패밀리의 CPU 버스트 크레딧 요금",
    "option4": "EBS 디스크와 서버에 연결 안 된 탄력적 IP 요금",
    "answerIndex": 3,
    "explanation": "섹션 4에서 인스턴스를 끄면 시간당 사용료는 멈추지만, 데이터를 보관 중인 EBS 디스크와 서버에 연결되지 않은 탄력적 IP는 계속 과금된다고 설명합니다. '켜 놓고 잊어버림'과 함께 대표적인 요금 함정입니다."
  },
  {
    "courseSlug": "aws-intro",
    "question": "수습 A가 nslookup으로 edu.awesomedevapp.com의 IP를 확인해 두었다. 며칠 뒤 서버가 재시작됐는데도 같은 IP로 잘 접속된다. 코스 내용상 이를 가능하게 하는 부품은?",
    "option1": "보안 그룹이 허락된 포트를 고정해 주기 때문",
    "option2": "탄력적 IP가 '영구 도로명 주소'처럼 서버에 붙어 있기 때문",
    "option3": "Caddy가 HTTPS 현관에서 요청을 대신 받아 주기 때문",
    "option4": "EBS 디스크에 서버의 IP 주소가 저장되어 있기 때문",
    "answerIndex": 1,
    "explanation": "섹션 6에서 EC2는 재시작하면 IP가 바뀔 수 있는데, 탄력적 IP는 '영구 도로명 주소'를 서버에 붙여 두는 것이라 도메인이 늘 같은 곳을 가리킬 수 있다고 설명합니다. 보안 그룹은 경비실(방화벽), Caddy는 HTTPS 처리 담당입니다."
  },
  {
    "courseSlug": "bandwidth",
    "question": "이 코스의 도로 비유에서 '대역폭(bandwidth)'은 무엇에 해당할까요?",
    "option1": "차 한 대가 달리는 최고 속도",
    "option2": "도로의 폭, 즉 동시에 지나갈 수 있는 차선 수",
    "option3": "출발지에서 목적지까지의 거리",
    "option4": "도로 위에 있는 신호등의 개수",
    "answerIndex": 1,
    "explanation": "섹션 1에서 배웠듯 신호가 이동하는 속도는 어느 회선이나 거의 비슷하고, 진짜 차이는 동시에 얼마나 많은 데이터를 흘려보낼 수 있느냐입니다. 그래서 대역폭은 차의 빠르기가 아니라 도로의 폭(차선 수)에 비유됩니다."
  },
  {
    "courseSlug": "bandwidth",
    "question": "통신사 500Mbps 상품을 쓸 때, 브라우저 다운로드 창에 뜰 수 있는 이론상 최대 속도는 약 얼마일까요?",
    "option1": "약 50 MB/s",
    "option2": "약 125 MB/s",
    "option3": "약 62.5 MB/s",
    "option4": "약 500 MB/s",
    "answerIndex": 2,
    "explanation": "Mbps의 b는 bit, MB/s의 B는 Byte이고 1바이트는 8비트이므로 500Mbps ÷ 8 = 62.5MB/s입니다. 섹션 2의 '8배의 비밀' — 다운로드 창 숫자가 작아 보이는 건 고장이 아니라 단위 착시예요."
  },
  {
    "courseSlug": "bandwidth",
    "question": "기가 인터넷을 쓰는데도 온라인 게임에서 랙이 걸릴 수 있는 이유로 가장 알맞은 것은?",
    "option1": "게임은 작은 데이터를 자주 주고받아서 대역폭보다 지연시간(ping)의 영향을 크게 받기 때문",
    "option2": "게임은 큰 데이터를 한꺼번에 받아야 해서 기가 회선으로도 대역폭이 부족하기 때문",
    "option3": "기가 회선은 다운로드 전용이라 게임의 업로드 데이터를 처리하지 못하기 때문",
    "option4": "핑이 낮아질수록 대역폭이 함께 줄어드는 반비례 관계가 있기 때문",
    "answerIndex": 0,
    "explanation": "섹션 4에서 배운 내용으로, 게임은 '내 캐릭터 움직였어!' 같은 작은 메시지를 아주 자주 주고받는 왕복 시간(ping) 싸움입니다. 도로가 아무리 넓어도 서버가 멀면 지연시간은 높을 수 있어요."
  },
  {
    "courseSlug": "bandwidth",
    "question": "인터넷 속도를 '제대로' 측정하는 방법으로 옳은 것은?",
    "option1": "숫자가 가장 크게 나오도록 해외 측정 서버를 골라서 잰다",
    "option2": "저녁 8~11시에 한 번만 측정해서 그 값을 기준으로 삼는다",
    "option3": "다운로드 숫자 하나만 기록하면 업로드와 핑은 볼 필요가 없다",
    "option4": "다른 기기·앱의 다운로드를 끄고, 시간대를 바꿔 2~3번 측정해 경향을 본다",
    "answerIndex": 3,
    "explanation": "섹션 7의 체크리스트대로 측정도 조건 통제가 생명입니다. 다운로드·업로드·핑을 모두 기록하고, 혼잡 시간(저녁 8~11시)을 피해 시간대를 바꿔 여러 번 재서 한 번의 숫자가 아니라 경향으로 읽어야 해요."
  },
  {
    "courseSlug": "bandwidth",
    "question": "500Mbps 상품을 쓰는 집에서 벽 너머 방에서 와이파이로 측정했더니 다운로드 90Mbps, 핑 5ms가 나왔습니다. 가장 의심되는 병목 구간은?",
    "option1": "통신사 회선 구간 — 계약 상품이 실제보다 낮게 설정되어 있다",
    "option2": "내 PC와 공유기 사이 구간 — 벽 너머 와이파이가 속도를 깎고 있다",
    "option3": "상대 서버 구간 — 해외 서버라 국제 구간에서 느려지고 있다",
    "option4": "측정 도구 구간 — 핑이 5ms로 낮게 나온 것 자체가 측정 오류다",
    "answerIndex": 1,
    "explanation": "섹션 7의 마지막 예시처럼 '다운로드는 낮은데 핑은 좋은' 패턴이면 회선(②)이 아니라 집 안 구간(①)이 병목일 가능성이 큽니다. 섹션 6에서 가정의 1순위 용의자는 거의 항상 벽·거리에 약해지는 와이파이라고 배웠어요."
  },
  {
    "courseSlug": "binary",
    "question": "컴퓨터가 0과 1, 딱 두 가지 상태만 사용하는 가장 큰 이유는 무엇인가요?",
    "option1": "2진수는 10진수보다 계산 공식이 단순해서 CPU 설계 비용이 줄어들기 때문",
    "option2": "켜짐과 꺼짐 두 상태만 있으면 전압이 조금 흔들려도 확실히 구분할 수 있기 때문",
    "option3": "전기의 세기를 10단계로 나누면 스위치(트랜지스터)가 10배 더 필요하기 때문",
    "option4": "0과 1만 쓰면 데이터가 자동으로 압축되어 저장 용량이 줄어들기 때문",
    "answerIndex": 1,
    "explanation": "섹션 1 '왜 0과 1인가'의 핵심입니다. 상태가 딱 2개면 헷갈릴 수가 없어서, 전압이 살짝 흔들려도 켜짐인지 꺼짐인지 확실히 구분됩니다. 반대로 0~9를 전압 10단계로 표현하면 6이 7로 읽히는 사고가 납니다."
  },
  {
    "courseSlug": "binary",
    "question": "2진수 1011을 10진수로 바꾸면 얼마인가요?",
    "option1": "9",
    "option2": "10",
    "option3": "11",
    "option4": "13",
    "answerIndex": 2,
    "explanation": "섹션 2의 자리값 계산입니다. 2진수는 오른쪽 끝부터 자리값이 1, 2, 4, 8로 커지고, 1이 켜진 자리의 값만 골라 더하면 됩니다. 1011은 8 + 0 + 2 + 1 = 11입니다."
  },
  {
    "courseSlug": "binary",
    "question": "1KB가 1,000바이트가 아니라 1,024바이트인 이유로 코스가 설명한 것은 무엇인가요?",
    "option1": "컴퓨터는 2의 거듭제곱으로 세는 게 편해서, 1,000에 가장 가까운 2¹⁰ = 1,024를 한 묶음으로 삼았기 때문",
    "option2": "하드디스크 제조사들이 용량을 더 크게 보이게 하려고 국제 규격을 1,024로 정했기 때문",
    "option3": "1바이트가 8비트이므로, 1,000에 가장 가까운 8의 거듭제곱이 1,024이기 때문",
    "option4": "초기 컴퓨터의 메모리가 1,024바이트 단위로만 생산되어 그 관습이 굳어졌기 때문",
    "answerIndex": 0,
    "explanation": "섹션 3 '1024의 비밀'입니다. 컴퓨터는 2진수 세상에 살기 때문에 2를 10번 곱한 1,024가 1,000에 아주 가까운 '2진수식 천 단위'가 되어 한 묶음으로 씁니다. 참고로 제조사는 1,000 기준으로 계산해서 1TB 디스크가 윈도우에선 약 931GB로 보입니다."
  },
  {
    "courseSlug": "binary",
    "question": "'뷁' 같은 글자 깨짐(외계어) 현상이 생기는 원인으로 코스가 설명한 것은 무엇인가요?",
    "option1": "글자 수가 128개를 넘으면 ASCII가 넘치는 글자를 자동으로 잘라내기 때문",
    "option2": "한글은 3바이트라서 전송 중에 마지막 바이트가 자주 손실되기 때문",
    "option3": "유니코드 번호표가 나라마다 달라서 같은 글자에 다른 번호가 붙기 때문",
    "option4": "UTF-8로 포장한 데이터를 EUC-KR 같은 다른 규칙으로 풀어서 번호표가 엉뚱한 글자에 매칭되기 때문",
    "answerIndex": 3,
    "explanation": "섹션 4의 UTF-8 설명입니다. 인코딩은 유니코드 번호를 바이트로 담는 '포장법'이라서, 포장 규칙과 개봉 규칙이 같아야 합니다. 우리 회사가 인코딩을 처음부터 끝까지 UTF-8로 통일하는 이유이기도 합니다."
  },
  {
    "courseSlug": "binary",
    "question": "수민이는 코스에서 배운 대로 한 손의 손가락 5개를 스위치처럼 써서(접힘=0, 펴짐=1) 수를 표현하려고 합니다. 한 손으로 셀 수 있는 가장 큰 수는 얼마인가요?",
    "option1": "15",
    "option2": "31",
    "option3": "32",
    "option4": "63",
    "answerIndex": 1,
    "explanation": "섹션 1의 손가락 비유입니다. 스위치 5개는 2×2×2×2×2 = 32가지 상태를 만들 수 있어 0부터 31까지 셀 수 있습니다. 생일의 '일'(1~31)이 반드시 2진수 5자리 안에 들어가는 이유이기도 합니다."
  },
  {
    "courseSlug": "cache-nosql",
    "question": "코스에서 설명한 '캐시 히트(hit)'에 해당하는 상황은 무엇인가?",
    "option1": "캐시에 데이터가 없어 DB까지 다녀온 뒤 응답하는 경우",
    "option2": "원본 DB가 바뀌어 캐시의 낡은 사본을 지워야 하는 경우",
    "option3": "캐시에 찾는 데이터가 있어 DB에 가지 않고 바로 응답하는 경우",
    "option4": "브라우저가 강력 새로고침으로 캐시를 무시하고 요청하는 경우",
    "answerIndex": 2,
    "explanation": "섹션 2의 사물함 비유처럼 캐시에 찾는 게 있으면 히트, 없으면 미스입니다. 미스일 때는 DB에 다녀오면서 캐시에 사본을 복사해 두어 다음 요청부터 히트가 납니다."
  },
  {
    "courseSlug": "cache-nosql",
    "question": "코스에서 Redis가 빠른 비결로 든 두 가지 이유를 바르게 묶은 것은?",
    "option1": "데이터를 메모리(RAM)에 두고, 키를 해시 계산 한 번으로 바로 찾기 때문",
    "option2": "데이터를 디스크에 압축해 저장하고, 인덱스를 미리 정렬해 두기 때문",
    "option3": "데이터를 표(테이블) 구조로 저장해 JOIN 연산이 강력하기 때문",
    "option4": "모든 데이터를 브라우저에 복사해 두어 네트워크를 타지 않기 때문",
    "answerIndex": 0,
    "explanation": "섹션 3에서 Redis는 디스크보다 훨씬 빠른 메모리(RAM)에 데이터를 두고, 키를 해시로 계산해 위치를 바로 알아내서 데이터가 백만 개여도 찾는 시간이 거의 같다고 설명합니다."
  },
  {
    "courseSlug": "cache-nosql",
    "question": "캐시의 유통기한(TTL)을 1시간처럼 길게 잡았을 때 생기는 문제로 코스가 설명한 것은?",
    "option1": "DB에 너무 자주 다녀오게 되어 캐시 효과가 떨어진다",
    "option2": "메모리 대신 디스크에 저장되어 읽기 속도가 크게 느려진다",
    "option3": "키를 해시로 찾을 수 없게 되어 검색 시간이 점점 늘어난다",
    "option4": "원본이 바뀌어도 낡은 정보를 계속 보여줄 위험이 커진다",
    "answerIndex": 3,
    "explanation": "섹션 4의 우유 유통기한 비유입니다. TTL이 짧으면 신선하지만 DB에 자주 다녀오고, 길면 빠르지만 낡은 정보를 보여줄 위험이 커지는 트레이드오프가 캐시 무효화가 어려운 이유입니다."
  },
  {
    "courseSlug": "cache-nosql",
    "question": "실습에서 확인한 상태 코드 304 Not Modified가 뜻하는 것은 무엇인가?",
    "option1": "서버가 요청받은 파일 전체를 본문에 담아 새로 보내 주었다",
    "option2": "파일이 안 바뀌었으니 브라우저가 가진 사본을 그대로 쓰라는 답장이다",
    "option3": "서버에 묻지 않고 메모리 캐시에서 바로 꺼내 썼다는 표시이다",
    "option4": "요청한 파일이 서버에서 삭제되어 더 이상 찾을 수 없다는 뜻이다",
    "answerIndex": 1,
    "explanation": "섹션 8에서 304는 서버가 ETag 등으로 버전을 비교해 보고 '안 바뀌었으니 네 사본을 써'라고 본문 없이 답장만 보내는 신호, 즉 브라우저 캐시 히트의 영수증이라고 설명합니다."
  },
  {
    "courseSlug": "cache-nosql",
    "question": "학습 플랫폼을 설계한다. 학생마다 형식이 제각각인 자유 양식 학습일지, 날아가도 다시 만들 수 있는 로그인 세션, 절대 사라지면 안 되는 수강 기록 — 섹션 7의 결정 기준에 맞는 배치는?",
    "option1": "학습일지는 Redis, 세션은 PostgreSQL, 수강 기록은 MongoDB",
    "option2": "학습일지는 PostgreSQL, 세션은 MongoDB, 수강 기록은 Redis",
    "option3": "학습일지는 MongoDB, 세션은 Redis, 수강 기록은 PostgreSQL",
    "option4": "학습일지는 Redis, 세션은 MongoDB, 수강 기록은 PostgreSQL",
    "answerIndex": 2,
    "explanation": "섹션 7의 세 질문 그대로입니다. 사라지면 안 되는 원본(수강 기록)은 PostgreSQL, 날아가도 재생성 가능한 것(세션)은 Redis, 문서마다 모양이 다른 것(자유 양식 학습일지)은 MongoDB가 맡습니다."
  },
  {
    "courseSlug": "cicd",
    "question": "코스에서 설명하는 CI(Continuous Integration, 지속적 통합)의 핵심 의미로 가장 알맞은 것은?",
    "option1": "서버에 새 버전을 올린 뒤 사람이 브라우저를 새로고침하며 직접 확인하는 것",
    "option2": "코드를 저장소에 합칠 때마다(push할 때마다) 로봇이 자동으로 빌드하고 테스트하는 것",
    "option3": "여러 사람의 코드를 한 달 치씩 모아 두었다가 한꺼번에 몰아서 검사하는 것",
    "option4": "새 버전에서 장애가 났을 때 이전 버전으로 1분 안에 되돌리는 것",
    "answerIndex": 1,
    "explanation": "섹션 2의 내용으로, CI는 push될 때마다 로봇이 빌드와 테스트를 자동으로 하는 것입니다. '가끔 크게'가 아니라 '자주 조금씩' 검사하기 때문에 버그를 일찍, 싸게 잡을 수 있어요."
  },
  {
    "courseSlug": "cicd",
    "question": "CD(지속적 배포)가 도입된 뒤 사람에게 남는 일은 무엇이라고 코스는 설명하나?",
    "option1": "빌드 결과물을 scp 명령으로 서버에 직접 올리는 일",
    "option2": "서버에 SSH로 접속해서 돌고 있던 옛 버전을 중지하는 일",
    "option3": "git push 명령 하나를 실행하는 일",
    "option4": "배포가 끝난 뒤 헬스체크 로그를 사람이 눈으로 검사하는 일",
    "answerIndex": 2,
    "explanation": "섹션 3에서 CD 이후에는 빌드·검사·업로드·교체·확인이 전부 로봇 몫이 되고, 사람이 하는 일은 git push 하나만 남는다고 설명합니다. 손빨래가 세탁기 버튼이 되는 비유가 여기에 해당해요."
  },
  {
    "courseSlug": "cicd",
    "question": "코스가 파이프라인의 가장 중요한 성질로 꼽는 것은?",
    "option1": "한 정거장이라도 실패하면 그 자리에서 멈춰 배포 단계까지 가지 않는다",
    "option2": "모든 단계를 사람보다 훨씬 빠른 속도로 처리해 배포 시간을 줄인다",
    "option3": "빌드와 테스트를 병렬로 동시에 실행해서 기다리는 시간을 아낀다",
    "option4": "장애가 나면 원인을 자동으로 분석해서 코드까지 고쳐 준다",
    "answerIndex": 0,
    "explanation": "섹션 4의 핵심으로, 파이프라인은 컨베이어 벨트처럼 '실패하면 멈추는' 구조입니다. 그래서 테스트가 깨진 코드는 배포 단계에 구조적으로 도착할 수 없고, 불량품이 사용자에게 가기 전에 벨트가 멈춥니다."
  },
  {
    "courseSlug": "cicd",
    "question": "코스에서 우리 스택의 롤백(되돌리기)을 빠르게 만들어 주는 열쇠로 꼽은 것은?",
    "option1": "장애가 나면 서버에 접속해 코드를 그 자리에서 고치고 다시 빌드하는 것",
    "option2": "배포 전에 테스트를 여러 번 반복 실행해서 장애 자체를 아예 막는 것",
    "option3": "Caddy가 이전 버전의 화면을 캐시로 저장해 두었다가 대신 보여 주는 것",
    "option4": "Docker 이미지에 버전 태그를 붙여 보관해 두고 이전 이미지로 다시 실행하는 것",
    "answerIndex": 3,
    "explanation": "섹션 6에서 롤백의 열쇠는 Docker라고 설명합니다. v1.2, v1.3처럼 버전마다 이미지를 태그로 보관하면, 되돌리기가 '옛 이미지로 컨테이너 다시 실행' 한 줄이 되기 때문이에요."
  },
  {
    "courseSlug": "cicd",
    "question": "수습 A가 마지막 실습대로 우리 플랫폼의 배포 지도를 그리고 각 단계에 [사람]·[자동]·[위험] 표시를 붙였다. 코스의 기준에 따르면 ★(자동화 1순위)을 쳐야 하는 단계는?",
    "option1": "[자동] 표시가 붙은, 이미 로봇이 처리하고 있는 단계",
    "option2": "[사람]과 [위험] 표시가 함께 붙은, 실수가 잦고 실수하면 아픈 단계",
    "option3": "[사람] 표시만 붙은 단계 중에서 시간이 가장 오래 걸리는 단계",
    "option4": "지도의 맨 앞에 있는, 코드 수정 직후의 첫 번째 단계",
    "answerIndex": 1,
    "explanation": "섹션 7 실습의 3단계 규칙으로, [사람]+[위험]이 겹치는 단계에 ★을 칩니다. 사람 손이 필요해 실수가 잦고, 실수하면 사이트가 죽는 아픈 곳부터 자동화하는 것이 1순위이기 때문이에요."
  },
  {
    "courseSlug": "clean-code",
    "question": "이 코스는 모든 규칙이 한 문장으로 요약된다고 소개합니다. 그 문장은 무엇인가요?",
    "option1": "코드는 무조건 짧게 써라",
    "option2": "동작만 같으면 스타일은 자유다",
    "option3": "읽는 사람의 물음표를 줄여라",
    "option4": "주석은 많이 달수록 안전하다",
    "answerIndex": 2,
    "explanation": "섹션 1의 팁에서 이름 짓기부터 코드리뷰 매너까지 이 코스의 규칙 전부가 '읽는 사람의 물음표를 줄여라' 한 문장의 각론이라고 설명합니다."
  },
  {
    "courseSlug": "clean-code",
    "question": "코스에서 제시한 이름 짓기 규칙으로 올바른 것은 무엇인가요?",
    "option1": "변수는 동사로, 함수는 명사로 짓는다",
    "option2": "변수·상수는 명사로, 함수는 동사로 시작한다",
    "option3": "모든 이름은 타자 속도를 위해 짧게 줄인다",
    "option4": "임시 값에는 tmp, data 같은 이름을 쓴다",
    "answerIndex": 1,
    "explanation": "섹션 2의 규칙 3줄 요약 — 변수·상수는 명사(activeMembers 등), 함수는 동사로 시작(fetchCourses 등), a·tmp·data 같은 '아무 이름'은 금지입니다."
  },
  {
    "courseSlug": "clean-code",
    "question": "함수를 쪼개야 할지 판단할 때 코스가 제시한 진짜 기준은 무엇인가요?",
    "option1": "함수가 5줄을 넘으면 무조건 쪼갠다",
    "option2": "주석이 세 개 이상 달리면 쪼갠다",
    "option3": "매개변수가 두 개를 넘으면 쪼갠다",
    "option4": "하는 일을 한 문장으로 말할 때 '그리고'가 나오면 쪼갠다",
    "answerIndex": 3,
    "explanation": "섹션 3에서 기준은 줄 수가 아니라 \"이 함수가 하는 일을 '그리고' 없이 한 문장으로 말할 수 있는가\"라고 했습니다. 한 가지 일을 하는 20줄 함수는 괜찮습니다."
  },
  {
    "courseSlug": "clean-code",
    "question": "코스가 말하는 '좋은 주석'의 예로 가장 알맞은 것은 무엇인가요?",
    "option1": "서버가 페이지 번호를 0이 아니라 1부터 세기 때문에 보정한다",
    "option2": "i 값을 1만큼 증가시키는 코드다",
    "option3": "activeMembers는 회원 목록을 담는 변수다",
    "option4": "아래 반복문은 배열을 처음부터 끝까지 돈다",
    "answerIndex": 0,
    "explanation": "섹션 4에 따르면 좋은 주석은 코드만 봐서는 알 수 없는 '왜'(의도·사정·제약)를 남기는 주석입니다. 나머지 셋은 코드가 이미 말하는 '무엇'을 반복한 소음 주석입니다."
  },
  {
    "courseSlug": "clean-code",
    "question": "수습 동료가 handleSubmit 함수 하나에서 입력 검사, 데이터 가공, 서버 전송, 화면 뒷정리를 전부 처리하고 있습니다. 이 상황을 코스의 비유로 표현하면 무엇인가요?",
    "option1": "받는 사람 없이 나만 알아보면 되는 일기 같은 코드다",
    "option2": "이름표 대신 '그거, 저거'라고 써 붙인 창고다",
    "option3": "한 사람이 주문받고 요리하고 서빙하고 설거지까지 하는 식당이다",
    "option4": "게시판 세 곳에 같은 공지를 붙여 둔 학교다",
    "answerIndex": 2,
    "explanation": "섹션 3 '함수는 한 가지 일만'의 비유입니다. 한 함수가 네 가지 일을 하면 사고 나기 쉽고 일을 나눠 줄 수도 없으니, 일 하나당 함수 하나로 쪼개야 합니다."
  },
  {
    "courseSlug": "cloud-network",
    "question": "우리 학습 플랫폼의 EC2 서버가 실제로 위치한 AWS 리전은 어디인가?",
    "option1": "ap-northeast-1 (도쿄 리전)",
    "option2": "us-east-1 (미국 버지니아 리전)",
    "option3": "ap-northeast-2 (서울 리전)",
    "option4": "ap-northeast-2a (서울의 가용영역)",
    "answerIndex": 2,
    "explanation": "코스 1섹션에서 우리 서버는 사용자와 물리적으로 가까운 서울 리전(ap-northeast-2)에 있다고 배웠다. ap-northeast-2a는 리전이 아니라 그 안의 독립된 건물 그룹인 가용영역(AZ)이다."
  },
  {
    "courseSlug": "cloud-network",
    "question": "코스에서 설명한 VPC(Virtual Private Cloud)의 개념으로 가장 알맞은 것은?",
    "option1": "클라우드 안에 친 가상의 울타리로, 다른 회사와 서로 전혀 보이지 않게 분리된 나만의 네트워크",
    "option2": "서버 여러 대 앞에서 요청을 골고루 나눠 주며 교통정리를 하는 장치",
    "option3": "전 세계 곳곳의 엣지 서버에 복사본을 미리 배치해 두는 전송 네트워크",
    "option4": "도시 단위로 데이터센터 건물들을 묶어 놓은 지리적 구역",
    "answerIndex": 0,
    "explanation": "2섹션에서 VPC는 거대한 데이터센터 단지 안에 울타리를 치고 '여기부터는 우리 땅'이라 선언한 가상 네트워크로, 다른 회사 VPC와는 서로 절대 보이지 않는다고 설명했다. 지난 코스의 공유기 사설 네트워크(192.168.x.x)의 회사·클라우드 버전이다."
  },
  {
    "courseSlug": "cloud-network",
    "question": "데이터베이스처럼 절대 외부에 노출되면 안 되는 서버를 배치할 서브넷과 그 이유로 옳은 것은?",
    "option1": "퍼블릭 서브넷 — 인터넷 게이트웨이와 연결되어 있어 관리자가 접속하기 편하기 때문",
    "option2": "퍼블릭 서브넷 — 손님(사용자)의 요청을 가장 빠르게 받아 처리할 수 있기 때문",
    "option3": "프라이빗 서브넷 — 퍼블릭 서브넷보다 IP 주소를 더 많이 확보할 수 있기 때문",
    "option4": "프라이빗 서브넷 — 인터넷에서 들어오는 길 자체가 없어 해커가 두드려도 접근할 수 없기 때문",
    "answerIndex": 3,
    "explanation": "3섹션의 핵심 원칙 '손님 맞을 것만 대로변에, 금고(DB)는 안쪽 방에'에 해당한다. 프라이빗 서브넷은 인터넷 게이트웨이(정문)와 이어지지 않아 외부에서 직접 들어올 길이 없으며, DB를 퍼블릭에 두는 것은 실무에서 가장 흔한 보안 사고 중 하나라고 경고했다."
  },
  {
    "courseSlug": "cloud-network",
    "question": "코스에서 탄력적 IP(Elastic IP)를 '서버의 평생 전화번호'에 비유했다. 탄력적 IP가 해결해 주는 문제는 무엇인가?",
    "option1": "서버가 살아 있는지 주기적으로 확인해 쓰러진 서버를 배분 명단에서 빼는 문제",
    "option2": "EC2 서버를 껐다 켜면 공인 IP가 바뀌어 도메인이 허공을 가리키게 되는 문제",
    "option3": "지구 반대편 사용자에게는 물리적 거리 때문에 응답이 느려지는 문제",
    "option4": "한 데이터센터 건물에 정전이나 화재가 나면 서비스 전체가 멈추는 문제",
    "answerIndex": 1,
    "explanation": "4섹션에서 EC2는 껐다 켜면 공인 IP가 바뀔 수 있어 도메인이 가리키던 주소가 어긋나는 사고가 나는데, 탄력적 IP(우리 플랫폼은 3.36.160.246)를 고정으로 붙이면 재부팅하거나 새 서버로 갈아타도 같은 주소를 유지한다고 배웠다. 휴대폰 번호이동과 같은 원리다."
  },
  {
    "courseSlug": "cloud-network",
    "question": "우리 플랫폼에 브라질 지사가 생겨 CDN을 도입하기로 했다. 코스의 '편의점 체인' 비유에 따른 올바른 배치는 무엇인가?",
    "option1": "개인별 학습 기록을 각 엣지 서버에 미리 복사해 두고, 이미지는 서울 서버가 매번 새로 보낸다",
    "option2": "모든 데이터를 브라질 엣지 서버로 옮기고 서울의 원본 서버는 더 이상 쓰지 않는다",
    "option3": "이미지·JS/CSS처럼 모두에게 똑같은 파일은 엣지에 두고, 사람마다 다른 학습 기록은 서울 원본 서버가 만든다",
    "option4": "HTTPS 암호 해제만 엣지 서버에 맡기고, 이미지를 포함한 모든 요청은 전부 서울 서버로 보낸다",
    "answerIndex": 2,
    "explanation": "7섹션에서 CDN에는 이미지·동영상·JS/CSS처럼 자주 바뀌지 않는 것을 두고, 개인 학습 기록처럼 매번 달라지는 데이터는 원본 서버가 만들어야 한다고 했다. 본사 공장(원본 서버)은 그대로 두고 동네 편의점(엣지)에 똑같은 물건을 미리 깔아 두는 비유와 같다."
  },
  {
    "courseSlug": "coding",
    "question": "이 코스에서는 '코딩'을 무엇이라고 정의하나요?",
    "option1": "컴퓨터가 스스로 배우도록 데이터를 주는 일",
    "option2": "컴퓨터에게 일을 시키는 글쓰기",
    "option3": "컴퓨터 부품을 조립해 성능을 높이는 작업",
    "option4": "컴퓨터 화면을 예쁘게 꾸미는 디자인 작업",
    "answerIndex": 1,
    "explanation": "섹션 ①에서 코딩은 '컴퓨터에게 일을 시키는 글쓰기'라고 정의합니다. 컴퓨터는 적힌 대로만 움직이므로 무엇을, 어떤 순서로, 어떤 조건에서 할지 빠짐없이 적어야 합니다."
  },
  {
    "courseSlug": "coding",
    "question": "로그인 페이지 코드에서 setUsername 같은 set 함수로 상태(state) 값을 바꾸면 React는 어떤 일을 하나요?",
    "option1": "바뀐 값을 서버에 자동으로 저장한다",
    "option2": "브라우저 전체를 새로고침해서 초기화한다",
    "option3": "값이 바뀐 것을 알아채고 화면을 다시 그린다",
    "option4": "에러가 없는지 검사해 콘솔에 결과를 출력한다",
    "answerIndex": 2,
    "explanation": "섹션 ②의 useState 설명대로, set 함수로 값을 바꾸면 React가 '값이 바뀌었네? 화면도 다시 그리자'라고 알아챕니다. 입력창에 한 글자 칠 때마다 이 일이 일어납니다."
  },
  {
    "courseSlug": "coding",
    "question": "대시보드 체크리스트 코드에 나오는 done ? ' done' : '' 한 줄의 의미로 알맞은 것은?",
    "option1": "체크된 항목이면 done이라는 CSS 클래스를 붙이고, 아니면 붙이지 않는다",
    "option2": "체크된 항목이면 그 항목을 목록에서 바로 삭제한다",
    "option3": "체크 여부와 상관없이 모든 항목에 done 클래스를 붙인다",
    "option4": "체크된 항목이면 서버에 완료 알림 요청을 보낸다",
    "answerIndex": 0,
    "explanation": "섹션 ③에서 이 조건식은 '체크됐으면 done 클래스를 붙이고, 아니면 붙이지 마라'는 if문과 같다고 설명합니다. 체크할 때 글자에 취소선이 생기는 이유가 바로 이 한 줄입니다."
  },
  {
    "courseSlug": "coding",
    "question": "이 앱에서 '비밀번호가 맞는지' 같은 믿을 수 있는 판단을 프론트엔드가 아니라 백엔드(서버)가 맡는 이유는 무엇인가요?",
    "option1": "브라우저에서는 자바스크립트로 비교 연산을 할 수 없기 때문",
    "option2": "서버가 브라우저보다 계산 속도가 훨씬 빠르기 때문",
    "option3": "프론트엔드는 화면을 그리는 기능이 아예 없기 때문",
    "option4": "브라우저에서 도는 코드는 사용자가 열어보고 고칠 수 있기 때문",
    "answerIndex": 3,
    "explanation": "섹션 ④에서 브라우저 코드는 사용자가 얼마든지 열어보고 고칠 수 있어, 검사 코드를 지워버리면 통과할 수 있다고 설명합니다. 그래서 로그인·권한·저장 같은 판단은 전부 서버가 합니다."
  },
  {
    "courseSlug": "coding",
    "question": "수습생 A가 회원가입 화면에서 이미 다른 사람이 쓰고 있는 아이디를 입력하자 '이미 사용 중인 아이디예요'라는 문구가 떴습니다. 이 동작을 만들어 낸 규칙은 코스의 어느 부분일까요?",
    "option1": "passwordHash 필드가 비밀번호를 해시로만 저장하는 보안 수칙",
    "option2": "User 엔티티의 username 컬럼에 붙은 unique = true 규칙",
    "option3": ".map()이 배열의 각 항목마다 화면을 하나씩 만들어 내는 반복 규칙",
    "option4": "try/catch 구조로 실패 상황을 미리 준비해 두는 함수 작성법",
    "answerIndex": 1,
    "explanation": "섹션 ⑤의 '직접 확인해 보기'에서 다룬 상황입니다. users 테이블의 username에 unique = true가 붙어 있어 같은 아이디로 두 명이 가입할 수 없다는 규칙이 데이터베이스 차원에서 강제됩니다."
  },
  {
    "courseSlug": "color",
    "question": "색의 3속성 중 채도(Saturation)가 답하는 질문으로 가장 알맞은 것은?",
    "option1": "\"무슨 색이야?\"",
    "option2": "\"얼마나 밝아?\"",
    "option3": "\"얼마나 쨍해?\"",
    "option4": "\"얼마나 투명해?\"",
    "answerIndex": 2,
    "explanation": "섹션 1의 HSL 좌표에서 채도(S)는 0%(회색)부터 100%(쨍함)까지 색이 얼마나 쨍한지를 나타냅니다. \"무슨 색이야?\"는 색상(H), \"얼마나 밝아?\"는 명도(L)의 질문이에요."
  },
  {
    "courseSlug": "color",
    "question": "60-30-10 법칙에서 포인트색인 브랜드 블루 #2159C5를 쓰기에 알맞은 곳은?",
    "option1": "배경과 카드 같은 넓은 바탕",
    "option2": "버튼·링크 같은 강조 요소",
    "option3": "본문 텍스트와 경계선 전체",
    "option4": "아이콘을 포함한 화면의 절반",
    "answerIndex": 1,
    "explanation": "섹션 3에서 포인트색은 화면의 10%만 차지하는 '주인공'으로, 버튼·링크·강조에 씁니다. 파랑이 귀하니까(희소성) 눈에 띄는 것이고, 60%를 차지하면 아무것도 강조되지 않아요."
  },
  {
    "courseSlug": "color",
    "question": "색상환에서 보색(Complementary)에 대한 설명으로 옳은 것은?",
    "option1": "정반대(180° 차이)의 색으로, 강조가 필요한 한 곳에만 쓴다",
    "option2": "이웃(±30° 안팎)의 색으로, 배경 같은 넓은 면적에 어울린다",
    "option3": "같은 색상에서 명도만 바꾼 색으로, 다크모드 전환에 쓴다",
    "option4": "채도가 같은 색끼리의 묶음으로, 그래프 계열색에 쓴다",
    "answerIndex": 0,
    "explanation": "섹션 2에서 보색은 색상환에서 정반대(180° 차이)에 있는 색으로 대비가 강렬해 강조·경고에 딱이라고 배웁니다. 이웃한 색은 유사색으로, 넓은 면적에 어울려요. 암기법은 \"싸울 땐 맞은편, 친할 땐 옆자리\"."
  },
  {
    "courseSlug": "color",
    "question": "WCAG 기준으로 본문 텍스트가 통과해야 하는 최소 대비비(AA 등급)는?",
    "option1": "3 : 1 이상",
    "option2": "1.8 : 1 이상",
    "option3": "21 : 1 이상",
    "option4": "4.5 : 1 이상",
    "answerIndex": 3,
    "explanation": "섹션 6에서 WCAG AA 등급의 본문 텍스트 최소 기준은 4.5:1입니다. 3:1은 큰 텍스트(18pt+)의 기준이고, 흰 배경 위 #2159C5는 약 6.3:1로 본문 합격이에요."
  },
  {
    "courseSlug": "color",
    "question": "다크모드 화면에서 브랜드 블루 #2159C5 버튼이 어두운 배경에 묻혀 잘 안 보인다. 코스의 원칙대로 고치는 방법은?",
    "option1": "채도를 100%로 올려 형광처럼 쨍하게 만든다",
    "option2": "눈에 잘 띄는 주황 같은 다른 색상으로 교체한다",
    "option3": "색상(H)은 그대로 두고 명도(L)만 올린 밝은 파랑을 쓴다",
    "option4": "버튼 글자를 순수 흰색 #FFFFFF로 바꿔 번쩍이게 한다",
    "answerIndex": 2,
    "explanation": "섹션 7에서 다크모드용 파랑은 색상(H)을 유지한 채 명도(L)만 올린 색입니다. 색상까지 바꾸면 '다른 브랜드'가 되니 정체성은 지키고 조명 대응만 하는 것 — \"같은 사람인데 무대 조명에 맞춰 옷만 갈아입는\" 비유 그대로예요."
  },
  {
    "courseSlug": "cpu-deep",
    "question": "CPU가 명령어 하나를 처리할 때 반복하는 명령어 사이클의 올바른 순서는 무엇인가요?",
    "option1": "해석(Decode) → 가져오기(Fetch) → 실행(Execute)",
    "option2": "실행(Execute) → 해석(Decode) → 가져오기(Fetch)",
    "option3": "가져오기(Fetch) → 해석(Decode) → 실행(Execute)",
    "option4": "가져오기(Fetch) → 실행(Execute) → 해석(Decode)",
    "answerIndex": 2,
    "explanation": "섹션 1의 명령어 사이클입니다. CPU는 메모리에서 명령어를 가져오고(Fetch), 무슨 뜻인지 해석한 뒤(Decode), 실행하는(Execute) 세 박자를 1초에 수십억 번 반복해요."
  },
  {
    "courseSlug": "cpu-deep",
    "question": "코스의 주방 비유에서 '코어(Core)'와 '클럭(Clock)'은 각각 무엇에 해당하나요?",
    "option1": "코어는 요리사 수, 클럭은 손 놀리는 속도",
    "option2": "코어는 도마 옆 재료 선반, 클럭은 요리사 수",
    "option3": "코어는 손 놀리는 속도, 클럭은 요리사의 손 놀림",
    "option4": "코어는 주문 창구 수, 클럭은 냉장고까지의 거리",
    "answerIndex": 0,
    "explanation": "섹션 2의 주방 비유예요. 코어는 주방의 요리사 수(8코어 = 요리사 8명), 클럭은 손 놀리는 속도(4.5GHz = 1초에 45억 박자)에 해당합니다. 재료 선반은 캐시, 냉장고는 RAM이었죠."
  },
  {
    "courseSlug": "cpu-deep",
    "question": "캐시 3층 구조(L1~L3)에 대한 설명으로 옳은 것은 무엇인가요?",
    "option1": "L3가 코어에서 가장 가깝고, 셋 중 가장 빠르다",
    "option2": "L1은 코어들이 공유하는 큰 선반이라 용량이 가장 크다",
    "option3": "CPU는 데이터를 RAM에서 먼저 찾고 없으면 L1으로 내려간다",
    "option4": "코어에서 가까울수록 작고 빠르며, 멀수록 크고 느리다",
    "answerIndex": 3,
    "explanation": "섹션 3의 층수 규칙입니다. L1은 손 안의 재료처럼 가장 작고 빠르고, L3는 코어들이 공유하는 큰 선반이에요. CPU는 L1 → L2 → L3 → RAM 순서로 찾아봅니다."
  },
  {
    "courseSlug": "cpu-deep",
    "question": "CPU 온도가 90℃를 넘었을 때 일어나는 '스로틀링'에 대한 설명으로 옳은 것은 무엇인가요?",
    "option1": "CPU가 고장 나기 직전이라는 신호이므로 즉시 교체해야 한다",
    "option2": "CPU가 타 버리는 걸 막으려고 스스로 클럭을 낮추는 자기 보호 동작이다",
    "option3": "쿨링 팬이 멈추면서 CPU가 강제로 전원을 차단하는 현상이다",
    "option4": "온도를 낮추기 위해 CPU가 클럭을 오히려 더 높여서 일을 빨리 끝내는 기능이다",
    "answerIndex": 1,
    "explanation": "섹션 4의 스로틀링입니다. 마라톤 선수가 무리가 오면 스스로 페이스를 낮추듯, CPU가 클럭을 낮춰(예: 4.5 → 3.0GHz) 타 버리는 걸 막는 정상적인 자기 보호 동작이에요. 고장이 아닙니다."
  },
  {
    "courseSlug": "cpu-deep",
    "question": "친구가 \"내 컴퓨터는 i7인데 최신 i5를 쓰는 네 컴퓨터보다 왜 느리지?\"라고 물었습니다. 코스에서 배운 내용으로 가장 알맞은 대답은 무엇인가요?",
    "option1": "i7이 i5보다 항상 빠르니까 다른 부품이 고장 난 게 분명하다",
    "option2": "클럭(GHz) 숫자만 비교하면 되고, 등급 숫자는 성능과 관계없다",
    "option3": "등급만큼 세대가 중요해서, 최신 세대 i5가 구형 i7을 이기는 건 흔한 일이다",
    "option4": "인텔과 라이젠은 이름 문법이 달라서 두 CPU는 애초에 비교할 수 없다",
    "answerIndex": 2,
    "explanation": "섹션 5의 중고차 비유를 적용한 문제예요. 체급(그랜저냐 아반떼냐)만큼 연식(세대)이 중요해서, 최신 연식 중형차가 10년 된 대형차보다 잘 달리듯 최신 세대 i5가 구형 i7을 이기는 일은 아주 흔합니다."
  },
  {
    "courseSlug": "data-modeling",
    "question": "이 코스가 '미리 보는 핵심 문장'으로 제시한, 코스 전체를 한 줄로 줄인 문장은 무엇인가요?",
    "option1": "테이블은 무조건 잘게 쪼갤수록 좋은 설계다",
    "option2": "모든 데이터는 하나의 테이블에 모아서 관리한다",
    "option3": "같은 사실은 딱 한 곳에만 적고, 필요한 곳에서는 번호(FK)로 가리킨다",
    "option4": "관계는 종류와 상관없이 항상 중간 테이블로 표현한다",
    "answerIndex": 2,
    "explanation": "섹션 1의 팁에서 코스 전체를 한 줄로 줄인 문장으로 \"같은 사실은 딱 한 곳에만 적고, 필요한 곳에서는 번호(FK)로 가리킨다\"를 제시합니다. 무조건 쪼개는 것은 오히려 섹션 5에서 경계한 내용이에요."
  },
  {
    "courseSlug": "data-modeling",
    "question": "코스에서 기본키(PK)를 설명할 때 사용한 비유로 알맞은 것은 무엇인가요?",
    "option1": "이름 — 누구나 알아보기 쉬운 값",
    "option2": "학번 — 학교 안에서 절대 겹치지 않는 번호",
    "option3": "지도 — 테이블 전체의 구조를 보여 주는 것",
    "option4": "다리 — 두 테이블을 이어 주는 연결값",
    "answerIndex": 1,
    "explanation": "섹션 3에서 기본키는 '학번'에 비유됩니다. 이름은 동명이인이 있을 수 있지만 학번은 절대 겹치지 않아 모든 줄을 유일하게 찍어낼 수 있기 때문이에요. 지도는 ERD, 다리는 중간 테이블의 비유입니다."
  },
  {
    "courseSlug": "data-modeling",
    "question": "1:N 관계(예: 멘토 1명이 수습 여러 명을 담당)를 테이블로 표현할 때, 코스가 알려 준 규칙으로 옳은 것은 무엇인가요?",
    "option1": "항상 N쪽(여럿 쪽) 테이블에 FK 한 칸을 둔다",
    "option2": "항상 1쪽 테이블에 담당 대상 칸을 개수만큼 늘려 간다",
    "option3": "두 테이블 사이에 반드시 중간 테이블을 새로 만든다",
    "option4": "양쪽 테이블 모두에 서로를 가리키는 FK를 하나씩 둔다",
    "answerIndex": 0,
    "explanation": "섹션 4의 규칙은 \"1:N에서는 항상 N쪽(여럿 쪽)에 FK를 둔다\"입니다. 1쪽에 '담당수습1, 담당수습2...' 칸을 늘리는 것은 섹션 6의 나쁜 설계 냄새 2에 해당하고, 중간 테이블은 N:M 관계에서 필요해요."
  },
  {
    "courseSlug": "data-modeling",
    "question": "코스에서 이메일을 기본키(PK)로 쓰지 말라고 한 핵심 이유는 무엇인가요?",
    "option1": "이메일은 문자열이라 숫자보다 검색 속도가 크게 느리기 때문",
    "option2": "이메일은 동명이인처럼 여러 사람이 겹칠 수 있는 값이기 때문",
    "option3": "이메일은 민감한 개인정보라 테이블에 저장하면 안 되기 때문",
    "option4": "이메일은 바뀔 수 있는 값이라, 바뀌면 그걸 가리키던 FK가 모두 흔들리기 때문",
    "answerIndex": 3,
    "explanation": "섹션 3의 경고 박스에서 이메일은 유일하긴 하지만 '바뀔 수 있는 값'이라 PK가 바뀌면 그걸 가리키던 모든 FK가 함께 흔들린다고 설명합니다. 그래서 PK는 의미 없는 번호(id)로 두는 것이 정석이에요."
  },
  {
    "courseSlug": "data-modeling",
    "question": "실습에서 '쪽지 기능'을 설계하던 수습이 한 쪽지를 여러 명에게 보내는 단체 쪽지까지 지원하기로 했습니다. 코스에서 배운 대로라면 가장 알맞은 설계는 무엇인가요?",
    "option1": "쪽지 테이블에 받는사람1, 받는사람2, 받는사람3 칸을 추가한다",
    "option2": "받는 사람들의 이메일을 쉼표로 이어 붙여 한 칸에 저장한다",
    "option3": "쪽지와 users 사이에 수신자 중간 테이블을 만들어 N:M로 잇는다",
    "option4": "받는 사람 수만큼 쪽지 제목과 내용을 통째로 복사해 저장한다",
    "answerIndex": 2,
    "explanation": "섹션 7의 '흔한 함정 미리보기'에서 단체 쪽지(한 쪽지 → 여러 수신자)는 N:M 관계이므로 섹션 4의 중간 테이블이 답이라고 알려 줍니다. 번호 붙은 칸의 행진과 한 칸에 여러 값은 섹션 6의 나쁜 설계 냄새예요."
  },
  {
    "courseSlug": "data-structures",
    "question": "코스에서 브라우저의 '뒤로가기' 버튼과 똑같은 방식으로 동작한다고 소개한 자료구조는 무엇인가요?",
    "option1": "큐 — 먼저 방문한 페이지부터 꺼낸다",
    "option2": "배열 — 방문한 순서대로 번호를 붙인다",
    "option3": "스택 — 나중에 쌓은 페이지부터 꺼낸다",
    "option4": "트리 — 페이지를 부모-자식으로 잇는다",
    "answerIndex": 2,
    "explanation": "스택은 접시 탑처럼 맨 위에 쌓고 맨 위부터 꺼내는 LIFO(Last In, First Out) 구조입니다. 코스 섹션 3에서 브라우저 뒤로가기, Ctrl+Z, 콜 스택을 모두 스택의 예로 들었습니다."
  },
  {
    "courseSlug": "data-structures",
    "question": "해시맵(Map)이 배열과 달리 값을 '한 번에' 찾을 수 있는 이유로 코스가 설명한 것은 무엇인가요?",
    "option1": "데이터를 항상 이름 순서로 정렬해 두기 때문에",
    "option2": "해시 함수가 열쇠(key)로 보관 위치를 바로 계산해 주기 때문에",
    "option3": "자주 찾는 값을 맨 앞 칸으로 미리 옮겨 두기 때문에",
    "option4": "같은 데이터를 여러 칸에 복사해서 나눠 담아 두기 때문에",
    "answerIndex": 1,
    "explanation": "섹션 4의 사물함 비유처럼, 해시 함수는 열쇠를 넣으면 보관 위치가 계산되어 나오는 기계입니다. 저장할 때와 찾을 때 같은 계산을 하므로 뒤지지 않고 바로 그 칸으로 직행합니다."
  },
  {
    "courseSlug": "data-structures",
    "question": "자바스크립트 배열로 스택과 큐를 흉내낼 때, 코스에서 설명한 메서드 조합을 바르게 짝지은 것은 무엇인가요?",
    "option1": "스택은 push와 shift, 큐는 push와 pop을 쓴다",
    "option2": "스택은 pop과 shift, 큐는 push와 at을 쓴다",
    "option3": "스택은 shift와 pop, 큐는 pop과 push를 쓴다",
    "option4": "스택은 push와 pop, 큐는 push와 shift를 쓴다",
    "answerIndex": 3,
    "explanation": "두 그릇 모두 넣을 때는 push로 같고, 꺼내는 쪽만 다릅니다. 스택은 뒤에서 꺼내는 pop(LIFO), 큐는 앞에서 꺼내는 shift(FIFO)를 쓴다고 섹션 3에서 정리했습니다."
  },
  {
    "courseSlug": "data-structures",
    "question": "코스의 시간복잡도 설명에 따르면, O(n²)인 코드에서 데이터가 10배로 늘어나면 해야 하는 일의 양은 대략 몇 배가 되나요?",
    "option1": "약 100배로 늘어난다",
    "option2": "약 10배로 늘어난다",
    "option3": "약 2배로 늘어난다",
    "option4": "거의 그대로 유지된다",
    "answerIndex": 0,
    "explanation": "섹션 6의 '반 전체 악수' 비유처럼 O(n²)는 데이터가 10배면 일은 100배가 됩니다(30명 약 450번, 300명 약 45,000번). 그래서 반복문 안의 반복문이 데이터가 커질 때 화면을 버벅이게 만듭니다."
  },
  {
    "courseSlug": "data-structures",
    "question": "학교 매점 프로그램을 만든다고 해 봅시다. 주문이 들어온 순서 그대로, 먼저 주문한 학생부터 빵을 받아야 합니다. 코스의 비유에 따르면 이 주문 목록에 가장 어울리는 그릇은 무엇인가요?",
    "option1": "스택 — 접시 탑처럼 최근 주문부터 처리한다",
    "option2": "해시맵 — 사물함처럼 학생 이름으로 바로 찾는다",
    "option3": "큐 — 급식 줄처럼 먼저 선 사람이 먼저 받는다",
    "option4": "트리 — 폴더처럼 주문을 종류별 가지로 나눈다",
    "answerIndex": 2,
    "explanation": "먼저 온 주문이 먼저 처리되어야 하므로 FIFO(First In, First Out)인 큐가 맞습니다. 코스 섹션 3에서 큐를 '새치기 없는 급식 줄'에 비유하며 프린터 대기열, 서버의 요청 처리 줄을 예로 들었습니다."
  },
  {
    "courseSlug": "debugging",
    "question": "코스에서는 에러 메시지를 '사건 보고서'에 비유하며 세 가지 질문에 답해 준다고 했다. 다음 중 그 세 가지에 해당하지 않는 것은?",
    "option1": "무슨 일이야? — 에러의 종류 (NullPointerException, TypeError 등)",
    "option2": "왜 그랬는데? — 에러 옆의 설명 문장 (\"... is null\" 등)",
    "option3": "어디서? — 파일 이름과 줄 번호 (UserService.java:42 등)",
    "option4": "어떻게 고쳐? — 에러를 해결할 수정 코드 제안",
    "answerIndex": 3,
    "explanation": "섹션 1 '에러 = 단서'에서 에러 메시지는 종류(무슨 일)·설명 문장(왜)·파일과 줄 번호(어디서) 세 가지에 답해 준다고 했다. 고치는 방법까지 알려 주지는 않으며, 이 셋만 읽어도 문제의 절반은 푼 것이라고 설명한다."
  },
  {
    "courseSlug": "debugging",
    "question": "Spring Boot 콘솔에 수십 줄짜리 스택트레이스가 여러 덩어리로 이어져 나올 때, 코스가 알려 준 '진짜 원인'을 찾는 치트키는?",
    "option1": "맨 위의 NestedServletException을 자세히 분석한다",
    "option2": "Ctrl+F로 검색해 가장 아래쪽 \"Caused by\"를 찾는다",
    "option3": "org.springframework로 시작하는 줄부터 차례로 읽는다",
    "option4": "\"... 47 more\"로 접힌 부분을 모두 펼쳐서 읽는다",
    "answerIndex": 1,
    "explanation": "섹션 2에서 에러는 양파처럼 겹겹이 포장돼 올라오며, 맨 위 예외는 겉포장(증상)이고 진범은 거의 항상 가장 아래쪽 Caused by에 있다고 했다. 그래서 긴 로그에서는 Ctrl+F로 Caused by부터 검색하라고 안내한다."
  },
  {
    "courseSlug": "debugging",
    "question": "의심 가는 함수 안에 라벨을 붙여 console.log를 찍었는데, 콘솔에 그 로그가 아예 나타나지 않았다. 코스에 따르면 이 상황의 올바른 해석은?",
    "option1": "그 함수 자체가 호출되지 않았다는 뜻으로, 용의자 절반을 지워 준 중요한 단서다",
    "option2": "로그 출력이 실패한 것이므로 console.log를 다른 문법으로 다시 찍어야 한다",
    "option3": "변수 값이 undefined라서 로그가 출력되지 못한 것이다",
    "option4": "브라우저 캐시 문제이므로 시크릿 창에서 다시 확인해야 한다",
    "answerIndex": 0,
    "explanation": "섹션 4의 주의 박스에서 로그가 아예 안 찍히는 것도 중요한 단서라고 했다. '값이 이상하다'가 아니라 '이 함수 자체가 호출되지 않는다'는 뜻이며, 실패가 아니라 용의자 절반을 지워 준 수확이라고 설명한다."
  },
  {
    "courseSlug": "debugging",
    "question": "브라우저 F12 콘솔에 \"500 Internal Server Error\"가 떠 있다. 코스에서 배운 '프론트 문제인지 서버 문제인지 가르는 감각'에 따르면 다음으로 열어 봐야 할 곳은?",
    "option1": "React 컴포넌트의 JSX에서 오타가 없는지 코드를 다시 읽는다",
    "option2": "브라우저 콘솔에서 TypeError가 함께 떴는지 계속 지켜본다",
    "option3": "백엔드(Spring Boot) 콘솔을 열어 스택트레이스를 확인한다",
    "option4": "새로고침한 뒤 다른 브라우저에서도 같은 화면을 띄워 본다",
    "answerIndex": 2,
    "explanation": "섹션 3에서 TypeError·ReferenceError는 내 JS 코드 문제지만 404·500 같은 숫자는 서버와의 통신 문제라고 했다. 500은 서버 쪽에서 터진 것이므로 브라우저가 아니라 Spring Boot 콘솔의 스택트레이스를 봐야 한다."
  },
  {
    "courseSlug": "debugging",
    "question": "버튼을 누르면 화면이 하얗게 죽는다. 처리 흐름은 A(이벤트 핸들러) → B(fetch 호출) → C(응답 가공) → D(화면 그리기)이고, B와 C 사이에 찍어 둔 로그가 콘솔에 정상적으로 나타났다. 코스의 이분 탐색 요령대로라면 다음 한 수는?",
    "option1": "A의 첫 줄에 로그를 찍어 이벤트가 들어오는지부터 다시 확인한다",
    "option2": "범인을 C·D로 좁혔으니 그 남은 절반의 가운데에 로그를 찍는다",
    "option3": "A부터 D까지 모든 줄에 로그를 찍어 전체 흐름을 한 번에 본다",
    "option4": "fetch 호출(B)의 코드를 처음부터 한 줄씩 자세히 읽어 본다",
    "answerIndex": 1,
    "explanation": "섹션 6의 이분 탐색에서 가운데 검문소(로그)를 통과했으면 앞 구간(A·B)은 무죄이고 범인은 C·D 중에 있다고 했다. 이후에도 남은 절반의 가운데에 또 로그를 찍어 용의자를 반씩 줄여 가는 것이 요령이다."
  },
  {
    "courseSlug": "design-refs",
    "question": "코스에서는 3대 레퍼런스 사이트를 각각 비유로 소개해요. '미술관 전시장'에 비유되며, 완성도 높은 '한 장면'을 보기 좋지만 실제 서비스가 아닌 컨셉 작업이 많은 곳은 어디인가요?",
    "option1": "비핸스",
    "option2": "드리블",
    "option3": "핀터레스트",
    "option4": "피그마",
    "answerIndex": 1,
    "explanation": "섹션 2에서 드리블은 '미술관 전시장'으로, 색·타이포·분위기 같은 한 장면의 완성도를 보기 좋지만 보여주기용 컨셉이 많다고 설명해요. 비핸스는 발표회장, 핀터레스트는 스크랩북에 비유됩니다."
  },
  {
    "courseSlug": "design-refs",
    "question": "피그마 무드보드 만들기 4단계 중, 코스가 '이게 무드보드의 절반'이라고 할 만큼 중요하다고 강조한 단계는 무엇인가요?",
    "option1": "새 파일을 만들고 이름 짓기",
    "option2": "스크린샷을 캔버스에 붙여넣기",
    "option3": "이미지 옆에 태그 메모 붙이기",
    "option4": "비슷한 것끼리 프레임으로 묶기",
    "answerIndex": 2,
    "explanation": "섹션 4에서 가장 중요한 건 ③ 태그라고 말해요. 태그 없는 무드보드는 제목 없는 도서관 같아서 100장을 모아도 필요할 때 못 찾기 때문에, 저장하는 순간 한 단어라도 붙이는 습관이 무드보드의 절반이라고 강조합니다."
  },
  {
    "courseSlug": "design-refs",
    "question": "코스에서 '베끼기'와 '참고'를 스스로 구분해 보라며 제시한 판별 테스트 질문은 무엇인가요?",
    "option1": "원본을 안 보고도 내 디자인을 다시 그릴 수 있는가?",
    "option2": "결과물에 원본의 출처 URL을 표기했는가?",
    "option3": "레퍼런스를 세 개 이상 섞어서 참고했는가?",
    "option4": "색과 폰트를 원본과 다르게 바꿨는가?",
    "answerIndex": 0,
    "explanation": "섹션 3의 판별 테스트는 '원본을 안 보고도 다시 그릴 수 있는가'예요. YES면 원리를 이해한 참고, NO면 그림을 외운 것이라 베끼기에 가깝다고 설명하며, 출처 표기는 사용 허락이 아니라고 못 박습니다."
  },
  {
    "courseSlug": "design-refs",
    "question": "'나만의 수집 루틴' 섹션에서 디자인 실력이 자라는 공식으로 제시한 것은 무엇인가요?",
    "option1": "하루 스크롤 시간 × 저장한 이미지 수",
    "option2": "팔로우한 디자이너 수 × 활동 기간",
    "option3": "무드보드 페이지 수 × 붙인 태그 수",
    "option4": "많이 본 총량 × 분석한 횟수",
    "answerIndex": 3,
    "explanation": "섹션 6의 루틴 다이어그램은 실력이 '많이 본 총량 × 분석한 횟수'로 자란다고 말해요. 모으기만 하고 분석하지 않으면 그냥 스크랩 중독이라고 경고합니다."
  },
  {
    "courseSlug": "design-refs",
    "question": "수습생 지민이는 드리블에서 본 학습 앱 화면을 밑에 깔고 그대로 따라 그린 뒤, 이미지 아래에 원본 출처 URL을 적어 포트폴리오에 넣었어요. 코스의 기준으로 가장 옳은 판단은 무엇인가요?",
    "option1": "출처를 밝혔으므로 문제없는 '참고'에 해당한다",
    "option2": "연습 삼아 그린 것이므로 포트폴리오에 넣어도 괜찮다",
    "option3": "출처 표기는 사용 허락이 아니므로, 트레이싱을 포트폴리오에 쓰면 안 된다",
    "option4": "색과 문구만 바꾸면 저작권 문제가 사라지므로 수정 후 올리면 된다",
    "answerIndex": 2,
    "explanation": "섹션 3에서 트레이싱은 연습장 안에서만 허용되고 제출물·포트폴리오·실제 서비스에 쓰는 순간 문제가 된다고 해요. 또 '출처 표기 ≠ 사용 허락'이므로 URL을 적었다고 베끼기가 참고가 되지는 않습니다."
  },
  {
    "courseSlug": "design-system",
    "question": "코스에서는 디자인 시스템을 색·글꼴·간격·부품·사용 규칙을 모아 둔 것이라고 소개합니다. 이를 한마디로 무엇에 비유했나요?",
    "option1": "제품의 설계 도면",
    "option2": "제품의 부품 창고",
    "option3": "제품의 문법책",
    "option4": "제품의 사용 설명 영상",
    "answerIndex": 2,
    "explanation": "섹션 1에서 디자인 시스템을 '제품의 문법책'에 비유합니다. 문법이 있어야 여러 사람이 써도 한 사람이 쓴 글처럼 읽히듯, 약속이 있어야 열 명이 만들어도 한 사람이 만든 화면처럼 보이기 때문이에요."
  },
  {
    "courseSlug": "design-system",
    "question": "코스에서 말하는 '디자인 토큰(token)'의 뜻으로 가장 알맞은 것은 무엇인가요?",
    "option1": "색·간격·글꼴 같은 디자인 결정에 이름을 붙인 변수",
    "option2": "피그마 파일에 접근할 때 쓰는 인증 키",
    "option3": "컴포넌트의 버전을 관리하는 고유 번호",
    "option4": "버튼의 상태를 저장해 두는 자바스크립트 객체",
    "answerIndex": 0,
    "explanation": "섹션 2에서 토큰은 '디자인 결정에 이름을 붙인 변수'라고 정의합니다. #3B82F6 같은 값 대신 --color-primary라는 이름으로 부르기로 약속하는 것으로, global.css의 :root에 실물이 살고 있어요."
  },
  {
    "courseSlug": "design-system",
    "question": "피그마 컴포넌트와 React 컴포넌트의 짝이 어긋나기 시작하는 것을 막기 위해 코스가 제시한 규칙은 무엇인가요?",
    "option1": "부품 변경은 디자이너가 먼저 끝낸 뒤 개발자에게 문서로 전달한다",
    "option2": "React props 이름은 피그마 variant와 다르게 지어 역할을 구분한다",
    "option3": "variant 추가는 분기별로 모아 두었다가 한꺼번에 코드에 반영한다",
    "option4": "부품을 바꿀 땐 피그마와 코드를 같은 PR·같은 티켓에서 함께 바꾼다",
    "answerIndex": 3,
    "explanation": "섹션 4의 경고 박스에서, 피그마에만 variant가 추가되고 코드에는 없으면 두 세계가 갈라지기 시작하므로 '피그마와 코드를 같은 PR·같은 티켓에서 함께 바꾸는 것'이 규칙이라고 설명합니다."
  },
  {
    "courseSlug": "design-system",
    "question": "코스에서 소개한 '좋은 컴포넌트 문서에 꼭 들어가는 4가지'에 해당하지 않는 것은 무엇인가요?",
    "option1": "언제 쓰는 부품인지 알려 주는 When",
    "option2": "누가 언제 만들었는지 적는 제작자·날짜",
    "option3": "하지 말아야 할 사용법을 적는 Don't",
    "option4": "복사해서 바로 쓰는 예제 코드인 How",
    "answerIndex": 1,
    "explanation": "섹션 5에서 좋은 문서의 4요소는 When(언제 쓰나), How(예제 코드), Don't(하지 말 것), 그리고 상태·변형 전체 목록입니다. 제작자와 날짜는 이 목록에 없어요."
  },
  {
    "courseSlug": "design-system",
    "question": "수습 A가 과제 제출 버튼을 눌렀는데, 서버가 처리하는 동안에도 버튼이 그대로 눌려서 과제가 두 번 제출되는 사고가 났습니다. 버튼의 다섯 상태 중 어떤 설계가 빠진 걸까요?",
    "option1": "hover — 마우스를 올리면 살짝 어두워지는 상태",
    "option2": "loading — 스피너를 보여 주고 다시 못 누르게 잠그는 상태",
    "option3": "active — 누르는 순간 꺼진 느낌을 주는 상태",
    "option4": "default — 평소의 또렷한 기본 모습",
    "answerIndex": 1,
    "explanation": "섹션 3의 상태표에서 loading은 '처리 중(제출 직후 등)에 스피너를 띄우고 다시 못 누르게 잠그는 상태'입니다. 이 상태가 빠지면 제출 직후 버튼이 두 번 눌려 과제가 중복 제출되는 사고로 이어져요."
  },
  {
    "courseSlug": "dns-deep",
    "question": "재귀 조회의 여행에서 도메인의 진짜 IP를 최종적으로 답해 주는 서버는 무엇인가요?",
    "option1": "루트 서버 — 여행의 출발점으로 전 세계 도메인을 다 외우고 있다",
    "option2": "TLD 서버 — .com 같은 최상위 도메인을 관리하는 안내소이므로",
    "option3": "리졸버 — 내 PC 대신 뛰어다니는 심부름꾼이 답을 만들어 낸다",
    "option4": "권한(Authoritative) 서버 — 그 도메인의 진짜 명부를 가진 최종 담당자",
    "answerIndex": 3,
    "explanation": "섹션 1의 여행에서 루트와 TLD 서버는 \"담당자는 저쪽\"이라고 안내만 하고, 실제 IP는 도메인의 명부를 가진 권한 서버가 답합니다. 우리 도메인은 Route53이 그 역할이에요."
  },
  {
    "courseSlug": "dns-deep",
    "question": "DNS 레코드 타입 중 CNAME에 대한 설명으로 옳은 것은 무엇인가요?",
    "option1": "이름을 IPv4 주소로 바꿔 주는, 여행이 끝나는 종착역 레코드다",
    "option2": "이 도메인의 메일이 어디로 가야 하는지 알려 주는 레코드다",
    "option3": "이름을 다른 이름으로 연결하는 별명 레코드로, 사슬 끝에는 A 레코드가 필요하다",
    "option4": "도메인 소유 확인이나 스팸 방지 정책 같은 증명 문자열을 담는 레코드다",
    "answerIndex": 2,
    "explanation": "섹션 2에서 CNAME은 \"이름 → 다른 이름\"인 별명이라 한 정거장 더 가야 하고, 사슬의 맨 끝에는 반드시 A 레코드가 있어야 실제 IP에 도착한다고 배웠어요."
  },
  {
    "courseSlug": "dns-deep",
    "question": "TTL을 길게(예: 86400 = 하루) 설정하면 어떤 일이 생기나요?",
    "option1": "DNS 질문이 줄어 빠르고 서버 부담이 적지만, IP를 바꿔도 최대 하루 동안 옛 주소로 가는 사람이 남는다",
    "option2": "변경이 금방 퍼지는 대신 질문이 잦아져서 Route53 과금이 늘어난다",
    "option3": "리졸버가 캐시를 아예 쓰지 않게 되어 매번 풀코스 여행을 다녀와야 한다",
    "option4": "권한 서버가 하루에 한 번만 레코드를 갱신할 수 있도록 제한된다",
    "answerIndex": 0,
    "explanation": "섹션 3에서 TTL은 \"이 답을 몇 초 동안 믿고 재사용해도 되는지\"를 뜻하며, 길면 질문이 줄어 빠르지만 변경 사항이 캐시 만료 전까지 퍼지지 않는다고 배웠어요."
  },
  {
    "courseSlug": "dns-deep",
    "question": "hosts 파일에 적은 이름은 왜 DNS 서버 설정과 상관없이 항상 이길까요?",
    "option1": "hosts 파일의 내용이 리졸버의 캐시를 강제로 덮어써서 바꾸기 때문이다",
    "option2": "OS가 리졸버(DNS)에 물어보기 전에 hosts 파일을 먼저 확인하고, 답을 찾으면 뒤에는 묻지 않기 때문이다",
    "option3": "hosts 파일에 적은 내용이 루트 서버의 명부에 자동으로 등록되기 때문이다",
    "option4": "관리자 권한으로 저장한 파일은 네트워크 설정보다 항상 우선 적용되기 때문이다",
    "answerIndex": 1,
    "explanation": "섹션 4에서 이름 → IP 변환은 브라우저 캐시 → OS 캐시 → hosts 파일 → 리졸버 순서로 진행되고, 먼저 답을 찾으면 뒤에는 안 물어본다고 배웠어요."
  },
  {
    "courseSlug": "dns-deep",
    "question": "서버 IP를 바꾼 직후 사무실 PC에서는 새 사이트가 열리는데, 친구 폰에서는 아직 옛 주소로 접속됩니다. 코스 내용에 따른 가장 알맞은 진단은 무엇인가요?",
    "option1": "서버 고장이므로 Route53의 레코드를 삭제하고 처음부터 다시 만들어야 한다",
    "option2": "친구 폰의 hosts 파일이 조작된 것이므로 즉시 파일을 초기화해야 한다",
    "option3": "친구 쪽 통신사 리졸버가 옛 답을 TTL 동안 캐시 중인 것으로, 캐시가 만료되면 자연히 해결된다",
    "option4": "두 기기가 서로 다른 권한 서버를 쓰고 있으므로 통신사를 바꿔야 한다",
    "answerIndex": 2,
    "explanation": "섹션 3의 우리 회사 실화처럼 \"DNS 전파가 느리다\"의 정체는 세계 곳곳의 리졸버 캐시가 각자 만료되기를 기다리는 것이라, 고장이 아니며 기다리면 풀립니다."
  },
  {
    "courseSlug": "docker-intro",
    "question": "이 코스는 이미지와 컨테이너를 붕어빵에 비유해 설명합니다. 두 개념의 관계로 옳은 것은 무엇인가요?",
    "option1": "이미지는 실행 중인 프로그램이고, 컨테이너는 그 프로그램의 설계도다",
    "option2": "이미지와 컨테이너는 같은 것을 가리키는 두 가지 다른 이름이다",
    "option3": "이미지는 붕어빵틀처럼 읽기 전용 설계도이고, 컨테이너는 그 틀로 찍어낸 실행 실체다",
    "option4": "이미지는 컨테이너가 종료된 뒤에 남는 실행 기록(로그)을 모아 둔 것이다",
    "answerIndex": 2,
    "explanation": "섹션 2의 붕어빵 비유 그대로, 이미지는 실행에 필요한 모든 것을 구워 놓은 읽기 전용 틀이고 컨테이너는 그 이미지를 실행한 실체입니다. 틀 하나(postgres:17)로 여러 개의 컨테이너를 찍어낼 수 있어요."
  },
  {
    "courseSlug": "docker-intro",
    "question": "도커 5대 기본 명령 중, 실행 중인 컨테이너 '안으로 직접 들어갈' 때 쓰는 명령은 무엇인가요?",
    "option1": "docker ps -a — 멈춘 컨테이너까지 전부 확인한다",
    "option2": "docker exec -it <이름> bash — 컨테이너 안으로 들어간다",
    "option3": "docker logs -f <이름> — 로그를 실시간으로 따라가며 본다",
    "option4": "docker stop <이름> — 컨테이너를 정중하게 멈춘다",
    "answerIndex": 1,
    "explanation": "섹션 4에서 exec은 '문 열고 들어가기'로 기억하라고 했어요. 문제가 생기면 먼저 logs로 일기장을 읽고, 더 파야 할 때 exec으로 컨테이너 안에 직접 들어갑니다."
  },
  {
    "courseSlug": "docker-intro",
    "question": "실습에서 docker compose down으로 DB 컨테이너를 지웠다가 다시 띄웠는데, 어제 만든 hello 테이블과 데이터가 그대로 남아 있었습니다. 코스 내용에 따르면 그 이유는 무엇인가요?",
    "option1": "컨테이너를 삭제하면 도커가 내부 데이터를 자동으로 백업해 두기 때문",
    "option2": "postgres:17 이미지 자체에 사용자가 만든 데이터가 함께 저장되기 때문",
    "option3": "docker compose가 삭제 직전에 데이터를 Git 저장소로 옮겨 두기 때문",
    "option4": "데이터가 컨테이너 밖의 볼륨(practice-data)에 저장되어 컨테이너와 운명을 같이하지 않기 때문",
    "answerIndex": 3,
    "explanation": "섹션 6의 핵심입니다. 컨테이너는 지우면 안이 통째로 사라지는 일회용 도시락통이라, 데이터는 컨테이너 밖의 외장하드인 볼륨에 두어 컨테이너가 죽어도 살아남게 합니다."
  },
  {
    "courseSlug": "docker-intro",
    "question": "우리 docker-compose.yml에서 backend의 DB 주소가 IP가 아니라 jdbc:postgresql://db:5432/mirim처럼 그냥 'db'로 적혀 있습니다. 이게 동작하는 이유는 무엇인가요?",
    "option1": "compose로 띄운 컨테이너들은 같은 가상 네트워크에 들어가고, 서비스 이름이 그대로 도메인 이름이 되기 때문",
    "option2": "도커가 모든 컨테이너의 IP 주소를 항상 'db'라는 고정 별칭 하나로 통일해 주기 때문",
    "option3": "'db'는 호스트 PC의 localhost를 가리키도록 도커가 예약해 둔 특별한 키워드이기 때문",
    "option4": ".env 파일에 'db'라는 이름으로 실제 IP 주소가 미리 등록되어 주입되기 때문",
    "answerIndex": 0,
    "explanation": "섹션 5의 ⑪번 해설 내용입니다. compose가 만든 같은 가상 네트워크 안에서는 서비스 이름(db, backend)이 곧 도메인 이름이 되어, 네트워크 코스에서 배운 DNS가 컨테이너 세계 안에서도 작동하는 셈입니다."
  },
  {
    "courseSlug": "docker-intro",
    "question": "수습 개발자 A가 실습대로 docker compose up -d를 실행했더니 'port is already allocated' 에러가 났습니다. 알고 보니 A의 PC에는 로컬 PostgreSQL이 이미 5432번 포트를 쓰고 있었어요. 코스에서 배운 해결 방법은 무엇인가요?",
    "option1": "ports를 \"5432:15432\"로 바꿔 컨테이너 안의 포스트그레스가 다른 포트를 듣게 한다",
    "option2": "docker compose down -v로 볼륨까지 전부 지운 뒤 처음부터 다시 올린다",
    "option3": "ports를 \"15432:5432\"처럼 바깥쪽(호스트) 번호만 바꿔서 포트 충돌을 피한다",
    "option4": "depends_on 목록에 로컬 PostgreSQL을 추가해서 켜지는 순서를 맞춰 준다",
    "answerIndex": 2,
    "explanation": "포트 매핑은 '바깥:안쪽' 순서라서, 내 PC 쪽 포트가 이미 쓰이고 있으면 바깥 번호만 15432처럼 바꾸면 됩니다. 섹션 7의 회피 기술이자 섹션 8의 '흔한 막힘 포인트'로 소개된 내용이에요."
  },
  {
    "courseSlug": "figma",
    "question": "피그마 화면은 크게 세 부분으로 나뉩니다. 지금 선택한 요소의 크기·색·글꼴 같은 세부 정보를 보고 고치는 곳은 어디인가요?",
    "option1": "왼쪽 레이어 패널",
    "option2": "가운데 캔버스",
    "option3": "오른쪽 속성 패널",
    "option4": "상단 메뉴 바",
    "answerIndex": 2,
    "explanation": "섹션 2에서 배운 3분할 구조입니다. \"왼쪽에서 찾고, 가운데서 그리고, 오른쪽에서 다듬는다\" — 선택한 요소의 세부 속성은 오른쪽 속성 패널에서 고칩니다."
  },
  {
    "courseSlug": "figma",
    "question": "코스에서 \"섹션 4의 주인공\"이라고 소개한 단축키 Shift + A는 무엇을 하는 키인가요?",
    "option1": "선택한 것들을 그룹으로 묶는다",
    "option2": "선택한 것에 오토레이아웃을 씌운다",
    "option3": "방금 만든 요소를 하나 더 복제한다",
    "option4": "새 프레임(화면)을 하나 만든다",
    "answerIndex": 1,
    "explanation": "단축키 표에서 Shift+A는 오토레이아웃 씌우기입니다. 그룹 묶기는 Ctrl+G, 복제는 Ctrl+D, 프레임은 F가 맡습니다."
  },
  {
    "courseSlug": "figma",
    "question": "코스는 오토레이아웃을 \"피그마의 flexbox\"라고 설명하며 CSS와 짝지어 줍니다. 다음 중 옳게 짝지어진 것은 무엇인가요?",
    "option1": "간격(Spacing) ↔ gap",
    "option2": "패딩(Padding) ↔ flex-direction",
    "option3": "방향(가로/세로) ↔ justify-content",
    "option4": "정렬(Alignment) ↔ padding",
    "answerIndex": 0,
    "explanation": "섹션 4의 대응표에 따르면 간격(Spacing)은 CSS의 gap과 같은 개념입니다. 방향은 flex-direction, 패딩은 padding, 정렬은 justify-content/align-items와 짝을 이룹니다."
  },
  {
    "courseSlug": "figma",
    "question": "로그인 버튼을 Ctrl+Alt+K로 컴포넌트로 만들고 인스턴스를 여러 화면에 배치했습니다. 코스가 말하는 컴포넌트의 가장 큰 이점은 무엇인가요?",
    "option1": "인스턴스마다 서로 다른 색을 자동으로 입혀 준다",
    "option2": "버튼이 자동으로 CSS 코드 파일로 저장된다",
    "option3": "인스턴스를 복제할 때마다 이름이 자동으로 정리된다",
    "option4": "원본 하나만 고치면 모든 인스턴스가 한꺼번에 바뀐다",
    "answerIndex": 3,
    "explanation": "섹션 5의 \"마법 도장\" 비유처럼, 컴포넌트는 복사본(인스턴스)이 원본과 연결되어 원본만 수정하면 이미 찍힌 모든 자국이 함께 바뀝니다."
  },
  {
    "courseSlug": "figma",
    "question": "개발 수습생이 디자이너의 시안을 구현하다가 버튼의 정확한 파란색 색상 코드가 필요해졌습니다. 코스가 \"협업 매너이자 여러분의 속도\"라고 권한 행동은 무엇인가요?",
    "option1": "디자이너에게 바로 메시지를 보내 색상 값을 물어본다",
    "option2": "화면을 캡처해서 눈으로 가장 비슷한 색을 골라 쓴다",
    "option3": "우측 상단 </> 토글로 Dev Mode를 켜고 요소를 클릭해 값을 직접 읽는다",
    "option4": "버튼 이미지를 PNG로 내보내서 그대로 프로젝트에 붙여 넣는다",
    "answerIndex": 2,
    "explanation": "섹션 7 핸드오프에서 배운 내용입니다. Dev Mode를 켜고 요소를 클릭하면 색상 코드·글꼴·간격이 숫자와 CSS 형태로 표시되므로, 물어보기 전에 직접 읽는 것이 협업 매너입니다."
  },
  {
    "courseSlug": "files",
    "question": "코스에서는 파일시스템을 '디스크의 사서'에 비유합니다. 이 비유에서 파일시스템이 하는 핵심 역할은 무엇인가요?",
    "option1": "디스크의 0과 1을 사람이 읽을 수 있는 글자로 번역해 준다",
    "option2": "어떤 데이터가 디스크의 어느 구역에 있는지 장부(메타데이터)로 관리한다",
    "option3": "파일을 저장할 때마다 자동으로 압축해서 공간을 아껴 준다",
    "option4": "바이러스가 들어오지 못하게 디스크 입구를 지키는 보안 검사를 한다",
    "answerIndex": 1,
    "explanation": "섹션 1에서 디스크는 그냥 0과 1의 바다이고, 파일시스템(사서)이 '보고서.docx는 #4021부터'처럼 장부(메타데이터)를 얹어 관리한다고 배웠어요. 사서가 없으면 책(데이터)이 있어도 찾을 수 없습니다."
  },
  {
    "courseSlug": "files",
    "question": "photo.jpg 파일의 이름을 photo.txt로 바꾸면 어떤 일이 벌어지나요?",
    "option1": "내용물은 그대로이고, Windows가 여는 프로그램만 메모장으로 바뀐다",
    "option2": "파일 맨 앞의 매직 넘버가 텍스트용 바이트로 다시 쓰인다",
    "option3": "JPG 데이터가 손상되어 다시 .jpg로 바꿔도 열리지 않는다",
    "option4": "확장자와 내용을 맞추기 위해 사진이 자동으로 텍스트로 변환된다",
    "answerIndex": 0,
    "explanation": "섹션 2의 핵심: 확장자는 내용물이 아니라 이름표라서, 이름을 바꿔도 바이트는 1도 안 변해요. 단지 Windows가 어떤 프로그램으로 열지 판단하는 기준만 달라질 뿐이라 다시 .jpg로 되돌리면 멀쩡히 열립니다."
  },
  {
    "courseSlug": "files",
    "question": "현재 위치가 frontend/src/pages/ 폴더일 때, 코스 내용에 따르면 ../App.jsx 라는 상대 경로가 가리키는 것은?",
    "option1": "pages 폴더 안에 있는 App.jsx",
    "option2": "두 층 위로 올라간 frontend 폴더의 App.jsx",
    "option3": "한 층 올라간 부모 폴더(src)에 있는 App.jsx",
    "option4": "뿌리(루트)부터 시작하는 절대 경로라서 위치와 무관하다",
    "answerIndex": 2,
    "explanation": "섹션 3에서 ..은 부모 폴더, .은 현재 폴더를 뜻한다고 배웠어요. pages/에서 ../App.jsx는 한 층 올라간 src 폴더의 App.jsx를 가리키며, 상대 경로는 '지금 서 있는 곳' 기준이라는 게 포인트입니다."
  },
  {
    "courseSlug": "files",
    "question": "휴지통을 비우거나 Shift+Delete로 지운 파일을 복구 프로그램이 되살릴 수 있는 이유로, 코스의 설명과 맞는 것은?",
    "option1": "Windows가 모든 파일을 자동으로 클라우드에 백업해 두기 때문에",
    "option2": "휴지통 비우기는 파일을 C:\\$Recycle.Bin 폴더로 옮기기만 하기 때문에",
    "option3": "삭제된 파일이 압축된 형태로 디스크 한쪽에 따로 보관되기 때문에",
    "option4": "장부에서 '자리 비었음'만 표시할 뿐, 데이터는 덮어쓰기 전까지 그대로 남기 때문에",
    "answerIndex": 3,
    "explanation": "섹션 5에서 휴지통 비우기는 사서의 장부에서 지우개질만 하는 것이고, 책(데이터)은 새 파일이 덮어쓰기 전까지 서가에 그대로 남는다고 했어요. 복구 프로그램은 이 '장부엔 없지만 서가엔 남은 책'을 찾아냅니다. (휴지통으로 이사하는 건 Delete 단계의 일이에요.)"
  },
  {
    "courseSlug": "files",
    "question": "수습사원 A가 회의록.txt를 zip으로 압축하니 크기가 크게 줄었는데, 사진.jpg는 압축해도 거의 안 줄었습니다. 코스 내용으로 이 차이를 가장 잘 설명한 것은?",
    "option1": "텍스트는 반복 패턴이 많아 잘 줄지만, JPG는 이미 자체 압축이 되어 더 짜낼 반복이 없어서다",
    "option2": "zip은 글자 데이터 전용 형식이라 사진 파일은 원래 압축 대상이 아니어서다",
    "option3": "사진 파일은 시스템 파일로 분류되어 Windows가 압축을 막아 두기 때문이다",
    "option4": "텍스트는 손실 압축을 허용하지만 사진은 무손실만 가능해서 압축률이 낮은 것이다",
    "answerIndex": 0,
    "explanation": "섹션 6에서 압축은 '반복되는 패턴을 짧은 표현으로 바꿔치기'하는 것이라 반복 많은 글은 팍팍 줄고, 사진·영상은 이미 자기 방식으로 압축돼 있어 거의 안 줄어든다고 배웠어요. '직접 확인해 보기' 실습이 바로 이 실험입니다."
  },
  {
    "courseSlug": "firewall",
    "question": "코스에서 방화벽을 '건물 정문의 문지기'에 비유한 이유로 가장 알맞은 것은?",
    "option1": "서버에 몰리는 트래픽 속도를 조절해 여러 서버로 분산해 주기 때문에",
    "option2": "드나드는 데이터에게 출처와 목적지 포트를 물어 규칙표와 대조한 뒤 통과시키거나 차단하기 때문에",
    "option3": "서버에 저장된 데이터를 암호화해서 도둑이 훔쳐도 읽지 못하게 만들기 때문에",
    "option4": "바이러스에 감염된 파일을 찾아내 자동으로 치료까지 해 주기 때문에",
    "answerIndex": 1,
    "explanation": "섹션 1에서 방화벽은 드나드는 모든 데이터에게 \"어디서 왔죠? 몇 호(포트)에 가시죠?\"를 묻고 규칙표와 대조해 통과·차단하는 문지기로 설명됩니다. 규칙표에 없으면 기본은 차단이에요."
  },
  {
    "courseSlug": "firewall",
    "question": "코스 설명에 따르면 '인바운드(Inbound)' 검사에 대한 설명으로 옳은 것은?",
    "option1": "서버에서 밖으로 나가는 연결을 검사하며, 악성코드의 정보 유출을 잡는 마지막 그물이다",
    "option2": "같은 서버 안의 프로그램끼리 주고받는 내부 연결을 검사하는 것이다",
    "option3": "관리자가 SSH로 접속할 때만 예외적으로 동작하는 특별 검사다",
    "option4": "밖에서 서버로 들어오는 연결을 검사하며, 공격 대부분이 이 방향이라 규칙이 빡빡하다",
    "answerIndex": 3,
    "explanation": "섹션 1에서 인바운드는 밖에서 안으로 '들어오는 손님' 검사이고, 공격이 대부분 이 방향으로 오기 때문에 규칙을 빡빡하게 둔다고 설명합니다. 밖으로 나가는 연결을 검사하는 쪽은 아웃바운드예요."
  },
  {
    "courseSlug": "firewall",
    "question": "포트가 진짜로 '열려 있다'고 말하려면 동시에 성립해야 하는 두 가지 조건은?",
    "option1": "그 포트에서 귀 기울이는 프로그램이 실행 중이고, 방화벽이 그 포트로의 접속을 허용해야 한다",
    "option2": "그 포트 번호가 1024보다 작아야 하고, HTTPS 인증서가 미리 발급되어 있어야 한다",
    "option3": "방화벽에 허용 규칙이 있어야 하고, 최근에 그 포트로 접속한 기록이 남아 있어야 한다",
    "option4": "관리자가 서버에 로그인해 있어야 하고, 아웃바운드 규칙도 함께 열려 있어야 한다",
    "answerIndex": 0,
    "explanation": "섹션 2의 핵심입니다. 방 안에 손님을 맞을 프로그램이 실제로 실행 중이고(방에 사람이 있고), 방화벽이 그 포트로 가는 길을 허용해야(문지기가 통과시켜야) 해요. 둘 중 하나라도 아니면 밖에서는 연결할 수 없습니다."
  },
  {
    "courseSlug": "firewall",
    "question": "AWESOMEDEV 서버(AWS EC2)의 보안그룹에서 전 세계(0.0.0.0/0)가 아니라 지정한 IP에서만 접속을 허용하는 포트는?",
    "option1": "443 — 웹 서비스를 제공하는 HTTPS 창구",
    "option2": "3000 — Gitea 웹과 clone에 쓰이는 문",
    "option3": "22 — 서버를 원격 조종하는 SSH 관리자 전용 뒷문",
    "option4": "80 — Caddy가 443으로 안내해 주는 HTTP 입구",
    "answerIndex": 2,
    "explanation": "섹션 4의 실제 규칙표에서 80·443·3000은 전 세계에 공개하지만, SSH(22)는 뚫리면 서버 전체를 내주는 관리자 전용 뒷문이라 지정한 IP에서 온 접속만 통과시키도록 잠가 두었습니다."
  },
  {
    "courseSlug": "firewall",
    "question": "수습 개발자가 기능 테스트를 위해 서버의 PostgreSQL 포트(5432)를 전 세계에 잠깐 열어 두고 그대로 퇴근했다. 코스 내용에 비추어 가장 올바른 판단은?",
    "option1": "DB는 계정과 비밀번호로 보호되므로 다음 출근 때 닫아도 위험은 거의 없다",
    "option2": "포트 스캔 봇이 열린 문을 금방 찾아내므로, 최소 권한 원칙에 따라 즉시 다시 닫아야 한다",
    "option3": "아웃바운드 규칙만 잠겨 있으면 데이터가 밖으로 나갈 수 없으니 그대로 둬도 된다",
    "option4": "5432는 원래 전 세계 공개가 기본값이라 보안그룹 방침과 어긋나는 일이 아니다",
    "answerIndex": 1,
    "explanation": "섹션 5의 경고 그대로예요 — 잠깐 연 문은 다시 닫는 것까지가 한 세트입니다. 공격 봇은 모든 서버의 포트를 자동으로 두드리고 있고(섹션 6의 포트 스캔), 우리 방침에서 5432는 외부에 존재 자체를 숨기는 닫힌 문이어야 합니다."
  },
  {
    "courseSlug": "git-deep",
    "question": "이 코스에서 설명하는 '커밋'의 본질로 가장 알맞은 것은?",
    "option1": "마지막 커밋 이후 바뀐 부분(변경분)만 골라 저장한 기록",
    "option2": "브랜치 하나를 통째로 압축해 보관해 두는 백업 파일",
    "option3": "그 순간 프로젝트 전체를 통째로 찍은 스냅샷(사진) 한 장",
    "option4": "원격 저장소에 올리기 위해 임시로 묶어 둔 파일 묶음",
    "answerIndex": 2,
    "explanation": "섹션 1 '커밋 = 스냅샷'에서 커밋은 변경분 기록이 아니라 프로젝트 전체를 찍은 사진 한 장이라고 설명해요. 안 바뀐 파일은 이전 사진 것을 재활용해서 빠르고 용량도 작습니다."
  },
  {
    "courseSlug": "git-deep",
    "question": "단체 사진 비유에서 \"사진 찍을 사람 이리 모여!\" 하고 줄을 세운 상태에 해당하는 Git 단계는?",
    "option1": "스테이지 — git add로 커밋에 담을 파일을 골라 둔 상태",
    "option2": "작업 폴더 — 교실에서 각자 코드를 고치고 있는 상태",
    "option3": "저장소 — 커밋되어 사진첩에 정식 보관된 상태",
    "option4": "원격 저장소 — push가 끝나 서버에 올라간 상태",
    "answerIndex": 0,
    "explanation": "섹션 1의 3단계 비유에서 작업 폴더는 교실에서 노는 상태, 스테이지는 줄 세운 상태, 커밋은 사진첩 보관이에요. git add를 따로 두는 이유는 고친 것 중 일부만 골라 한 커밋에 담기 위해서입니다."
  },
  {
    "courseSlug": "git-deep",
    "question": "머지 중 충돌이 난 파일에서 <<<<<<< HEAD 부터 ======= 사이에 적혀 있는 내용은 무엇인가?",
    "option1": "합치려고 하는 상대 브랜치에서 고친 내용",
    "option2": "내 우주, 즉 현재 브랜치에서 고친 내용",
    "option3": "Git이 두 내용을 자동으로 합쳐 제안한 절충안 코드",
    "option4": "두 브랜치가 갈라지기 전의 원래 공통 코드",
    "answerIndex": 1,
    "explanation": "섹션 4에서 <<<<<<< HEAD부터 =======까지는 내 우주(현재 브랜치)의 내용, =======부터 >>>>>>>까지는 상대 우주(합치려는 브랜치)의 내용이라고 읽는 법을 알려 줘요."
  },
  {
    "courseSlug": "git-deep",
    "question": "이미 push해서 팀과 공유한 브랜치에서 \"그 기능 롤백해 주세요\" 요청을 받았다. 코스가 권하는 가장 안전한 방법은?",
    "option1": "git reset --hard로 그 커밋 이전 시점으로 역사를 되돌린다",
    "option2": "git checkout으로 과거 커밋 시점에 다녀오며 상태만 구경한다",
    "option3": "git stash로 해당 기능의 코드를 서랍에 임시 보관해 둔다",
    "option4": "git revert로 그 커밋의 정반대 내용을 새 커밋으로 추가한다",
    "answerIndex": 3,
    "explanation": "섹션 7 '되돌리기 3형제'에서 revert는 역사를 지우지 않고 반대 내용을 새 커밋으로 추가하므로 공유한 브랜치에서도 안전하다고 해요. 실무의 롤백 요청은 대부분 revert이고, 공유 후 reset은 대참사입니다."
  },
  {
    "courseSlug": "git-deep",
    "question": "기능 브랜치에서 코드를 고치던 중(커밋하기엔 애매한 미완성 상태) 멘토가 \"급한 버그부터 고쳐줘!\"라고 요청했다. 코스에서 배운 도구로 가장 알맞은 대처는?",
    "option1": "git reset --hard로 미완성 수정을 전부 지우고 버그 수정부터 시작한다",
    "option2": "git stash로 수정 중인 내용을 서랍에 넣어 두고, 버그를 고친 뒤 git stash pop으로 꺼낸다",
    "option3": "미완성 코드라도 일단 main 브랜치에 바로 커밋해 두고 버그를 고친다",
    "option4": "git revert로 마지막 커밋을 되돌려 놓고 버그 수정 작업을 이어 간다",
    "answerIndex": 1,
    "explanation": "섹션 6에서 stash는 커밋하기 애매한 미완성 코드를 '서랍에 임시 보관'했다가 pop으로 도로 꺼내는 도구이며, 작업 중에 급한 버그 요청이 왔을 때가 바로 쓰는 순간이라고 설명해요."
  },
  {
    "courseSlug": "gpu",
    "question": "코스에서 GPU를 '만 명의 일꾼'에 비유한 이유로 가장 알맞은 것은?",
    "option1": "코어 하나하나가 CPU 코어보다 복잡한 판단을 더 잘하기 때문",
    "option2": "프로그램의 전체 흐름과 순서를 GPU가 직접 결정하기 때문",
    "option3": "단순한 계산을 수천~수만 개의 코어가 동시에 처리하기 때문",
    "option4": "코어 수는 적지만 코어 한 개의 성능이 매우 강력하기 때문",
    "answerIndex": 2,
    "explanation": "섹션 1의 핵심 비유입니다. GPU는 단순한 덧셈·곱셈만 하는 코어 수천~수만 개가 '동시에' 달려들어, 같은 계산이 대량으로 필요한 일에서 천재 한 명(CPU)을 압도합니다."
  },
  {
    "courseSlug": "gpu",
    "question": "코스에서 VRAM을 무엇에 비유했고, VRAM이 부족하면 어떤 일이 생긴다고 설명했나요?",
    "option1": "창고 — 부족하면 GPU가 데이터를 압축해서 쌓아 두게 된다",
    "option2": "작업 책상 — 부족하면 창고(일반 RAM)를 왔다 갔다 하느라 느려진다",
    "option3": "냉각 팬 — 부족하면 GPU가 뜨거워져 스스로 속도를 낮춘다",
    "option4": "두뇌 — 부족하면 복잡한 판단을 전부 CPU에 넘기게 된다",
    "answerIndex": 1,
    "explanation": "섹션 4에서 VRAM은 GPU 전용 '작업 책상'으로 비유됩니다. 책상이 좁으면 재료를 창고(일반 RAM)에서 자꾸 가져와야 해서 일꾼 만 명이 놀게 되고, 연산 속도가 충분해도 느려집니다."
  },
  {
    "courseSlug": "gpu",
    "question": "\"모니터가 144Hz면 화면이 무조건 부드럽다\"는 말에 대해 코스가 설명한 올바른 내용은?",
    "option1": "GPU가 1초에 60장밖에 못 그리면 모니터가 갈아 끼울 그림이 없으므로, 주사율과 GPU 성능은 세트로 맞아야 한다",
    "option2": "주사율은 화면의 색 표현 개수를 정하는 값이라서 화면의 부드러움과는 아무 관계가 없다",
    "option3": "144Hz 모니터는 해상도를 자동으로 낮춰 주기 때문에 어떤 GPU를 써도 항상 부드럽다",
    "option4": "부드러움은 오직 VRAM 용량이 결정하므로 모니터 주사율은 신경 쓰지 않아도 된다",
    "answerIndex": 0,
    "explanation": "섹션 2의 '흔한 오해'입니다. 주사율은 모니터가 1초에 그림을 갈아 끼우는 횟수일 뿐, GPU가 프레임을 제때 만들어 주지 못하면 갈아 끼울 그림이 없어 부드러워지지 않습니다."
  },
  {
    "courseSlug": "gpu",
    "question": "게임용으로 태어난 GPU가 AI 학습의 표준 장비가 된 이유로 코스가 설명한 것은?",
    "option1": "AI 전용 명령어가 게임 그래픽 명령어보다 훨씬 단순하게 만들어졌기 때문",
    "option2": "GPU가 CPU보다 전력을 적게 써서 데이터센터 운영 비용이 줄어들기 때문",
    "option3": "GPU에 달린 VRAM이 일반 RAM보다 용량이 언제나 더 크기 때문",
    "option4": "AI는 사실상 거대한 행렬 곱셈 덩어리라, 픽셀 수백만 개를 동시에 계산하는 게임 작업과 하는 일이 사실상 같기 때문",
    "answerIndex": 3,
    "explanation": "섹션 5의 핵심입니다. AI 학습·실행은 '단순한 곱셈·덧셈을 수십억 번' 반복하는 일이고, 이는 픽셀을 동시에 계산하던 GPU(만 명의 일꾼)가 세상에서 제일 잘하는 일과 같아서 GPU가 AI의 표준 장비가 됐습니다."
  },
  {
    "courseSlug": "gpu",
    "question": "실습 중 작업 관리자 [성능] 탭을 옆에 띄워 두고 유튜브에서 4K 영상을 재생했더니 Video Decode 그래프가 쑥 올라갔다. 코스에 따르면 이 현상이 의미하는 것은?",
    "option1": "브라우저가 CPU 대신 3D 게임 엔진을 실행하기 시작했다는 뜻이다",
    "option2": "VRAM이 부족해서 일반 RAM에서 재료를 빌려 오고 있다는 뜻이다",
    "option3": "영상 압축을 푸는 일을 GPU의 전용 회로가 담당하고 있다는 뜻이다",
    "option4": "외장 GPU가 고장 나서 내장 GPU가 대신 일을 떠맡았다는 뜻이다",
    "answerIndex": 2,
    "explanation": "섹션 7의 실습 ②입니다. 4K 영상을 재생하면 영상 압축을 푸는 일(디코딩)을 GPU 전용 회로가 담당해 Video Decode 그래프가 올라가며, GPU가 게임할 때만 쓰이는 게 아님을 숫자로 확인하는 실습입니다."
  },
  {
    "courseSlug": "hardware",
    "question": "컴퓨터가 하는 5가지 일(입력·기억·계산·저장·출력) 중에서 '계산'을 담당하는 부품은 무엇인가?",
    "option1": "RAM(메모리)",
    "option2": "SSD/HDD(저장장치)",
    "option3": "CPU",
    "option4": "메인보드",
    "answerIndex": 2,
    "explanation": "코스 1섹션의 다이어그램에서 CPU는 '두뇌'로서 계산(생각하기)을 담당한다. RAM은 기억(펼쳐 놓기), SSD/HDD는 저장, 메인보드는 부품들이 꽂히는 판이다."
  },
  {
    "courseSlug": "hardware",
    "question": "코스에서 설명한 RAM(메모리)의 중요한 특징으로 올바른 것은?",
    "option1": "전원을 꺼도 저장한 자료가 그대로 남는다",
    "option2": "전원을 끄면 내용이 싹 비워진다",
    "option3": "윈도우와 프로그램 파일이 설치되는 보관 창고다",
    "option4": "책장처럼 넓지만 자료를 꺼내는 속도가 가장 느리다",
    "answerIndex": 1,
    "explanation": "코스 3섹션에서 RAM은 '지금 하는 일을 펼쳐 놓는 작업 책상'이라 전원을 끄면 싹 비워진다고 설명한다. 전원을 꺼도 남는 보관은 저장장치(SSD/HDD)의 역할이다."
  },
  {
    "courseSlug": "hardware",
    "question": "CPU 모델명 '인텔 코어 i5-14400F'에서 맨 뒤의 'F'가 뜻하는 것은 무엇인가?",
    "option1": "오버클럭이 가능한 고급형 모델이라는 표시",
    "option2": "노트북용 저전력 모델이라는 표시",
    "option3": "같은 세대 안에서 가장 최신 모델이라는 표시",
    "option4": "내장그래픽이 없어 외장 그래픽카드가 꼭 필요하다는 표시",
    "answerIndex": 3,
    "explanation": "코스 2섹션의 CPU 스펙표 읽기에서 'F'는 내장그래픽 없음을 뜻하며, 이 경우 외장 그래픽카드가 필수라고 설명한다. i5는 등급, 14는 세대, 400은 세부 모델이다."
  },
  {
    "courseSlug": "hardware",
    "question": "코스에서 PC 견적을 짤 때 '세 개가 한 세트로 맞아야 한다'고 강조한 조합은 무엇인가?",
    "option1": "CPU ↔ 메인보드 소켓 ↔ RAM 세대(DDR4/DDR5)",
    "option2": "CPU ↔ 파워 정격 출력 ↔ 케이스 색상",
    "option3": "그래픽카드 ↔ 모니터 크기 ↔ SSD 용량",
    "option4": "RAM 용량 ↔ HDD 회전 속도 ↔ 케이스 팬 개수",
    "answerIndex": 0,
    "explanation": "5섹션에서 소켓이 맞아야 CPU가 꽂히고 RAM 규격(DDR4/DDR5)도 메인보드가 정하므로, 'CPU ↔ 보드 소켓 ↔ RAM 세대' 세 가지가 한 세트로 맞아야 한다고 설명한다."
  },
  {
    "courseSlug": "hardware",
    "question": "크롬 탭 30개에 게임과 디스코드까지 켰더니 컴퓨터가 뚝뚝 끊긴다. 코스의 주방·책상 비유로 이 현상의 원인을 가장 잘 설명한 것은?",
    "option1": "요리사(CPU)의 손 빠르기(GHz)가 절반으로 줄어들었기 때문",
    "option2": "책상(RAM)이 꽉 차서 자료를 책장(저장장치)으로 치웠다 꺼내는 스왑이 일어나기 때문",
    "option3": "냉장고(저장장치)가 가득 차서 새 파일을 저장할 공간이 없기 때문",
    "option4": "배전반(파워)의 전력이 모자라 부품 일부가 꺼졌다 켜지기 때문",
    "answerIndex": 1,
    "explanation": "3섹션의 '스왑' 설명에 나오는 상황이다. RAM(책상)이 꽉 차면 덜 쓰는 자료를 SSD/HDD(책장)로 잠시 치워 두는데, 책상에서 바로 집는 것보다 수백~수천 배 느려서 끊김이 생긴다."
  },
  {
    "courseSlug": "history",
    "question": "1946년에 등장한 에니악(ENIAC)에서 실행할 프로그램을 바꾸려면 어떻게 해야 했나요?",
    "option1": "천공카드를 새로 뚫어서 기계에 읽혔다",
    "option2": "메모리에 저장된 파일을 교체해 실행했다",
    "option3": "케이블 배선을 손으로 다시 꽂았다",
    "option4": "진공관을 다른 종류로 갈아 끼웠다",
    "answerIndex": 2,
    "explanation": "코스 1섹션에 나오듯 에니악은 \"프로그램을 바꾼다 = 배선을 다시 꽂는다\"였던 시대의 기계입니다. 파일을 바꿔 실행하는 방식은 이후 폰 노이만 구조가 등장하면서 가능해졌어요."
  },
  {
    "courseSlug": "history",
    "question": "코스에서 소개한 무어의 법칙(1965, 고든 무어의 관찰)의 내용으로 옳은 것은?",
    "option1": "칩의 가격은 2년마다 절반으로 떨어진다",
    "option2": "칩 하나에 들어가는 트랜지스터 수는 약 2년마다 2배가 된다",
    "option3": "컴퓨터의 전력 소모는 매년 2배씩 늘어난다",
    "option4": "소프트웨어의 크기는 하드웨어 성능만큼 빠르게 커진다",
    "answerIndex": 1,
    "explanation": "무어의 법칙은 트랜지스터 수가 약 2년마다 2배가 된다는 관찰로, 코스에서는 \"2배 × 25번\"이 50년간 수백만 배라는 지수적 성장을 만들었다고 설명합니다."
  },
  {
    "courseSlug": "history",
    "question": "코스에서 '헷갈리기 쉽다'며 따로 구분한 인터넷과 웹의 관계로 옳은 것은?",
    "option1": "인터넷은 전 세계 컴퓨터를 잇는 도로망이고, 웹은 그 도로 위에서 HTTP로 문서와 앱을 주고받는 서비스다",
    "option2": "웹이 물리적 연결망이고, 인터넷은 그 연결망 위에서 돌아가는 여러 서비스 중 하나다",
    "option3": "인터넷과 웹은 같은 것을 시대에 따라 다르게 부르는 두 가지 이름일 뿐이다",
    "option4": "인터넷은 한 나라 안의 연결망을, 웹은 나라와 나라를 잇는 국제 연결망을 가리킨다",
    "answerIndex": 0,
    "explanation": "섹션 4의 핵심 구분입니다. 인터넷은 도로망(물리적 연결), 웹은 그 도로 위를 달리는 서비스 중 하나(HTTP로 문서·앱 주고받기)라고 코스가 설명해요."
  },
  {
    "courseSlug": "history",
    "question": "클라우드 섹션에서 소개한 우리 회사(AWESOMEDEV)의 실제 서버 구성 중, Caddy가 맡고 있는 역할은?",
    "option1": "앱을 컨테이너(규격 화물)로 포장해 실행한다",
    "option2": "백엔드 API의 비즈니스 로직을 처리한다",
    "option3": "서비스의 데이터를 저장하고 관리한다",
    "option4": "HTTPS 현관 안내데스크 역할을 한다",
    "answerIndex": 3,
    "explanation": "코스의 실제 구성도에서 Docker는 컨테이너 포장, Spring Boot는 백엔드, PostgreSQL은 데이터베이스, Caddy는 'HTTPS 현관 안내데스크'로 소개됩니다."
  },
  {
    "courseSlug": "history",
    "question": "수습사원 A가 학습 플랫폼 페이지에서 F12를 눌러 기기 툴바를 켜고 화면 폭을 천천히 줄였더니, 어느 지점에서 레이아웃이 '뚝' 하고 다시 배치되었습니다. 코스에 따르면 이 지점의 이름과, 이런 설계가 기본기가 된 시대는?",
    "option1": "렌더링 포인트 — 클라우드 시대",
    "option2": "분기점(breakpoint) — 스마트폰 시대",
    "option3": "체크포인트 — PC 혁명 시대",
    "option4": "접속점(access point) — 인터넷·웹 시대",
    "answerIndex": 1,
    "explanation": "섹션 5의 '직접 확인해 보기' 실습 그대로입니다. 배치가 바뀌는 지점이 반응형의 분기점(breakpoint)이며, \"사용자가 어떤 화면으로 볼지 모른다\"는 전제를 만든 스마트폰 시대가 반응형 디자인을 기본기로 남겼어요."
  },
  {
    "courseSlug": "home-network",
    "question": "공유기 관리자 페이지에 들어가려면 먼저 공유기의 주소를 알아내야 한다. 코스에서 안내한 방법은 무엇인가?",
    "option1": "브라우저 주소창에 8.8.8.8을 입력해서 관리 화면을 연다",
    "option2": "속도 측정 사이트에서 우리 집 공인 IP를 확인해 입력한다",
    "option3": "터미널에서 ipconfig를 실행해 기본 게이트웨이 주소를 확인한다",
    "option4": "공유기 밑면 스티커에 적힌 SSID를 주소창에 입력한다",
    "answerIndex": 2,
    "explanation": "섹션 1에서 배운 3단계의 첫걸음이다. ipconfig로 나오는 기본 게이트웨이(예: 192.168.0.1)가 곧 공유기의 주소이고, 그 주소를 브라우저에 입력하면 관리자 페이지에 들어갈 수 있다."
  },
  {
    "courseSlug": "home-network",
    "question": "코스에서 2.4GHz 대역을 저음(우퍼) 스피커에 비유했다. 2.4GHz의 특징으로 맞는 것은?",
    "option1": "속도는 느리지만 멀리 도달하고 벽을 잘 통과한다",
    "option2": "속도가 빠르고 벽도 잘 통과하지만 도달 거리가 짧다",
    "option3": "도달 거리는 짧지만 전자레인지 간섭이 거의 없다",
    "option4": "채널이 수십 개라 이웃 간 간섭 걱정이 적은 대역이다",
    "answerIndex": 0,
    "explanation": "저음이 벽 너머까지 웅웅 울려 퍼지듯, 낮은 주파수인 2.4GHz는 멀리 가고 벽을 잘 뚫는 대신 속도가 느리고 전자레인지·블루투스 등 간섭이 많다. 빠르지만 여린 쪽은 5GHz다."
  },
  {
    "courseSlug": "home-network",
    "question": "2.4GHz 대역에는 채널 1~13번이 있지만 서로 겹치는 구조다. 실제로 겹치지 않게 쓸 수 있는 채널 조합은?",
    "option1": "2번 · 7번 · 12번",
    "option2": "1번 · 6번 · 11번",
    "option3": "1번 · 5번 · 9번",
    "option4": "3번 · 6번 · 9번",
    "answerIndex": 1,
    "explanation": "섹션 3의 핵심이다. 2.4GHz 채널은 이웃 번호끼리 겹치는 구조라 안 겹치는 조합은 1·6·11 세 개뿐 — 왕복 6차선 도로에서 실제로 안 부딪히는 차선이 3개뿐인 상황에 비유했다."
  },
  {
    "courseSlug": "home-network",
    "question": "외부에서 내 기기로 접속을 허용해야 할 때, 코스가 권장한 순서를 안전한 것부터 바르게 나열한 것은?",
    "option1": "DMZ → UPnP → 필요한 포트만 포트포워딩",
    "option2": "UPnP → 필요한 포트만 포트포워딩 → DMZ",
    "option3": "DMZ → 필요한 포트만 포트포워딩 → UPnP",
    "option4": "필요한 포트만 포트포워딩 → UPnP → DMZ",
    "answerIndex": 3,
    "explanation": "좁게 열수록 안전하다는 원칙이다. 필요한 포트만 여는 포트포워딩이 1순위, 자동문인 UPnP가 2순위, 모든 문을 열어 버리는 DMZ는 최후 수단(사실상 쓰지 말 것)이다. 서버 보안의 최소 권한 원칙과 같다."
  },
  {
    "courseSlug": "home-network",
    "question": "내 PC(192.168.0.5)에서 Spring Boot 서버를 8080 포트로 켰다. 친구가 우리 집 공인 IP의 8080 포트로 접속했지만 공유기에서 막혀 실패했다. 코스의 원칙에 맞는 가장 안전한 해결 방법은?",
    "option1": "공유기 DMZ에 내 PC를 지정해 모든 외부 요청을 받게 한다",
    "option2": "외부 8080 포트를 192.168.0.5의 8080으로 보내는 포트포워딩 규칙을 등록한다",
    "option3": "PC를 5GHz SSID로 옮겨 접속해 무선 속도를 끌어올린다",
    "option4": "무선 채널을 1·6·11 중 이웃이 덜 쓰는 번호로 수동 변경한다",
    "answerIndex": 1,
    "explanation": "경비실(NAT) 비유처럼 공유기는 밖에서 먼저 온 손님을 몇 호로 보낼지 몰라 돌려보내므로, 필요한 포트 하나만 정확히 여는 포트포워딩 규칙을 등록해야 한다. DMZ도 되긴 하지만 문을 전부 여는 위험한 방법이다."
  },
  {
    "courseSlug": "htmlcss",
    "question": "이 코스에서는 웹페이지의 세 가지 재료를 사람에 비유합니다. CSS는 무엇에 해당하나요?",
    "option1": "골격 — 몸의 구조를 잡아 준다",
    "option2": "옷과 화장 — 겉모습을 꾸며 준다",
    "option3": "근육 — 몸을 움직이게 한다",
    "option4": "신경 — 자극에 반응하게 한다",
    "answerIndex": 1,
    "explanation": "섹션 1 '웹페이지의 정체'에서 HTML은 골격, CSS는 옷과 화장, JS는 근육에 비유합니다. 같은 뼈대(HTML)에 옷(CSS)만 갈아입혀도 완전히 다른 사이트처럼 보인다고 설명해요."
  },
  {
    "courseSlug": "htmlcss",
    "question": "박스 모델의 택배 상자 비유에서 '뽁뽁이'(물건과 상자 벽 사이의 완충재)에 해당하는 CSS 속성은 무엇인가요?",
    "option1": "margin — 옆 상자와의 간격",
    "option2": "border — 상자의 벽",
    "option3": "padding — 벽과 내용 사이의 안쪽 여백",
    "option4": "box-shadow — 상자가 떠 있는 느낌",
    "answerIndex": 2,
    "explanation": "섹션 4 박스 모델에서 content는 물건, padding은 뽁뽁이, border는 상자 벽, margin은 옆 상자와의 간격에 비유합니다. 뽁뽁이는 상자 '안'에 있으므로 안쪽 여백인 padding이에요."
  },
  {
    "courseSlug": "htmlcss",
    "question": "우리 플랫폼은 컴포넌트 코드를 한 줄도 바꾸지 않고 다크 모드를 지원합니다. 코스에서 설명한 그 원리는 무엇인가요?",
    "option1": "모든 색이 :root 변수에 모여 있어, @media 블록이 변수 값만 갈아끼우기 때문",
    "option2": "다크 모드 전용 CSS 파일을 페이지마다 하나씩 따로 만들어 두었기 때문",
    "option3": "JS가 화면의 모든 색 속성을 찾아서 어두운 색으로 바꿔 주기 때문",
    "option4": "브라우저가 페이지의 모든 색을 자동으로 반전시켜 주기 때문",
    "answerIndex": 0,
    "explanation": "섹션 5에서 global.css의 :root에 색 '이름표'(--bg, --ink, --primary)를 붙여 두고, @media (prefers-color-scheme: dark)에서 이름표의 값만 바꿔치기하면 사이트 전체가 옷을 갈아입는다고 설명합니다."
  },
  {
    "courseSlug": "htmlcss",
    "question": "겉보기엔 <div onClick=...>도 클릭이 됩니다. 그런데 코스에서 \"누르는 것은 반드시 <button>\"이라고 강조하는 이유는 무엇인가요?",
    "option1": "div는 클릭 이벤트를 처리하는 속도가 button보다 훨씬 느려서",
    "option2": "div에 onClick을 달면 화면에 아예 표시되지 않아서",
    "option3": "키보드 Tab으로 접근할 수 없고 스크린리더도 버튼인 줄 몰라서",
    "option4": "button 태그가 아니면 CSS 스타일을 적용할 수 없어서",
    "answerIndex": 2,
    "explanation": "섹션 2의 '클릭은 button에게' 경고 내용입니다. div에 onClick을 달면 키보드 Tab으로 접근이 안 되고 스크린리더도 버튼인 줄 모르기 때문에, 우리 코드 리뷰에서도 자주 나오는 지적이라고 해요."
  },
  {
    "courseSlug": "htmlcss",
    "question": "수습 티켓 FE-06 상황입니다. 폰(화면 폭 640px 이하)에서 두 칸짜리 카드 바둑판이 깨져서, 좁은 화면에서만 한 칸으로 바꾸려 합니다. 코스 내용대로라면 어떻게 고쳐야 할까요?",
    "option1": ".card-grid에 display: flex와 align-items: center를 추가한다",
    "option2": ":root의 --primary 변수 값을 더 밝은 색으로 바꾼다",
    "option3": ".card-grid의 padding과 margin을 크게 늘려 간격을 조정한다",
    "option4": "@media (max-width: 640px) 블록 안에서 grid-template-columns를 1fr로 바꾼다",
    "answerIndex": 3,
    "explanation": "섹션 7 반응형에서 @media (max-width: 640px)는 화면 폭이 640px 이하일 때만 CSS를 적용하는 조건문이고, 그 안에서 grid-template-columns: 1fr로 두 칸 바둑판을 한 칸으로 접는 예시를 그대로 보여 줍니다."
  },
  {
    "courseSlug": "http",
    "question": "HTTP 요청과 응답이 공통으로 갖는 세 부분 구조는 무엇인가요?",
    "option1": "주소줄, 본문, 서명",
    "option2": "메서드, 상태코드, 쿠키",
    "option3": "시작줄, 헤더, 바디",
    "option4": "헤더, 푸터, 첨부파일",
    "answerIndex": 2,
    "explanation": "섹션 1에서 배웠듯 HTTP 편지는 요청이든 응답이든 똑같이 시작줄·헤더·바디 세 부분으로 이루어집니다. 헤더는 편지 겉봉의 메모, 바디는 진짜 내용물이에요."
  },
  {
    "courseSlug": "http",
    "question": "로그인은 되어 있지만 학생 계정으로 관리자 API를 호출했을 때 서버가 돌려주는 상태코드는 무엇인가요?",
    "option1": "401 Unauthorized",
    "option2": "403 Forbidden",
    "option3": "404 Not Found",
    "option4": "500 Internal Server Error",
    "answerIndex": 1,
    "explanation": "섹션 2의 학생증 비유로, 403은 학생증은 있지만 교무실 출입증이 아닌 경우(권한 부족)입니다. 401은 학생증 자체가 없어 로그인부터 해야 하는 상황이에요."
  },
  {
    "courseSlug": "http",
    "question": "쿠키와 세션의 역할 분담을 카페 비유로 옳게 설명한 것은 무엇인가요?",
    "option1": "쿠키는 서버가 적는 장부이고, 세션은 브라우저가 드는 번호표다",
    "option2": "쿠키와 세션 모두 서버가 보관하고 브라우저는 아무것도 저장하지 않는다",
    "option3": "쿠키에는 비밀번호가 담겨 매 요청마다 그대로 전송된다",
    "option4": "쿠키는 브라우저가 드는 번호표이고, 세션은 서버가 적어 두는 장부다",
    "answerIndex": 3,
    "explanation": "섹션 3의 비유대로 쿠키는 손에 든 번호표(브라우저 보관), 세션은 카운터 뒤의 장부(서버 보관)입니다. 비밀번호는 로그인 그 한 번만 전송되고 이후엔 번호표만 오가요."
  },
  {
    "courseSlug": "http",
    "question": "Set-Cookie에 붙는 HttpOnly 옵션이 하는 일은 무엇인가요?",
    "option1": "자바스크립트가 쿠키를 읽지 못하게 해서 악성 스크립트의 번호표 탈취를 막는다",
    "option2": "HTTPS 연결일 때만 쿠키를 전송하도록 제한한다",
    "option3": "다른 사이트에서 시작된 요청에는 쿠키를 붙이지 않게 한다",
    "option4": "쿠키의 유효 기간을 90일마다 자동으로 갱신해 준다",
    "answerIndex": 0,
    "explanation": "섹션 3에서 배운 번호표 도난 방지 장치 중 HttpOnly는 자바스크립트가 쿠키를 못 읽게 하는 옵션입니다. HTTPS 전용 전송은 Secure, 타 사이트 요청 차단은 SameSite의 역할이에요."
  },
  {
    "courseSlug": "http",
    "question": "카페 공용 와이파이에서 http:// 사이트에 아이디와 비밀번호를 입력해 로그인하면 위험한 이유는 무엇인가요?",
    "option1": "공용 와이파이는 속도가 느려서 세션 쿠키가 도중에 만료되기 때문이다",
    "option2": "http 사이트는 서버 인증서 검사를 두 번 해서 로그인이 자주 끊기기 때문이다",
    "option3": "HTTP는 엽서와 같아서 길을 지나는 누구나 아이디와 비밀번호를 그대로 읽을 수 있기 때문이다",
    "option4": "브라우저가 http 사이트에는 쿠키를 아예 저장하지 않아 로그인이 불가능하기 때문이다",
    "answerIndex": 2,
    "explanation": "섹션 4의 엽서 비유입니다. HTTP는 봉인이 없는 엽서라 와이파이 주인 같은 중간 지점에서 내용이 그대로 노출되고, HTTPS는 TLS 봉투로 밀봉되어 훔쳐봐도 암호문 덩어리만 보여요."
  },
  {
    "courseSlug": "icons-images",
    "question": "비트맵과 벡터의 가장 근본적인 차이는 무엇인가요?",
    "option1": "비트맵은 색을 적게 쓰고, 벡터는 수백만 가지 색을 쓴다",
    "option2": "비트맵은 웹 전용 포맷이고, 벡터는 인쇄 전용 포맷이다",
    "option3": "비트맵은 픽셀마다 색을 전부 기록하고, 벡터는 '어떻게 그리는지'를 기록한다",
    "option4": "비트맵은 압축이 불가능하고, 벡터는 항상 자동으로 압축된다",
    "answerIndex": 2,
    "explanation": "코스 1섹션의 핵심입니다. 비트맵(PNG·JPG)은 칸(픽셀)마다 무슨 색인지 전부 기록하고, 벡터(SVG)는 '중심에 반지름 40 원을 그려' 같은 설계도를 기록해서 확대해도 항상 매끈합니다."
  },
  {
    "courseSlug": "icons-images",
    "question": "코스의 포맷 선택 기준표에 따르면, 아이콘과 로고에 가장 알맞은 포맷은 무엇인가요?",
    "option1": "JPG — 색이 많은 이미지에 최적이라서",
    "option2": "SVG — 벡터라 어떤 크기에서도 매끈해서",
    "option3": "PNG — 투명 배경을 지원하는 비트맵이라서",
    "option4": "WebP — 같은 품질에 용량이 더 작아서",
    "answerIndex": 1,
    "explanation": "기준표에서 사진은 JPG(또는 WebP), 아이콘·로고는 SVG, '투명 배경 + 비트맵'이면 PNG로 정리했습니다. 아이콘·로고는 도형 몇 개로 이루어져 벡터인 SVG가 최적입니다."
  },
  {
    "courseSlug": "icons-images",
    "question": "비트맵 아이콘은 @2x 버전을 따로 만들어야 하는데, SVG는 그럴 필요가 없습니다. 그 이유는 무엇인가요?",
    "option1": "SVG는 파일 용량이 항상 1KB 이하로 작기 때문에",
    "option2": "브라우저가 SVG를 미리 2배 크기로 확대해 저장해 두기 때문에",
    "option3": "SVG는 고해상도 화면에서 자동으로 비트맵으로 변환되기 때문에",
    "option4": "SVG는 '그리는 법'의 기록이라 어떤 화면 밀도에서도 다시 계산해 매끈하게 그려지기 때문에",
    "answerIndex": 3,
    "explanation": "2섹션 '해상도와 @2x'의 내용입니다. 고해상도 화면은 CSS 1px을 화소 2~3개로 그리므로 비트맵은 2배 크기 파일이 필요하지만, SVG는 설계도라 어떤 밀도에서도 스스로 매끈하게 그려집니다."
  },
  {
    "courseSlug": "icons-images",
    "question": "'개인/비상업용 무료'라고 표시된 이미지를 우리 회사 서비스에 쓰면 안 되는 이유는 무엇인가요?",
    "option1": "회사 서비스에 쓰는 것은 전부 상업적 이용에 해당하기 때문에",
    "option2": "무료 이미지는 해상도가 낮아 서비스 품질을 떨어뜨리기 때문에",
    "option3": "개인용 이미지는 파일 형식이 회사 시스템과 호환되지 않기 때문에",
    "option4": "무료 이미지는 색을 바꾸는 등 수정이 기술적으로 불가능하기 때문에",
    "answerIndex": 0,
    "explanation": "4섹션 '무료 소스와 저작권'에서 강조한 첫 번째 확인 사항입니다. 회사 서비스에 쓰는 건 전부 상업적 이용이므로 '개인/비상업용 무료' 이미지는 쓸 수 없고, 어기면 저작권 침해로 손해배상까지 이어질 수 있습니다."
  },
  {
    "courseSlug": "icons-images",
    "question": "수습 개발자가 화면에 200px로 보일 프로필 사진 자리에 폰 카메라 원본(4000px, 약 5MB)을 그대로 올렸습니다. 코스의 최적화 순서에 따라 가장 먼저 해야 할 일은 무엇인가요?",
    "option1": "F12의 Network 탭을 열어 실제 전송 용량부터 확인한다",
    "option2": "압축 도구에 넣어 눈에 안 띄는 수준으로 용량을 줄인다",
    "option3": "보여줄 크기에 맞게 이미지를 리사이즈한다",
    "option4": "파일 확장자만 WebP로 바꿔서 다시 저장한다",
    "answerIndex": 2,
    "explanation": "5섹션 '이미지 최적화'의 순서는 1) 리사이즈 → 2) 포맷 선택 → 3) 압축 → 4) 확인입니다. 보여줄 크기보다 큰 이미지를 올리지 않는 리사이즈가 첫 단계로, 200px 자리에 4000px 원본을 올리면 쓰지도 않을 픽셀 수백만 개를 내려받게 됩니다."
  },
  {
    "courseSlug": "ip-deep",
    "question": "IPv4 주소의 구조에 대한 설명으로 옳은 것은?",
    "option1": "16비트를 4비트씩 4칸으로 나눈 것으로, 한 칸은 0~15까지 가능하다",
    "option2": "32비트를 8비트씩 4칸으로 나눈 것으로, 한 칸은 0~255까지 가능하다",
    "option3": "64비트를 16비트씩 4칸으로 나눈 것으로, 한 칸은 0~65535까지 가능하다",
    "option4": "128비트를 16진수 8덩어리로 나눈 것으로, 콜론(:)으로 구분해 표기한다",
    "answerIndex": 1,
    "explanation": "섹션 1의 핵심 — IPv4는 32비트를 8비트(옥텟)씩 4칸으로 끊어 점으로 표기하며, 한 칸은 2^8=256가지라 0~255까지만 가능합니다. 그래서 192.168.0.300 같은 주소는 존재할 수 없어요."
  },
  {
    "courseSlug": "ip-deep",
    "question": "새 기기가 와이파이에 붙을 때 DHCP가 IP를 빌려주는 4단계(DORA)의 올바른 순서는?",
    "option1": "Offer → Discover → Request → Acknowledge",
    "option2": "Discover → Request → Offer → Acknowledge",
    "option3": "Request → Offer → Discover → Acknowledge",
    "option4": "Discover → Offer → Request → Acknowledge",
    "answerIndex": 3,
    "explanation": "섹션 5의 DORA — 세입자가 \"방 있나요?\"(Discover) 외치면 임대인이 \"이 방 어때요?\"(Offer) 제안하고, \"계약할게요!\"(Request) 뒤 \"도장 쾅!\"(Acknowledge)으로 확정되는 순서입니다."
  },
  {
    "courseSlug": "ip-deep",
    "question": "다음 중 사설 IP 대역에 속하는 주소는 무엇인가?",
    "option1": "172.20.10.3",
    "option2": "172.15.10.3",
    "option3": "172.32.10.3",
    "option4": "192.169.0.5",
    "answerIndex": 0,
    "explanation": "섹션 2의 함정 포인트 — 172 대역은 172.16.x.x부터 172.31.x.x까지만 사설입니다. \"172는 16층부터 31층까지만 우리 것\"이라는 암기법대로 172.20은 사설이고, 172.15·172.32·192.169는 남의 공인 IP예요."
  },
  {
    "courseSlug": "ip-deep",
    "question": "서브넷마스크가 255.255.255.0(/24)인 네트워크에서 실제로 기기에 배정할 수 있는 호스트 주소는 몇 개인가?",
    "option1": "256개 — 호스트 비트 8개의 모든 조합을 쓸 수 있다",
    "option2": "255개 — 네트워크 주소(.0) 하나만 빼면 된다",
    "option3": "254개 — 네트워크 주소(.0)와 방송용(.255)을 빼야 한다",
    "option4": "252개 — 게이트웨이와 DNS 서버 몫까지 미리 빼야 한다",
    "answerIndex": 2,
    "explanation": "섹션 3의 아파트 단지 비유 — /24는 호스트 비트가 8개라 2^8=256개 조합이 나오지만, 단지 자체를 가리키는 .0과 단지 전체 방송용 .255를 빼면 실제 입주 가능한 세대는 254개입니다."
  },
  {
    "courseSlug": "ip-deep",
    "question": "서울 EC2에서 돌던 서버를 중지(stop)했다가 다시 시작(start)했더니 도메인 edu.awesomedevapp.com으로 접속이 안 된다. 코스 내용에 비추어 가장 가능성이 높은 원인은?",
    "option1": "서브넷마스크가 /24에서 /16으로 바뀌어 네트워크 경계선이 달라졌기 때문",
    "option2": "탄력적 IP를 연결해 두지 않아 재시작 때 공인 IP가 바뀌었는데, DNS는 여전히 옛 IP를 가리키고 있기 때문",
    "option3": "DHCP 임대 기간이 절반쯤 지나 서버가 자동으로 임대 갱신을 요청했기 때문",
    "option4": "IPv6 주소가 :: 축약 표기로 바뀌어 브라우저가 주소를 해석하지 못하기 때문",
    "answerIndex": 1,
    "explanation": "섹션 8 — EC2의 공인 IP는 기본이 유동이라 중지 후 시작하면 바뀝니다. 가게가 이사 다니면 단골이 못 찾아오듯 DNS 전화번호부가 틀려지므로, 탄력적 IP를 못 박아 두어야 재시작해도 같은 주소로 열려요."
  },
  {
    "courseSlug": "java-basics",
    "question": "JavaScript에서는 let age = 17; 뒤에 age = \"열일곱\";처럼 다른 타입을 넣어도 허용되지만, Java에서 int age = 17; 뒤에 age = \"열일곱\";을 쓰면 어떻게 되는가?",
    "option1": "실행 중에 오류가 발생해 프로그램이 그때 멈춘다",
    "option2": "문자열이 자동으로 숫자로 변환되어 그대로 실행된다",
    "option3": "컴파일 단계에서 에러가 나서 실행조차 할 수 없다",
    "option4": "경고 메시지만 표시되고 정상적으로 실행된다",
    "answerIndex": 2,
    "explanation": "Java는 변수를 만들 때 타입을 미리 선언하고, 약속을 어기면 실행 전에(컴파일 때) 막는 언어다. 섹션 1 'JS와 뭐가 다를까'에서 배운 타입 선언과 컴파일 검사의 핵심이다."
  },
  {
    "courseSlug": "java-basics",
    "question": "코스의 붕어빵 비유에서 클래스, 객체, new 키워드를 올바르게 짝지은 것은?",
    "option1": "클래스=붕어빵, 객체=붕어빵 틀, new=틀을 새로 만드는 것",
    "option2": "클래스=붕어빵 틀, 객체=붕어빵, new=틀에 반죽을 부어 하나 굽는 것",
    "option3": "클래스=반죽, 객체=붕어빵 틀, new=틀에 팥을 채우는 것",
    "option4": "클래스=붕어빵 틀, 객체=반죽, new=다 구운 붕어빵을 꺼내는 것",
    "answerIndex": 1,
    "explanation": "섹션 3에서 클래스는 붕어빵 틀, 객체는 그 틀로 구운 붕어빵, new는 '틀에 반죽 부어서 하나 굽기'라고 배웠다. 틀은 하나여도 붕어빵(객체)은 원하는 만큼 만들 수 있다."
  },
  {
    "courseSlug": "java-basics",
    "question": "List<String> mentors에 멘토 3명이 들어 있을 때, 마지막(세 번째) 멘토를 꺼내는 올바른 코드는?",
    "option1": "mentors.get(2)",
    "option2": "mentors.get(3)",
    "option3": "mentors.get(-1)",
    "option4": "mentors.get(\"이선배\")",
    "answerIndex": 0,
    "explanation": "List의 첫 번째 칸은 get(0)부터 시작하므로, 3명이 든 리스트의 마지막은 get(3)이 아니라 get(2)다. 섹션 7에서 '몇 번을 배워도 헷갈리는 함정'으로 강조한 부분이다."
  },
  {
    "courseSlug": "java-basics",
    "question": "Java 클래스 안의 여러 메서드 중에서 어떤 것이 '생성자'인지 알아보는 기준은 무엇인가?",
    "option1": "이름이 main이고 앞에 static이 붙어 있다",
    "option2": "반환 타입이 void이고 이름이 new로 시작한다",
    "option3": "public이 붙어 있고 매개변수를 하나도 받지 않는다",
    "option4": "클래스와 이름이 똑같고 반환 타입이 없다",
    "answerIndex": 3,
    "explanation": "섹션 5에서 배운 대로, 클래스와 이름이 같고 반환 타입이 없는 특별한 메서드가 생성자다. 생성자는 '아이디 없는 회원' 같은 반쯤 만들어진 객체가 생기지 않게 막는 안전장치 역할을 한다."
  },
  {
    "courseSlug": "java-basics",
    "question": "동아리 회계 프로그램에서 잔액 필드를 프로젝트 아무 데서나 직접 고칠 수 있어서, 버그가 나도 누가 언제 바꿨는지 추적이 안 된다. 코스에서 배운 습관대로 고치는 방법은?",
    "option1": "잔액 필드를 public으로 열어 어디서든 값을 바로 확인하고 고치게 한다",
    "option2": "잔액 필드를 private으로 잠그고, 값을 바꾸는 public 메서드 하나만 열어 검증을 그 창구에 단다",
    "option3": "잔액을 바꾸는 코드를 전부 try-catch로 감싸 사고가 나도 멈추지 않게 한다",
    "option4": "잔액 필드를 String 타입으로 바꿔 숫자가 아닌 값이 들어오면 거부하게 한다",
    "answerIndex": 1,
    "explanation": "섹션 6의 캡슐화 습관 — 데이터는 금고(private)에 잠그고 창구(public 메서드) 하나로만 만지게 하면, 값이 바뀌는 통로가 한 곳뿐이라 검사와 추적을 거기에만 달면 된다."
  },
  {
    "courseSlug": "javascript",
    "question": "F12 콘솔에 \"1\" + 1 을 붙여넣고 Enter를 치면 어떤 결과가 나올까요?",
    "option1": "숫자 2",
    "option2": "문자열 \"2\"",
    "option3": "문자열 \"11\"",
    "option4": "빨간 에러 메시지",
    "answerIndex": 2,
    "explanation": "+ 연산자는 문자열이 끼어 있으면 이어붙이기로 동작해서 \"11\"이 됩니다. 섹션 1의 '콘솔의 재미있는 함정'에서 다룬 자동 형변환 예제예요."
  },
  {
    "courseSlug": "javascript",
    "question": "const로 선언한 변수에 대한 설명으로 옳은 것은 무엇일까요?",
    "option1": "한 번 선언하면 재할당할 수 없다",
    "option2": "선언하기 전 줄에서 사용해도 에러가 나지 않는다",
    "option3": "값이 정말 바뀌어야 할 때만 골라 쓰는 이름표다",
    "option4": "옛날 문법이라 현대 코드에서는 쓰지 않는다",
    "answerIndex": 0,
    "explanation": "const는 바꿀 수 없는 이름표라서 재할당하면 에러가 납니다. 섹션 2의 기본 규칙은 '일단 전부 const로 쓰고, 정말 바뀌어야 할 때만 let'이에요. 나머지 보기는 var와 let에 대한 설명입니다."
  },
  {
    "courseSlug": "javascript",
    "question": "콘솔에서 typeof null 을 실행하면 어떤 값이 나올까요?",
    "option1": "\"null\"",
    "option2": "\"undefined\"",
    "option3": "\"boolean\"",
    "option4": "\"object\"",
    "answerIndex": 3,
    "explanation": "null인데도 \"object\"가 나오는 것은 섹션 2에서 소개한 유명한 역사적 버그입니다. null은 '여긴 비워 뒀음'이라고 사람이 일부러 붙여 둔 팻말이라는 점도 함께 기억하세요."
  },
  {
    "courseSlug": "javascript",
    "question": "코스에서 정리한 'map과 for 중 무엇을 쓸까?'의 기준으로 옳은 것은 무엇일까요?",
    "option1": "반복 횟수가 많으면 map, 적으면 for를 쓴다",
    "option2": "새 배열(결과물)이 필요하면 map, 각각에 행동만 하면 되면 for를 쓴다",
    "option3": "배열에는 map만 쓸 수 있고 for는 숫자 세기 전용이다",
    "option4": "for가 더 최신 문법이라 가능하면 map 대신 for를 쓴다",
    "answerIndex": 1,
    "explanation": "섹션 5의 정리 그대로, 배열을 새 배열로 변환할 때는 map, 변환 결과 없이 각각에 뭔가 하거나 break로 멈추고 싶을 때는 for입니다. React 화면 그리기가 map인 이유도 '배열 → JSX 배열' 변환이기 때문이에요."
  },
  {
    "courseSlug": "javascript",
    "question": "지수는 콘솔에서 document.querySelector로 학습 페이지의 배경을 핑크로 바꾸고 제목 글자도 고쳤습니다. 그런데 F5를 누르자 전부 원래대로 돌아왔어요. 가장 알맞은 이유는 무엇일까요?",
    "option1": "서버가 잘못된 변경을 감지하고 자동으로 되돌렸기 때문이다",
    "option2": "콘솔에서 바꾼 내용은 일정 시간이 지나면 저절로 사라지기 때문이다",
    "option3": "콘솔은 브라우저 속 모형(DOM)만 바꾸고, 새로고침은 서버의 원본을 다시 받아 모형을 새로 만들기 때문이다",
    "option4": "querySelector는 요소를 찾기만 할 뿐 화면을 실제로 바꾸지는 못하기 때문이다",
    "answerIndex": 2,
    "explanation": "섹션 6에서 배웠듯 콘솔의 변경은 서버 원본이 아니라 내 브라우저가 들고 있는 DOM 모형에만 적용됩니다. 새로고침은 원본을 다시 받아 모형을 처음부터 다시 만드는 일이라 마음껏 실험해도 안전해요."
  },
  {
    "courseSlug": "layout",
    "question": "코스에서 소개한 8pt 그리드의 핵심 규칙은 무엇인가요?",
    "option1": "모든 요소를 화면 가운데에 정렬한다",
    "option2": "한 화면에 카드를 8개 이하로만 배치한다",
    "option3": "모든 간격과 크기를 8의 배수로만 쓴다",
    "option4": "글꼴 크기를 항상 8pt로 고정한다",
    "answerIndex": 2,
    "explanation": "8pt 그리드의 규칙은 딱 하나, 모든 간격·크기를 8의 배수(8, 16, 24, 32...)로만 쓰는 것입니다. 13, 18, 37 같은 어중간한 숫자가 사라지면 화면 전체가 같은 박자로 정돈돼 보입니다."
  },
  {
    "courseSlug": "layout",
    "question": "코스에서 설명한 '근접성'의 원리로 옳은 것은?",
    "option1": "관련 있는 요소일수록 서로 다른 색으로 칠해 구분한다",
    "option2": "관련 있는 것은 가깝게, 관련 없는 것은 멀게 배치한다",
    "option3": "모든 요소 사이 간격을 16px로 똑같이 통일한다",
    "option4": "그룹마다 반드시 선이나 박스를 그려 경계를 표시한다",
    "answerIndex": 1,
    "explanation": "근접성은 '붙어 있으면 한 팀, 떨어져 있으면 남남'이라는 원리입니다. 선이나 박스를 긋지 않아도 간격 차이만으로 그룹 관계가 보이며, 모든 간격이 똑같으면 오히려 어디서 끊어 읽을지 알 수 없게 됩니다."
  },
  {
    "courseSlug": "layout",
    "question": "그림과 여백이 많은 랜딩 페이지에서 가장 중요한 '시작하기' 버튼을 놓기 좋은 자리는 어디인가요?",
    "option1": "화면 왼쪽 위, 로고 바로 옆",
    "option2": "화면 정중앙, 제목 바로 아래",
    "option3": "본문 첫 줄이 시작되는 왼쪽 끝",
    "option4": "화면 오른쪽 아래, 시선이 끝나는 지점",
    "answerIndex": 3,
    "explanation": "그림과 여백이 많은 화면에서 시선은 Z 모양으로 움직이며, Z의 끝인 오른쪽 아래가 시선의 종착역입니다. 코스에서 랜딩 페이지의 시작하기 버튼이 대부분 오른쪽 아래에 있는 이유로 든 내용입니다."
  },
  {
    "courseSlug": "layout",
    "question": "화면이 좁아질 때 반응형 레이아웃에서 12칸 그리드가 '접히는' 방식으로 옳은 설명은?",
    "option1": "컬럼 수는 12개 그대로 두고, 각 요소가 차지하는 칸 수를 늘린다",
    "option2": "컬럼 수 자체를 12개에서 6개, 1개로 점점 줄여 나간다",
    "option3": "데스크톱용과 모바일용 화면을 두 벌 만들어 바꿔 끼운다",
    "option4": "요소 크기는 그대로 두고 화면을 좌우로 스크롤하게 만든다",
    "answerIndex": 0,
    "explanation": "칸(컬럼) 수는 12개 그대로이고, 카드가 차지하는 칸 수만 4칸에서 6칸, 12칸으로 바뀌면서 요소가 아래로 쌓입니다. 이렇게 접히는 기준이 되는 화면 폭을 브레이크포인트라고 부릅니다."
  },
  {
    "courseSlug": "layout",
    "question": "동아리 소개 카드에 제목, 설명, 버튼을 넣었는데 세 요소 사이 간격을 전부 16px로 똑같이 줬더니 어디까지가 한 묶음인지 안 보입니다. 코스에서 배운 대로 고치는 방법은?",
    "option1": "모든 간격을 8px로 더 좁혀 세 요소를 한 덩어리로 만든다",
    "option2": "제목과 설명 사이에 굵은 구분선을 먼저 그어 경계를 만든다",
    "option3": "한 팀인 제목과 설명 사이는 좁히고, 버튼과의 간격은 확실히 넓힌다",
    "option4": "세 요소를 모두 가운데 정렬해서 새로운 기준선을 만든다",
    "answerIndex": 2,
    "explanation": "근접성의 포인트는 간격의 '차이'입니다. 피그마 실습에서도 제목과 설명 간격은 8, 설명과 버튼 간격은 24로 두어 팀 안은 좁게, 팀 사이는 넓게 만들었습니다. 선을 긋기 전에 간격 차이로 먼저 그룹을 만드는 것이 코스의 핵심입니다."
  },
  {
    "courseSlug": "linux-advanced",
    "question": "터미널에 docker라고 입력했을 때, 셸이 실행할 프로그램을 찾는 방법으로 옳은 것은?",
    "option1": "홈 디렉터리부터 하위 폴더 전체를 훑어 가장 최신 파일을 실행한다",
    "option2": "시스템에 설치된 모든 프로그램 목록에서 이름을 조회해 실행한다",
    "option3": "PATH에 적힌 폴더들을 왼쪽부터 순서대로 뒤져 처음 발견한 것을 실행한다",
    "option4": "/usr/bin 폴더 하나만 확인하고 없으면 바로 오류를 낸다",
    "answerIndex": 2,
    "explanation": "섹션 1에서 PATH는 '명령어를 찾을 때 뒤져 볼 폴더 목록'이며, 셸은 왼쪽 폴더부터 순서대로 찾아 처음 발견한 것을 실행한다고 설명한다. 그래서 command not found는 프로그램이 PATH에 등록된 폴더 밖에 있다는 뜻이다."
  },
  {
    "courseSlug": "linux-advanced",
    "question": "리다이렉션 기호 >와 >>의 차이를 바르게 설명한 것은?",
    "option1": ">는 파일 끝에 이어 붙이고, >>는 기존 내용을 덮어쓴다",
    "option2": ">는 기존 내용을 덮어쓰고, >>는 파일 끝에 이어 붙인다",
    "option3": ">는 화면 출력용이고, >>는 파일 저장 전용 기호다",
    "option4": ">는 출력을 파일로 보내고, >>는 입력을 파일에서 읽어 온다",
    "answerIndex": 1,
    "explanation": "섹션 4에서 >는 덮어쓰기(기존 내용 삭제), >>는 이어 붙이기라고 강조한다. 로그를 모을 때 >를 잘못 쓰면 어제까지의 기록이 통째로 사라진다."
  },
  {
    "courseSlug": "linux-advanced",
    "question": "크론 예약 줄 \"0 4 * * * /home/ubuntu/backup.sh\"가 실행되는 시각은 언제인가?",
    "option1": "4시간마다 한 번씩",
    "option2": "매시 4분마다",
    "option3": "매월 4일 0시에",
    "option4": "매일 새벽 4시 0분에",
    "answerIndex": 3,
    "explanation": "섹션 3의 별 5개 문법은 왼쪽부터 '분·시·일·월·요일'이다. 분=0, 시=4, 나머지는 *(아무 때나)이므로 매일 새벽 4시 0분에 백업 스크립트가 실행된다."
  },
  {
    "courseSlug": "linux-advanced",
    "question": "sudo systemctl enable caddy 명령이 하는 일은 무엇인가?",
    "option1": "서버가 부팅될 때 caddy가 자동으로 시작되도록 등록한다",
    "option2": "caddy 서비스를 즉시 시작하고 현재 상태를 출력한다",
    "option3": "caddy가 죽지 않도록 메모리를 우선 할당해 고정한다",
    "option4": "caddy의 로그를 /var/log 폴더에 기록하도록 설정한다",
    "answerIndex": 0,
    "explanation": "섹션 5에서 enable은 '부팅 시 자동 시작 등록'이라고 설명한다. 이를 빼먹으면 정전이나 EC2 재시작 후 서비스가 살아나지 않아 '사이트가 안 열려요' 사태가 난다."
  },
  {
    "courseSlug": "linux-advanced",
    "question": "몇 달 동안 잘 돌던 서버가 어느 날 이유 없이 갑자기 죽었다. 코스에서 말한 이 사고의 단골 범인과 확인 명령을 고르면?",
    "option1": "크론이 백업을 너무 자주 실행해서 — crontab -e로 주기를 줄인다",
    "option2": "로그가 계속 쌓여 디스크가 가득 차서 — df -h로 사용률을 확인한다",
    "option3": "PATH 환경변수가 초기화되어서 — echo $PATH로 값을 확인한다",
    "option4": "Caddy가 재시작을 반복해서 — systemctl stop caddy를 실행한다",
    "answerIndex": 1,
    "explanation": "섹션 6의 '디스크 꽉 참 주의보'에서 로그를 방치하면 디스크가 가득 차 멀쩡한 서비스가 갑자기 죽으며, 이것이 '잘 되던 서버가 이유 없이 죽었다'의 단골 범인이라고 설명한다. 예방책은 df -h로 디스크를 주기적으로 확인하는 습관이다."
  },
  {
    "courseSlug": "linux",
    "question": "터미널에서 pwd 명령을 입력하면 어떤 일이 일어날까요?",
    "option1": "현재 폴더 안에 있는 파일 목록을 보여준다",
    "option2": "어디에 있든 내 홈 폴더로 순간이동한다",
    "option3": "지금 내가 있는 위치(현재 작업 디렉터리)를 출력한다",
    "option4": "부모 폴더로 한 칸 위로 이동한다",
    "answerIndex": 2,
    "explanation": "pwd는 Print Working Directory의 약자로, 코스 섹션 3 '길 찾기 명령'에서 배운 \"나 지금 어디지?\"에 답하는 명령입니다. 파일 목록은 ls, 이동은 cd가 담당해요."
  },
  {
    "courseSlug": "linux",
    "question": "윈도우에서 파일을 지우는 것과 비교했을 때, 리눅스 rm 명령의 특징으로 옳은 것은?",
    "option1": "지운 파일이 휴지통으로 들어가 나중에 되살릴 수 있다",
    "option2": "휴지통 없이 즉시, 영구히 삭제된다",
    "option3": "sudo를 붙여 관리자 권한이 있어야만 실행된다",
    "option4": "폴더도 아무 옵션 없이 안의 내용까지 함께 지워진다",
    "answerIndex": 1,
    "explanation": "코스 섹션 4의 경고처럼 rm에는 휴지통이 없어 즉시·영구 삭제됩니다. 폴더를 지우려면 -r(recursive) 옵션을 따로 붙여야 하고, 지우기 전 pwd와 ls로 두 번 확인하는 습관이 필요해요."
  },
  {
    "courseSlug": "linux",
    "question": "코스에서 '실무에서 제일 많이 치는 조합'으로 소개한 tail -f app.log | grep ERROR 를 실행하면 어떻게 될까요?",
    "option1": "app.log의 앞에서 20줄 중 ERROR가 든 줄만 보여준다",
    "option2": "app.log 전체를 한 번에 좌르륵 출력하고 바로 끝난다",
    "option3": "ERROR라는 이름의 새 로그 파일을 만들어 저장한다",
    "option4": "실시간으로 쌓이는 로그 중 ERROR가 포함된 줄만 골라 보여준다",
    "answerIndex": 3,
    "explanation": "tail -f는 파일에 새 줄이 쌓이는 걸 실시간 중계하고, grep은 원하는 단어가 든 줄만 골라냅니다. 섹션 5에서 이 둘을 파이프(|)로 이은 것을 \"실시간 CCTV에 형광펜 필터 달기\"라고 비유했어요."
  },
  {
    "courseSlug": "linux",
    "question": "ls -l 결과에 -rwxr-xr-- 라고 표시된 파일이 있습니다. 이 파일에 대한 그룹(group)의 권한은?",
    "option1": "읽기와 실행은 되지만 쓰기는 안 된다",
    "option2": "읽기·쓰기·실행이 전부 가능하다",
    "option3": "읽기만 가능하고 쓰기·실행은 안 된다",
    "option4": "실행만 가능하고 읽기·쓰기는 안 된다",
    "answerIndex": 0,
    "explanation": "권한은 첫 글자(파일/폴더) 뒤로 [주인][그룹][모두] 순서로 세 글자씩 세 묶음입니다. 가운데 묶음 r-x가 그룹 권한이므로 읽기(r)·실행(x)은 되고 쓰기(w)는 안 됩니다(섹션 6)."
  },
  {
    "courseSlug": "linux",
    "question": "\"서비스가 느려요!\"라는 신고를 받은 수습 개발자가 서버에서 어떤 프로그램이 CPU를 많이 쓰는지 실시간으로 확인하려 합니다. 코스에서 '작업관리자의 터미널판'이라 소개한 명령은?",
    "option1": "cat — 파일 내용을 한 번에 전부 출력한다",
    "option2": "ssh — 암호화된 통로로 원격 서버에 접속한다",
    "option3": "top — 프로세스 목록을 실시간으로 모니터링한다",
    "option4": "chmod — 파일에 실행(x) 권한을 부여한다",
    "answerIndex": 2,
    "explanation": "섹션 7에서 top을 윈도우 작업관리자의 터미널판으로 소개하며, 서버가 느려지면 \"누가 CPU를 다 먹고 있지?\" 하고 top부터 연다고 설명합니다. ps는 목록, top은 실시간 화면이에요."
  },
  {
    "courseSlug": "mainboard-power",
    "question": "이 코스에서 메인보드를 비유한 표현으로 가장 알맞은 것은?",
    "option1": "도시 전체에 피를 돌리는 심장",
    "option2": "부품들을 서로 연결하는 도시의 도로망",
    "option3": "변두리 교통을 정리하는 교통관제센터",
    "option4": "바깥세상과 물자를 주고받는 항구와 공항",
    "answerIndex": 1,
    "explanation": "섹션 1에서 메인보드는 CPU(시청)·RAM(창고) 같은 부품들을 잇는 '도시의 도로망'으로 소개됩니다. 심장은 파워, 교통관제센터는 칩셋, 항구·공항은 후면 I/O 패널의 비유예요."
  },
  {
    "courseSlug": "mainboard-power",
    "question": "후면 패널의 파란색 USB-A 포트에 대한 설명으로 옳은 것은?",
    "option1": "USB 2.0 규격이라 키보드·마우스 전용이다",
    "option2": "방향 없이 꽂을 수 있는 요즘 표준 포트다",
    "option3": "USB 3.x 규격이라 검은색 포트보다 훨씬 빠르다",
    "option4": "내장그래픽용 화면 출력에 쓰는 포트다",
    "answerIndex": 2,
    "explanation": "섹션 3에서 파란색은 USB 3.x, 검은색은 USB 2.0이며 파란 포트가 이론상 10배 이상 빠르다고 배웠어요. 그래서 외장 SSD는 꼭 파란 포트에 꽂아야 제 속도가 납니다."
  },
  {
    "courseSlug": "mainboard-power",
    "question": "파워(PSU)를 고를 때 '정격 500W'라는 표기가 뜻하는 것은?",
    "option1": "500W를 계속, 안정적으로 낼 수 있다는 보증이다",
    "option2": "순간적으로만 500W까지 낼 수 있다는 뜻이다",
    "option3": "벽 콘센트에서 항상 500W를 끌어온다는 뜻이다",
    "option4": "부품 소비 전력의 합계가 500W여야 한다는 뜻이다",
    "answerIndex": 0,
    "explanation": "섹션 5에서 '정격'은 그 출력을 지속적으로 안정하게 낼 수 있다는 보증이고, '최대 출력'은 순간만 가능한 말장난이라고 배웠어요. 싸구려 파워가 최대 출력을 크게 적는 눈속임을 쓰기 때문에 반드시 정격 기준으로 골라야 합니다."
  },
  {
    "courseSlug": "mainboard-power",
    "question": "전원을 켜면 BIOS/UEFI가 가장 먼저 수행하는 POST 단계에서 하는 일은?",
    "option1": "Windows 부트로더를 불러와 OS를 시작한다",
    "option2": "어느 저장장치에서 부팅할지 순서를 정한다",
    "option3": "CPU 온도에 맞춰 팬 속도를 자동으로 조절한다",
    "option4": "CPU·RAM 등 부품이 제대로 있는지 점호를 한다",
    "answerIndex": 3,
    "explanation": "섹션 4에서 POST는 아침 조회의 출석 체크처럼 부품을 점호하는 단계이고, 문제가 있으면 비프음이나 LED로 알려 준다고 배웠어요. 부팅 순서 결정과 OS 바통 터치는 그다음 단계입니다."
  },
  {
    "courseSlug": "mainboard-power",
    "question": "친구가 그래픽카드를 새로 장착했는데 모니터에 화면이 안 나온다고 합니다. 코스에서 '단골 실수 1위'로 꼽은, 가장 먼저 확인해야 할 것은?",
    "option1": "케이스가 ATX 폼팩터를 지원하는지 확인한다",
    "option2": "UEFI에 들어가 부팅 순서를 바꾸고 저장한다",
    "option3": "모니터 케이블이 보드가 아닌 그래픽카드 쪽 HDMI/DP에 꽂혔는지 본다",
    "option4": "파워의 80PLUS 등급이 Gold 이상인지 확인한다",
    "answerIndex": 2,
    "explanation": "섹션 3에서 그래픽카드를 꽂았다면 모니터는 보드의 HDMI가 아니라 그래픽카드 쪽 HDMI/DP에 연결해야 하며, 이것이 단골 실수 1위라고 배웠어요. 보드 포트는 내장그래픽용 출력이기 때문입니다."
  },
  {
    "courseSlug": "memory-storage",
    "question": "'Random Access Memory'라는 이름이 붙은 이유로 코스에서 설명한 것은 무엇인가?",
    "option1": "데이터를 무작위 순서로 뒤섞어 저장해서 해킹에 강하기 때문",
    "option2": "어느 주소(칸)에 있든 똑같은 속도로 바로 접근할 수 있기 때문",
    "option3": "전원이 꺼질 때 데이터가 무작위로 사라지는 성질이 있기 때문",
    "option4": "CPU가 남는 시간에 임의의 칸을 골라 검사하는 방식이기 때문",
    "answerIndex": 1,
    "explanation": "RAM은 번호(주소)가 붙은 사물함 벽과 같아서 1번부터 차례로 뒤질 필요 없이 어느 칸이든 같은 속도로 즉시 꺼낼 수 있고, 그래서 Random Access(임의 접근) Memory라고 부른다(섹션 1)."
  },
  {
    "courseSlug": "memory-storage",
    "question": "코스의 비유에서 RAM을 '책상', SSD를 '책장'에 비유한 핵심 이유는 무엇인가?",
    "option1": "책상은 크기를 늘릴 수 없지만 책장은 계속 늘릴 수 있기 때문",
    "option2": "책상은 여럿이 나눠 쓰고 책장은 한 사람만 쓰는 공간이기 때문",
    "option3": "책상 위는 자주 정리해야 하지만 책장은 자동으로 정리되기 때문",
    "option4": "책상은 빠르지만 전원이 꺼지면 치워지고, 책장은 느리지만 그대로 남기 때문",
    "answerIndex": 3,
    "explanation": "RAM은 손만 뻗으면 닿는 책상처럼 빠르지만 휘발성이라 전원이 꺼지면 전부 사라지고, SSD는 걸어가야 하는 책장처럼 느리지만 비휘발성이라 내용이 그대로 남는다(섹션 1)."
  },
  {
    "courseSlug": "memory-storage",
    "question": "같은 16GB 용량이라도 '16GB 1개'보다 '8GB 2개'를 짝지어 꽂는 쪽이 보통 더 나은 이유는?",
    "option1": "듀얼채널이 활성화되어 CPU와 램 사이 전송 대역폭이 최대 2배가 되기 때문",
    "option2": "램 2개가 서로 데이터를 백업해 줘서 내용이 사라질 위험이 줄기 때문",
    "option3": "램 하나당 발열이 절반으로 줄어 클럭 속도가 자동으로 올라가기 때문",
    "option4": "나중에 증설할 때 쓸 수 있는 빈 슬롯의 여유가 더 많아지기 때문",
    "answerIndex": 0,
    "explanation": "램을 같은 색 슬롯에 짝지어 꽂으면 CPU와 램 사이가 왕복 2차선 도로처럼 되는 듀얼채널이 활성화되어 대역폭이 최대 2배가 된다(섹션 3). 코스는 오히려 증설 여유는 줄어든다고 설명한다."
  },
  {
    "courseSlug": "memory-storage",
    "question": "컴퓨터가 몹시 느려졌는데 CPU 사용률은 낮고, 디스크 사용률은 100%, 램 사용률은 90%를 넘는다. 코스에 따르면 가장 유력한 원인은?",
    "option1": "CPU 코어 수가 부족해서 작업들이 계산 순서를 기다리는 상태",
    "option2": "SSD의 TBW 수명이 다해서 읽기 속도가 급격히 떨어진 상태",
    "option3": "램 부족으로 스왑이 일어나 램과 SSD 창고 사이를 계속 왕복하는 상태",
    "option4": "듀얼채널이 풀려서 램의 전송 대역폭이 절반으로 줄어든 상태",
    "answerIndex": 2,
    "explanation": "세 증상이 동시에 나타나면 범인은 십중팔구 램 부족(스왑)이다. 수백~수천 배 느린 SSD의 스왑 공간을 계속 왕복하느라 정작 일을 못 하고, CPU는 데이터가 도착하길 기다리며 놀게 된다(섹션 6)."
  },
  {
    "courseSlug": "memory-storage",
    "question": "16GB 노트북에서 IntelliJ, React 개발 서버, Spring Boot, Docker, 브라우저 탭 30개를 동시에 띄우자 창을 전환할 때마다 1~2초씩 멈칫한다. 코스의 실습대로 '스왑이 지금 일어나는 중인지'를 실시간으로 확인하려면 리소스 모니터(resmon)에서 무엇을 봐야 하는가?",
    "option1": "[CPU] 탭의 코어별 사용률 그래프가 치솟는지",
    "option2": "[메모리] 탭의 '하드 폴트/초' 그래프가 치솟는지",
    "option3": "[디스크] 탭의 파일별 읽기 속도 목록이 비어 있는지",
    "option4": "[네트워크] 탭의 프로세스별 전송량이 줄어드는지",
    "answerIndex": 1,
    "explanation": "하드 폴트는 '찾는 데이터가 램에 없어서 디스크(스왑)까지 다녀온 횟수'이므로, 이 그래프가 계속 치솟으면 섹션 6에서 배운 스왑 연쇄가 지금 일어나고 있다는 뜻이다(섹션 7 실습)."
  },
  {
    "courseSlug": "mobile-design",
    "question": "코스에서 제시한 '눌리는 모든 요소'의 터치 타깃 최소 크기는 얼마인가?",
    "option1": "24×24px — 아이콘 표준 크기에 맞춰서",
    "option2": "44×44px — 어른 엄지 접촉면(약 10mm)을 기준으로",
    "option3": "16×16px — 본문 최소 글자 크기와 통일해서",
    "option4": "60×60px — 태블릿 화면까지 고려한 여유 크기",
    "answerIndex": 1,
    "explanation": "섹션 1 '엄지의 법칙'에서 마우스 커서는 바늘 끝이지만 엄지는 도장이라며, 눌리는 요소는 최소 44×44px을 확보해야 한다고 설명한다. 어른 엄지 접촉면 약 10mm가 화면에서 대략 44px 이상에 해당하기 때문이다."
  },
  {
    "courseSlug": "mobile-design",
    "question": "모바일 우선(Mobile First) 방식의 CSS 작성 순서로 옳은 것은?",
    "option1": "큰 화면 스타일을 기본값으로 두고 max-width 미디어 쿼리로 좁은 화면에서 덜어낸다",
    "option2": "화면 크기별로 CSS 파일을 따로 만들어 기기마다 다른 파일을 불러온다",
    "option3": "좁은 화면 스타일을 기본값으로 두고 min-width 미디어 쿼리로 넓은 화면에서 더한다",
    "option4": "JS로 화면 폭을 감지해 데스크톱용과 모바일용 컴포넌트를 통째로 교체한다",
    "answerIndex": 2,
    "explanation": "섹션 2에서 모바일 우선은 좁은 화면용 CSS를 기본값으로 쓰고 화면이 넓어질 때 min-width로 스타일을 '더해 가는' 방식이라고 설명한다. max-width로 '빼며' 내려가는 데스크톱 우선과 정반대 순서다."
  },
  {
    "courseSlug": "mobile-design",
    "question": "index.html에 viewport 메타 태그가 없으면 모바일 브라우저는 어떻게 동작하는가?",
    "option1": "PC용 페이지로 가정하고 980px짜리 가상 화면에 그린 뒤 축소해서 보여준다",
    "option2": "페이지 렌더링을 중단하고 데스크톱 전용 화면으로 리다이렉트한다",
    "option3": "기기의 실제 폭을 자동으로 감지해 처음부터 1:1 크기로 그려 준다",
    "option4": "화면을 가로 모드로 강제 회전시켜 부족한 폭을 확보한다",
    "answerIndex": 0,
    "explanation": "섹션 3에서 viewport 태그가 없으면 모바일 브라우저가 '이건 PC용 페이지겠지' 하고 980px 가상 화면에 그린 뒤 축소해 보여줘서 글씨가 개미만 해진다고 설명한다. 그래서 width=device-width, initial-scale=1 한 줄이 필요하다."
  },
  {
    "courseSlug": "mobile-design",
    "question": "코스가 말하는 모바일 제스처 설계의 '정석'에 가장 가까운 것은?",
    "option1": "다른 앱과 차별화되도록 우리 서비스만의 새로운 제스처를 발명한다",
    "option2": "화면을 깔끔하게 만들기 위해 버튼을 없애고 스와이프로만 기능을 제공한다",
    "option3": "제스처는 배우기 어려우므로 모바일 웹에서는 아예 사용하지 않는다",
    "option4": "기존 관습을 따르되, 제스처는 보이는 버튼과 짝을 이루는 지름길로만 제공한다",
    "answerIndex": 3,
    "explanation": "섹션 4의 두 규칙이다 — 사용자의 근육 기억인 관습을 따를 것, 그리고 제스처는 눈에 보이지 않으니 반드시 보이는 버튼과 짝을 이뤄 '아는 사람의 지름길'로만 쓸 것. 스와이프로만 가능한 기능은 없는 거나 마찬가지라고 설명한다."
  },
  {
    "courseSlug": "mobile-design",
    "question": "수습 A가 개발자 도구 기기 툴바(375px)로 플랫폼을 점검하다가, 넓은 표 하나 때문에 페이지 전체가 좌우로 덜렁거리는 것을 발견했다(FE-06 티켓과 같은 증상). 코스에서 배운 정석 해결법은?",
    "option1": "표의 글자 크기를 확 줄여 375px 폭 안에 표 전체가 들어가게 만든다",
    "option2": "표를 이미지로 캡처해 넣어서 폭이 넘치는 문제 자체를 없앤다",
    "option3": "overflow-x: auto를 준 컨테이너 안에 표를 가둬 그 안에서만 스크롤되게 한다",
    "option4": "body에 width: 600px을 고정해 모든 기기에서 같은 폭으로 보이게 한다",
    "answerIndex": 2,
    "explanation": "섹션 5에서 표처럼 원래 넓은 콘텐츠는 페이지를 늘리는 대신 overflow-x: auto를 준 컨테이너 안에서만 스크롤되게 가두는 것이 정석이라고 설명한다. 범인 요소 하나가 페이지 전체를 덜렁거리게 만드는 '의도치 않은 가로 스크롤'의 표준 처방이다."
  },
  {
    "courseSlug": "net-tools",
    "question": "ping 명령이 알려 주는 두 가지 핵심 정보는 무엇인가?",
    "option1": "상대 서버의 운영체제 종류와 열려 있는 포트 목록",
    "option2": "상대 서버의 생존 여부와 왕복 응답 시간(ms)",
    "option3": "내 PC의 IPv4 주소와 기본 게이트웨이 주소",
    "option4": "목적지까지 거치는 라우터(홉) 목록과 구간별 속도",
    "answerIndex": 1,
    "explanation": "코스에서 ping은 서버 문 앞에서 '노크'하고 대답이 오기까지의 왕복 시간을 재는 명령으로 소개된다. 대답 여부로 생존을 확인하고, 시간=8ms 같은 숫자로 응답 속도를 측정한다."
  },
  {
    "courseSlug": "net-tools",
    "question": "코스는 nslookup을 '전화번호부에 직접 전화를 걸어 물어보는 것'에 비유한다. 여기서 전화번호부가 가리키는 것은?",
    "option1": "우리 집의 정문 역할을 하는 공유기(기본 게이트웨이)",
    "option2": "서버 현관에서 응대하는 안내데스크(Caddy)",
    "option3": "도메인 이름을 IP 숫자로 바꿔 주는 DNS",
    "option4": "PC에 IP 주소를 자동으로 나눠 주는 DHCP",
    "answerIndex": 2,
    "explanation": "컴퓨터는 이름이 아니라 IP 숫자로 서로를 찾기 때문에, 이름을 번호로 바꿔 주는 전화번호부가 필요하다. 코스에서 그 전화번호부가 바로 DNS이고, nslookup은 거기에 직접 질문하는 명령이다."
  },
  {
    "courseSlug": "net-tools",
    "question": "netstat -ano 출력에서 상태(State)가 LISTENING인 줄은 무엇을 의미하는가?",
    "option1": "요청이 오면 받으려고 대기 중인 서버 프로그램이 있다",
    "option2": "상대 서버와 지금 데이터를 주고받는 통화 중인 연결이다",
    "option3": "연결이 끊겨서 재접속을 기다리고 있는 오류 상태다",
    "option4": "방화벽이 그 포트로 들어오는 접속을 차단하고 있다",
    "answerIndex": 0,
    "explanation": "코스에서 LISTENING은 \"전화 오면 받을게요\" 하고 대기 중인 서버 프로그램으로 설명된다. 예시에서 :5173(Vite 개발 서버)과 :5432(PostgreSQL)가 이 상태였고, 지금 통화 중인 연결은 ESTABLISHED다."
  },
  {
    "courseSlug": "net-tools",
    "question": "tracert 실행 결과 중간 홉 몇 개가 '* * *'로 나왔지만 마지막 줄에서 목적지에 도착했다. 코스에 따른 올바른 해석은?",
    "option1": "경로 중간이 끊긴 것이므로 통신사에 신고해야 한다",
    "option2": "패킷 손실이 생긴 것이므로 회선이 불안정한 상태다",
    "option3": "DNS 조회가 실패한 것이므로 nslookup으로 확인해야 한다",
    "option4": "그 라우터가 보안상 대답을 생략했을 뿐 경로는 정상이다",
    "answerIndex": 3,
    "explanation": "코스는 보안상 대답을 생략하는 라우터가 많으니 중간의 '* * *'에 당황하지 말라고 강조한다. 진짜 문제는 어느 홉 이후로 전부 끊기고 목적지에 도착하지 못하는 경우다."
  },
  {
    "courseSlug": "net-tools",
    "question": "동아리방 PC에서 웹사이트가 안 열린다. ipconfig로 내 IP가 정상임을 확인했고 게이트웨이 ping도 성공, ping 8.8.8.8도 성공했지만 nslookup naver.com은 실패했다. 진단 순서도에 따른 원인은?",
    "option1": "공유기 문제이므로 공유기를 재부팅해야 한다",
    "option2": "집 밖 회선 문제이므로 통신사에 연락해야 한다",
    "option3": "회선은 정상인데 DNS가 고장 난 상태다",
    "option4": "네이버 서버가 아픈 것이므로 기다리면 된다",
    "answerIndex": 2,
    "explanation": "순서도의 핵심 분기점: IP 숫자(8.8.8.8)로는 통신이 되는데 이름 찾기(nslookup)가 실패하면 '회선 O, DNS X'로 원인을 쪼갤 수 있다. 코스에서 \"인터넷은 되는데 사이트만 안 열림\"의 단골 범인으로 소개된 경우다."
  },
  {
    "courseSlug": "network",
    "question": "랜선(UTP 케이블) 속 구리선 8가닥이 두 가닥씩 꼬여 있는 이유는 무엇일까요?",
    "option1": "구리선 길이를 늘려서 신호가 더 멀리까지 가게 하려고",
    "option2": "케이블을 더 튼튼하게 만들어 잘 끊어지지 않게 하려고",
    "option3": "전자파 간섭이 두 선에 거의 똑같이 들어와 서로 상쇄되게 하려고",
    "option4": "8가닥의 색 순서를 눈으로 구분하기 쉽게 하려고",
    "answerIndex": 2,
    "explanation": "두 선을 꼬아 놓으면 전자파 간섭이 두 선에 거의 똑같이 들어와 서로 상쇄됩니다. 섹션 1에서 '시끄러운 교실에서 같은 소음을 듣는 두 사람' 비유로 설명한 트위스티드 페어의 원리예요."
  },
  {
    "courseSlug": "network",
    "question": "코스에서 DNS를 '인터넷의 전화번호부'라고 불렀습니다. DNS가 하는 일로 알맞은 것은?",
    "option1": "도메인 이름을 물어보면 그에 해당하는 IP 주소를 알려 준다",
    "option2": "큰 데이터를 패킷으로 쪼개고 도착지에서 순서대로 조립한다",
    "option3": "집 안에 새로 접속한 기기마다 사설 IP를 자동으로 나눠 준다",
    "option4": "브라우저와 서버가 주고받는 대화를 암호화해 엿보지 못하게 한다",
    "answerIndex": 0,
    "explanation": "사람은 이름(도메인)을 외우고 컴퓨터는 번호(IP)로 통신하기 때문에, DNS가 '이름 → 번호'를 찾아 주는 전화번호부 역할을 합니다. 섹션 5의 핵심 개념이에요."
  },
  {
    "courseSlug": "network",
    "question": "192.168.0.5 같은 사설 IP만으로는 바깥 인터넷에서 우리 집 PC를 직접 찾아올 수 없습니다. 그 이유로 알맞은 것은?",
    "option1": "사설 IP는 전송 속도가 느려서 외부 통신에는 쓸 수 없기 때문에",
    "option2": "사설 IP는 숫자 범위가 0~255를 벗어나 규격에 맞지 않기 때문에",
    "option3": "사설 IP는 기기를 켤 때마다 완전히 다른 주소로 바뀌기 때문에",
    "option4": "사설 IP는 집 네트워크 안에서만 통하는 '호수'라서 옆 집에도 같은 주소가 있을 수 있기 때문에",
    "answerIndex": 3,
    "explanation": "섹션 2의 아파트 비유처럼 사설 IP는 '302호' 같은 호수여서 우리 아파트 안에서만 통하고, 옆 아파트에도 같은 302호가 있습니다. 바깥에서 찾아오려면 전 세계에서 유일한 공인 IP(도로명 주소)가 필요해요."
  },
  {
    "courseSlug": "network",
    "question": "데이터를 보낼 때 IP 주소만으로는 부족하고 포트 번호가 함께 필요한 이유는 무엇일까요?",
    "option1": "포트 번호가 있어야 데이터 전송 속도가 훨씬 빨라지기 때문에",
    "option2": "한 서버(건물) 안에 여러 프로그램(방)이 있어서 어느 프로그램으로 갈지 정해야 하기 때문에",
    "option3": "IP 주소는 나라 안에서만 통하고 포트 번호가 국제 주소 역할을 하기 때문에",
    "option4": "포트 번호로 데이터를 암호화해서 다른 사람이 엿보지 못하게 하기 때문에",
    "answerIndex": 1,
    "explanation": "섹션 3의 비유대로 IP는 건물 주소, 포트는 방 번호입니다. 한 서버에 웹(443), Gitea(3000), DB(5432)처럼 여러 서비스가 살기 때문에 '어느 건물의 몇 호'까지 합쳐져야 데이터가 정확한 프로그램에 도착해요."
  },
  {
    "courseSlug": "network",
    "question": "친구와 실시간 영상통화를 하는데 화면이 잠깐 뭉개졌다가, 끊긴 부분을 다시 받지 않고 곧바로 최신 화면으로 이어졌습니다. 이 서비스가 썼을 가능성이 높은 배송 방식과 그 이유는?",
    "option1": "TCP — 상자마다 번호를 붙여 반드시 순서대로 조립해야 하므로",
    "option2": "TCP — 분실된 패킷을 다시 보내 한 조각도 빠지지 않게 하므로",
    "option3": "UDP — 지나간 화면을 재전송받는 것보다 최신 데이터를 빨리 받는 게 중요하므로",
    "option4": "UDP — 보내기 전에 3-way 악수로 연결을 맺어 확실하게 전달하므로",
    "answerIndex": 2,
    "explanation": "섹션 6에서 게임·영상통화처럼 '좀 깨져도 빠른 게 중요할 때'는 UDP를 쓴다고 배웠어요. 0.1초 전 화면은 지금 다시 받아 봐야 이미 과거라서, 분실된 건 버리고 최신 것을 받는 편이 낫습니다."
  },
  {
    "courseSlug": "ops-basics",
    "question": "장애의 3대 신호 중 사용자에게 '서버 오류' 페이지가 보이는 500 에러가 떴을 때, 코스가 꼽은 흔한 범인 후보는 무엇인가?",
    "option1": "DB 느린 쿼리나 트래픽 폭주",
    "option2": "도메인이나 인증서 설정 문제",
    "option3": "백엔드 예외(코드 버그)나 DB 연결 끊김",
    "option4": "서버 다운이나 컨테이너 죽음",
    "answerIndex": 2,
    "explanation": "500 에러는 요청은 도착했는데 처리 중 예외가 터진 상태로, 범인은 대부분 백엔드 코드나 DB 연결이다. DB 느린 쿼리는 '느림', 서버 다운이나 도메인·인증서 문제는 '접속 불가'의 범인 후보다."
  },
  {
    "courseSlug": "ops-basics",
    "question": "코스에서 배운 '원인 좁히기'는 정전 때 동네 전체 → 두꺼비집 → 방 스위치 → 전구 순서로 확인하듯 바깥에서 안쪽으로 진행한다. 올바른 확인 순서는?",
    "option1": "DB → 앱 로그 → 컨테이너 → 서버",
    "option2": "서버 → 컨테이너 → 앱 로그 → DB",
    "option3": "컨테이너 → 서버 → DB → 앱 로그",
    "option4": "앱 로그 → DB → 서버 → 컨테이너",
    "answerIndex": 1,
    "explanation": "섹션 3의 핵심은 큰 범위에서 작은 범위로 좁히는 것이다. 서버 생존(ping·SSH) → 컨테이너(docker ps) → 앱 로그(docker logs) → DB 순서로 확인해야 헛수고가 없다."
  },
  {
    "courseSlug": "ops-basics",
    "question": "docker ps를 쳤더니 어떤 컨테이너의 STATUS가 'Exited (137)'로 나왔다. 코스 설명에 따르면 137 코드가 자주 의미하는 것은?",
    "option1": "정상적으로 가동을 마치고 종료됐다는 뜻",
    "option2": "죽었다 살아나기를 반복하는 중이라는 뜻",
    "option3": "디스크 용량 부족으로 기록이 멈췄다는 뜻",
    "option4": "메모리 부족(OOM)으로 강제 종료됐다는 뜻",
    "answerIndex": 3,
    "explanation": "코스의 docker ps STATUS 읽는 법에서 Exited (137)은 메모리 부족(OOM)으로 강제 종료됐을 때 자주 보이는 코드라고 설명한다. 죽었다 살아나기를 반복하는 상태는 'Restarting'으로 표시된다."
  },
  {
    "courseSlug": "ops-basics",
    "question": "'재시작의 정석'에 따르면 docker restart 명령을 치기 전에 가장 먼저 해야 하는 일은 무엇인가?",
    "option1": "docker logs로 로그를 파일로 저장해 증거부터 확보한다",
    "option2": "docker compose down으로 전체 컨테이너를 내린다",
    "option3": "curl로 사이트가 200을 반환하는지 확인한다",
    "option4": "최근 백업 파일로 DB를 즉시 복구한다",
    "answerIndex": 0,
    "explanation": "재시작은 병을 고치는 동시에 현장을 치워 버리는 행동이라, 재시작 전에 로그부터 저장하지 않으면 원인을 영영 모르게 된다. curl 확인은 재시작 후 마지막 단계이고, 전체를 내리는 명령은 코스가 '절대 하지 말 것'으로 경고한다."
  },
  {
    "courseSlug": "ops-basics",
    "question": "아침에 확인해 보니 학습 플랫폼 사이트 자체는 잘 열리는데, 로그인 버튼만 누르면 500 에러가 난다. 코스의 원인 좁히기 논리대로라면 가장 먼저 무엇을 해야 할까?",
    "option1": "ping과 SSH로 서버가 살아 있는지부터 확인한다",
    "option2": "도메인과 인증서 설정이 꼬였는지 확인한다",
    "option3": "서버와 Caddy는 무죄로 보고 백엔드(Spring Boot) 로그부터 확인한다",
    "option4": "제1용의자인 최근 배포 내역을 확인하고 이전 버전으로 롤백한다",
    "answerIndex": 2,
    "explanation": "사이트가 '열린다'는 사실 자체가 서버와 Caddy는 살아 있다는 알리바이가 되어 용의선상에서 빠진다. 시나리오 B처럼 범위가 이미 백엔드·DB로 좁혀졌으므로 백엔드 로그부터 보는 것이 순서다."
  },
  {
    "courseSlug": "os",
    "question": "코스에서 컴퓨터를 건물에 비유할 때, 운영체제(OS)는 무엇에 해당한다고 설명했나요?",
    "option1": "건물에 들어와 사는 입주 손님",
    "option2": "전기·수도 같은 건물 시설",
    "option3": "앱과 하드웨어 사이의 총괄 매니저",
    "option4": "건물을 처음 지을 때 쓰는 설계도",
    "answerIndex": 2,
    "explanation": "섹션 1에서 CPU·메모리·디스크는 건물 시설, 앱은 입주 손님, 그 사이에서 자원을 배분하고 질서를 지키는 총괄 매니저가 운영체제라고 비유했습니다."
  },
  {
    "courseSlug": "os",
    "question": "코스의 '레시피와 냄비' 비유에 따르면, 프로그램과 프로세스의 차이로 옳은 것은 무엇인가요?",
    "option1": "프로그램은 실행 중인 상태이고, 프로세스는 디스크에 저장된 파일이다",
    "option2": "프로그램은 디스크에 저장된 파일이고, 프로세스는 실행되어 CPU와 메모리를 쓰며 살아 움직이는 상태다",
    "option3": "둘은 같은 말인데 크롬에서는 프로그램, 윈도우에서는 프로세스라고 부른다",
    "option4": "프로그램은 여러 개 띄울 수 있지만, 프로세스는 컴퓨터에 항상 하나만 존재한다",
    "answerIndex": 1,
    "explanation": "섹션 3에서 프로그램은 디스크에 가만히 있는 레시피(파일), 프로세스는 그 레시피로 지금 요리 중인 냄비라고 설명했습니다. 크롬 하나로도 프로세스는 여러 개 생길 수 있어요."
  },
  {
    "courseSlug": "os",
    "question": "전원 버튼을 누른 뒤 로그인 화면이 나올 때까지, 코스에서 배운 부팅 4단계의 올바른 순서는 무엇인가요?",
    "option1": "펌웨어(UEFI) → 부트로더 → 커널 적재 → 서비스·로그인 화면",
    "option2": "부트로더 → 펌웨어(UEFI) → 커널 적재 → 서비스·로그인 화면",
    "option3": "커널 적재 → 펌웨어(UEFI) → 부트로더 → 서비스·로그인 화면",
    "option4": "펌웨어(UEFI) → 커널 적재 → 부트로더 → 서비스·로그인 화면",
    "answerIndex": 0,
    "explanation": "섹션 4의 부팅 릴레이: 펌웨어가 하드웨어 점호(POST)를 하고, 부트로더가 디스크에서 커널을 찾아 메모리에 올리고, 커널이 드라이버와 함께 시동을 건 뒤 서비스들이 켜지며 로그인 화면이 나옵니다."
  },
  {
    "courseSlug": "os",
    "question": "크롬이 갑자기 죽어도 카톡은 멀쩡하게 계속 돌아갑니다. 코스에서 배운 내용 중 이것을 가능하게 하는 것은 무엇인가요?",
    "option1": "OS가 크롬에게만 CPU 시간을 몰아서 배분해 두었기 때문",
    "option2": "카톡은 RAM이 아니라 디스크의 페이지 파일에서 직접 실행되기 때문",
    "option3": "두 앱이 항상 서로 다른 CPU 코어에서만 실행되도록 정해져 있기 때문",
    "option4": "OS가 프로세스마다 독립된 가상 주소 공간을 주어 서로의 메모리를 침범하지 못하게 격리하기 때문",
    "answerIndex": 3,
    "explanation": "섹션 5(가상 메모리)에서 OS는 각 프로세스에게 자기만의 가상 주소 공간을 주므로 한 앱이 다른 앱의 메모리를 읽거나 망가뜨릴 수 없다고 했습니다. 이 '격리' 덕분에 크롬이 죽어도 카톡은 멀쩡해요."
  },
  {
    "courseSlug": "os",
    "question": "한 수습 개발자가 '귀찮아서' 몇 달째 OS 업데이트를 미루고 있습니다. 코스의 비유에 따르면 이 PC는 어떤 집과 같은 상태인가요?",
    "option1": "오래된 가전을 바꾸지 않아 전기 효율이 떨어진 집",
    "option2": "창문 청소를 미뤄서 바깥 전망이 흐려진 집",
    "option3": "도어락 비밀번호가 인터넷에 공개됐는데도 바꾸지 않은 집",
    "option4": "우편함이 가득 차서 새로운 소식을 받지 못하는 집",
    "answerIndex": 2,
    "explanation": "섹션 6에서 업데이트의 절반 이상은 보안 패치이고, 취약점은 발견되는 순간 전 세계에 공개되므로 패치 안 한 컴퓨터는 '도어락 비밀번호가 인터넷에 올라왔는데 안 바꾼 집'과 같다고 비유했습니다."
  },
  {
    "courseSlug": "peripherals",
    "question": "노트북 스펙표의 램 항목에 '온보드'라고 적혀 있을 때, 이것이 뜻하는 것은?",
    "option1": "램이 별도 슬롯에 꽂혀 있어 언제든 뺐다 꽂을 수 있다",
    "option2": "램이 최신 규격이라 같은 용량이라도 속도가 더 빠르다",
    "option3": "램 칩이 메인보드에 납땜되어 있어 나중에 용량을 늘릴 수 없다",
    "option4": "램의 일부를 SSD처럼 저장 공간으로 나눠 쓸 수 있다",
    "answerIndex": 2,
    "explanation": "코스 2번 섹션(스펙표 읽기)에서 온보드 램은 메인보드에 납땜된 것이라 증설이 불가능하고, '산 날의 용량이 평생 용량'이 된다고 설명합니다. 그래서 처음부터 여유 있는 용량을 골라야 합니다."
  },
  {
    "courseSlug": "peripherals",
    "question": "코스가 USB의 대혼란을 정리하며 딱 하나만 기억하라고 강조한 핵심 원칙은?",
    "option1": "USB-C 모양 케이블은 모두 USB4 속도를 낸다",
    "option2": "커넥터 '모양'과 전송 '속도'는 완전히 별개의 개념이다",
    "option3": "파란색 단자는 USB 2.0, 검은색 단자는 USB 3.x를 뜻한다",
    "option4": "USB-A는 위아래 구분이 없어 어느 방향으로든 꽂힌다",
    "answerIndex": 1,
    "explanation": "6번 섹션(USB 규격 정리)의 핵심입니다. USB-C는 커넥터 모양 이름이고 USB 3.x·USB4는 속도 규격 이름이라, C 모양인데 속도는 2.0인 저가 케이블도 있습니다."
  },
  {
    "courseSlug": "peripherals",
    "question": "코스가 제시한 '개발용 모니터' 스펙의 우선순위로 옳은 것은?",
    "option1": "주사율 > 해상도 > 패널",
    "option2": "패널 > 주사율 > 해상도",
    "option3": "주사율 > 패널 > 해상도",
    "option4": "해상도 > 패널(IPS) > 주사율",
    "answerIndex": 3,
    "explanation": "3번 섹션에서 개발용 우선순위는 해상도 > 패널(IPS) > 주사율로, 게임용과 정반대라고 설명합니다. 코드는 결국 글자라서 글자가 많이·또렷하게 보이는 게 최고이기 때문입니다."
  },
  {
    "courseSlug": "peripherals",
    "question": "Docker 컨테이너 여러 개와 무거운 IDE를 동시에 돌리는 개발자에게 코스가 추천하는 CPU 접미사와 그 이유는?",
    "option1": "H — 고성능·고발열 타입이라 빌드와 Docker 작업이 빠르다",
    "option2": "U — 저전력 타입이라 컨테이너를 오래 돌리기에 유리하다",
    "option3": "U와 H는 성능 차이가 없고 배터리 용량만 다르다",
    "option4": "접미사는 출시 연도를 뜻하므로 성능과는 관련이 없다",
    "answerIndex": 0,
    "explanation": "2번 섹션의 CPU 접미사 해독법에 따르면 H는 High performance(고성능·고발열)로 빌드·Docker가 빠르고, U는 저전력·저발열이라 가벼운 코딩엔 충분하지만 무거운 빌드는 느립니다."
  },
  {
    "courseSlug": "peripherals",
    "question": "수습 A가 아침에 출근해 도킹 스테이션에 C 케이블 한 가닥으로 노트북을 연결했다. 노트북 충전은 되는데 독에 꽂힌 외부 모니터 화면만 안 나온다. 코스 내용상 가장 먼저 의심해야 할 원인은?",
    "option1": "독의 전원 공급 와트(W)가 노트북 충전기보다 낮다",
    "option2": "충전 전용 C 케이블이라 영상 신호(DP Alt Mode)를 전달하지 못한다",
    "option3": "모니터 패널이 IPS가 아니라 VA여서 독의 신호와 호환되지 않는다",
    "option4": "모니터가 4K여서 배율(스케일링) 설정을 마치기 전에는 화면이 켜지지 않는다",
    "answerIndex": 1,
    "explanation": "6번 섹션의 실무 사례 그대로입니다. '독에 모니터를 꽂았는데 화면이 안 나와요'의 범인은 십중팔구 충전 전용 C 케이블 — 겉모습은 같아도 영상 신호(DP Alt Mode)를 못 나르는 케이블이 있으니, 살 때 Gen 숫자와 영상 출력 지원 문구를 읽어야 합니다."
  },
  {
    "courseSlug": "portfolio",
    "question": "이 코스에서 말하는 '좋은 포트폴리오'의 정의로 가장 알맞은 것은?",
    "option1": "완성작을 최대한 많이 모아 보여주는 전시회",
    "option2": "사용한 기술 스택을 빠짐없이 나열한 목록",
    "option3": "내가 문제를 어떻게 푸는 사람인지 보여주는 과정의 이야기",
    "option4": "수상 경력과 자격증을 한곳에 정리한 증명서 모음",
    "answerIndex": 2,
    "explanation": "코스는 포트폴리오를 완성작 전시회가 아니라 '문제 → 고민 → 해결 → 배움'의 과정을 담은 이야기책으로 정의한다. 기능 목록은 검색하면 나오지만 과정의 이야기는 나만 쓸 수 있기 때문이다."
  },
  {
    "courseSlug": "portfolio",
    "question": "STAR 틀에서 '가장 길게 써야 하고, 읽는 사람이 제일 궁금해하는' 칸은 무엇인가?",
    "option1": "S(상황) — 어떤 프로젝트에서 무슨 역할이었는지",
    "option2": "A(행동) — 내가 구체적으로 무엇을 했는지",
    "option3": "T(과제) — 내가 맡은 문제와 목표가 무엇이었는지",
    "option4": "R(결과) — 그래서 무엇이 달라졌는지",
    "answerIndex": 1,
    "explanation": "코스는 A(행동)가 STAR의 주인공이라고 강조한다. 다들 S와 R만 쓰고 A를 한 줄로 끝내지만, 읽는 사람이 궁금한 건 '너는 뭘 했는데?'라는 A 부분이다."
  },
  {
    "courseSlug": "portfolio",
    "question": "'수습 8주 = 재료 창고' 표에 따르면, 3~6주차에 생기는 티켓(실제 이슈 처리 기록)은 포트폴리오에서 어떤 쓰임이 있는가?",
    "option1": "\"실무 문제를 이렇게 풀었다\"를 보여주는 사례",
    "option2": "\"기초를 이렇게 다졌다\"를 보여주는 증거",
    "option3": "대표작 후보 1순위가 되는 종합 산출물",
    "option4": "'배움(R)' 칸을 채워 줄 회고의 원석",
    "answerIndex": 0,
    "explanation": "재료 창고 표에서 3~6주차 티켓은 '실무 문제를 이렇게 풀었다' 사례로 쓰인다. 기초 증거는 1~2주차 과제집, 대표작 후보 1순위는 7~8주차 종합 프로젝트, 배움의 원석은 매주 회고·멘토 피드백이다."
  },
  {
    "courseSlug": "portfolio",
    "question": "개발자 편에서 저장소를 공개하기 전에 '꼭' 확인하라고 강조한 것은 무엇인가?",
    "option1": "커밋 메시지가 한 단어로 너무 짧지 않은지",
    "option2": "README 맨 위에 스크린샷이나 데모 GIF가 있는지",
    "option3": "잔디(커밋 그래프)가 끊기지 않고 매일 이어지는지",
    "option4": "비밀번호·API 키·.env 파일이 커밋에 섞여 있지 않은지",
    "answerIndex": 3,
    "explanation": "코스는 한 번 커밋된 비밀은 삭제해도 이력에 남는다며, 공개 전 비밀번호·API 키·.env 파일 확인을 '올리기 전에 꼭!' 경고로 강조한다. 회사 코드는 공개 저장소에 올리지 않는 것도 원칙이다."
  },
  {
    "courseSlug": "portfolio",
    "question": "수습생 민지가 대표작 README에 넣을 스크린샷을 찍었는데, 화면에 테스트용 글 제목 \"ㅁㄴㅇㄹ\"과 북마크바·탭 20개가 그대로 보인다. 코스의 스크린샷 체크리스트에 따르면 민지는 어떻게 해야 하는가?",
    "option1": "전체 화면 구도는 그대로 두고 해상도만 높여 다시 찍는다",
    "option2": "스크린샷 아래에 긴 글 설명을 붙여 부족한 부분을 보완한다",
    "option3": "그럴듯한 더미 데이터로 바꾸고 브라우저 잡동사니를 잘라낸 뒤 핵심 위주로 다시 캡처한다",
    "option4": "정지 화면 대신 코드 편집기 전체를 캡처해서 올린다",
    "answerIndex": 2,
    "explanation": "체크리스트는 \"ㅁㄴㅇㄹ\" 같은 테스트 데이터 대신 그럴듯한 데이터를 넣고, 북마크바·탭 같은 브라우저 잡동사니를 잘라내며, 전체 화면 1장보다 자랑할 핵심을 크게 담으라고 안내한다. 스크린샷은 '작업물의 증명사진'이기 때문이다."
  },
  {
    "courseSlug": "react-intro",
    "question": "이 코스가 첫 섹션에서 소개하는 React의 핵심 아이디어를 한 줄로 나타낸 것은?",
    "option1": "화면(UI) = f(이벤트)",
    "option2": "상태 = f(화면)",
    "option3": "화면(UI) = f(상태)",
    "option4": "서버 = f(요청)",
    "answerIndex": 2,
    "explanation": "섹션 1의 핵심 문장은 '화면(UI) = f(상태)'입니다. 화면을 직접 조작하지 않고 상태가 이러면 화면은 이렇게 생겨야 한다고 선언하면, 상태가 바뀔 때 React가 바뀐 부분만 다시 그려 줍니다."
  },
  {
    "courseSlug": "react-intro",
    "question": "JSX에서 <h1>제목</h1><p>내용</p>처럼 형제 태그 두 개를 동시에 반환하려고 하면 에러가 난다. 코스에서 알려 주는 올바른 방법은?",
    "option1": "각 태그를 따옴표로 감싸 문자열로 반환한다",
    "option2": "<>...</> (Fragment)로 두 태그를 하나로 감싼다",
    "option3": "return 문을 태그마다 한 번씩 두 번 쓴다",
    "option4": "두 태그 사이에 쉼표를 넣어 나란히 반환한다",
    "answerIndex": 1,
    "explanation": "섹션 3의 JSX 필수 규칙 4번 — 형제 태그는 하나로 감싸야 하며, <>...</> (Fragment)로 포장하면 됩니다. 'Adjacent JSX elements must be wrapped...' 에러가 바로 이 규칙 위반의 신호입니다."
  },
  {
    "courseSlug": "react-intro",
    "question": "좋아요 버튼에서 setCount(count + 1) 대신 count = count + 1처럼 값을 직접 바꾸면 화면이 갱신되지 않는다. 코스가 설명하는 이유는?",
    "option1": "count가 부모에게서 받은 읽기 전용 props이기 때문",
    "option2": "state에는 문자열만 저장할 수 있고 숫자는 쓸 수 없기 때문",
    "option3": "브라우저가 폼의 기본 동작으로 새로고침을 막고 있기 때문",
    "option4": "setCount를 호출해야 React가 '다시 그리자'는 신호를 받기 때문",
    "answerIndex": 3,
    "explanation": "섹션 4에서 useState가 값을 바꾸는 함수를 따로 주는 이유는 딱 하나 — setCount를 불러야 React가 '상태가 바뀌었으니 화면을 다시 그리자'는 신호를 받기 때문입니다. 직접 바꾸면 React가 알아채지 못합니다."
  },
  {
    "courseSlug": "react-intro",
    "question": "코스가 '초보 함정 1위'로 꼽는 실수 — onClick={handleSubmit} 대신 onClick={handleSubmit()}처럼 괄호를 붙이면 어떤 일이 생기나?",
    "option1": "렌더링되는 순간 함수가 즉시 실행돼 버린다",
    "option2": "클릭할 때마다 함수가 두 번씩 실행된다",
    "option3": "함수가 등록되지 않아 클릭해도 아무 일이 없다",
    "option4": "이벤트가 부모 컴포넌트로 넘어가 버린다",
    "answerIndex": 0,
    "explanation": "섹션 5의 초보 함정 1위 — 괄호를 붙이면 '실행 결과'를 건네는 것이라 렌더링되는 순간 즉시 실행됩니다. 이벤트에는 괄호 없이 함수 그 자체를 건네야 '그때 실행해 줘'가 됩니다."
  },
  {
    "courseSlug": "react-intro",
    "question": "코스 목록을 .map()으로 그리면서 key={index}(자리 번호)를 썼더니, 항목 순서를 바꾸자 입력값이 엉뚱한 항목에 뒤섞였다. 코스의 '출석부' 비유에 따르면 올바른 해결책은?",
    "option1": "key를 아예 빼서 React가 자동으로 항목을 구분하게 둔다",
    "option2": ".map() 대신 반복문으로 목록을 하나씩 그린다",
    "option3": "DB가 준 고유한 id를 key로 넣어 '학번'으로 부르게 한다",
    "option4": "순서를 바꿀 때마다 목록 전체를 새로 만들어 갈아 끼운다",
    "answerIndex": 2,
    "explanation": "섹션 6의 비유 — key는 React의 출석부이며, 자리 번호(index)로 부르면 자리만 바뀌어도 헷갈립니다. 학번처럼 변하지 않는 고유 id를 key로 주면 순서가 바뀌어도 React가 각 항목을 정확히 알아봅니다."
  },
  {
    "courseSlug": "server-anatomy",
    "question": "이 코스에서 말하는 '서버'의 정체로 가장 알맞은 것은?",
    "option1": "데이터를 표(테이블) 형태로 안전하게 보관하는 저장 전용 장치",
    "option2": "특정 포트를 열고 요청을 기다렸다가 응답하는 프로그램",
    "option3": "서버실에 설치된 고성능 전용 컴퓨터 기계 그 자체",
    "option4": "다른 컴퓨터에 먼저 요청을 보내는 쪽을 부르는 역할 이름",
    "answerIndex": 1,
    "explanation": "섹션 1의 핵심 — 서버는 기계가 아니라 '역할'이고, 포트를 열고 하루 종일 요청을 기다리는 프로그램이 그 역할을 맡습니다. 먼저 요청을 보내는 쪽은 클라이언트예요."
  },
  {
    "courseSlug": "server-anatomy",
    "question": "우리 회사(AWESOMEDEV) 학습 플랫폼의 3계층에서 각 역할을 맡은 프로그램을 옳게 짝지은 것은?",
    "option1": "웹서버=Spring Boot, WAS=Caddy, DB=PostgreSQL",
    "option2": "웹서버=PostgreSQL, WAS=Spring Boot, DB=Caddy",
    "option3": "웹서버=Caddy, WAS=Spring Boot, DB=PostgreSQL",
    "option4": "웹서버=Caddy, WAS=PostgreSQL, DB=Spring Boot",
    "answerIndex": 2,
    "explanation": "섹션 2~3에서 본 우리 스택 — 손님을 맞는 웹서버는 Caddy(HTTPS·정적 파일), 요리(비즈니스 로직)를 하는 WAS는 Spring Boot, 재료(데이터) 창고인 DB는 PostgreSQL입니다."
  },
  {
    "courseSlug": "server-anatomy",
    "question": "로그인 후 화면에 보이는 \"○○님, 진도 40%\" 문구에 대한 설명으로 옳은 것은?",
    "option1": "요청이 올 때마다 새로 만들어지는 동적 응답이라 Spring Boot와 DB가 처리한다",
    "option2": "미리 만들어 둔 정적 파일이라 Caddy가 파일을 그대로 건네준다",
    "option3": "누가 요청해도 내용이 같아서 자판기처럼 바로 꺼내 줄 수 있다",
    "option4": "React 빌드 결과물에 포함되어 있어 서버 없이 브라우저가 만들어 낸다",
    "answerIndex": 0,
    "explanation": "섹션 4의 구분 — 누가·언제 요청하냐에 따라 내용이 달라지는 것은 동적 응답이고, 주방(WAS)과 창고(DB)를 다녀와야 하므로 Spring Boot + DB가 처리합니다."
  },
  {
    "courseSlug": "server-anatomy",
    "question": "우리 반 30명이 동시에 이 페이지를 열었을 때, 코스에서 설명한 서버의 처리 방식으로 옳은 것은?",
    "option1": "1번 요청 처리가 끝날 때까지 나머지 29명의 접속은 모두 끊어 버린다",
    "option2": "요청이 올 때마다 DB로 가는 통로를 매번 새로 뚫어 속도를 높인다",
    "option3": "대기열이 가득 차더라도 에러 없이 요청을 무한정 받아 준다",
    "option4": "스레드 풀에 대기시켜 둔 스레드를 요청마다 한 명씩 붙여 겹쳐서 처리한다",
    "answerIndex": 3,
    "explanation": "섹션 5 — Spring Boot는 요청 처리 직원(스레드)을 스레드 풀에 미리 대기시켜 두고 요청마다 한 명씩 붙입니다. DB 통로는 커넥션 풀로 미리 뚫어 돌려쓰고, 대기열마저 차면 에러가 나요."
  },
  {
    "courseSlug": "server-anatomy",
    "question": "학습 플랫폼 사용자가 수만 명으로 늘어 서버가 힘들어하고, '한 대가 멈추면 서비스 전체가 멈추는' 위험까지 없애고 싶다. 코스 내용에 따른 알맞은 처방은?",
    "option1": "EC2 인스턴스 타입을 더 좋은 것으로 바꾸는 스케일 업을 한다",
    "option2": "앞에 로드 밸런서를 세우고 바쁜 WAS만 여러 대로 늘리는 스케일 아웃을 한다",
    "option3": "WAS가 데이터를 메모리에 직접 보관하게 해서 DB까지 가는 왕복을 없앤다",
    "option4": "정적 파일 서빙을 중단해서 웹서버(Caddy)의 부담부터 줄인다",
    "answerIndex": 1,
    "explanation": "섹션 6 — 스케일 업은 아무리 좋아도 '한 대'의 한계와 한 대가 죽으면 전부 멈추는 위험이 그대로입니다. 3계층으로 역할을 나눠 둔 덕분에 로드 밸런서를 세우고 바쁜 WAS만 부분 확장하는 스케일 아웃이 가능해요."
  },
  {
    "courseSlug": "spring-intro",
    "question": "스프링의 '의존성 주입(DI)'을 이 코스의 식당 주방 비유로 가장 잘 설명한 것은?",
    "option1": "요리사가 요리할 때마다 직접 마트에 가서 재료를 사 오고 도구까지 스스로 준비하는 것",
    "option2": "손님이 주방에 직접 들어가서 원하는 요리를 자기 손으로 만들어 먹는 것",
    "option3": "요리사가 \"재료 주세요\"라고 선언만 하면 주방(컨테이너)이 필요한 재료를 알아서 가져다주는 것",
    "option4": "주방장이 모든 요리를 혼자 도맡아 하고 나머지 요리사들은 주문만 받게 하는 것",
    "answerIndex": 2,
    "explanation": "섹션 1의 핵심 비유입니다. 스프링은 앱이 켜질 때 필요한 객체들을 미리 만들어 컨테이너(주방 창고)에 보관해 두고, \"이거 필요해요\"라고 선언한 곳에 알아서 넣어 줍니다 — 이것이 의존성 주입(DI)입니다."
  },
  {
    "courseSlug": "spring-intro",
    "question": "코스의 '어노테이션 5총사' 설명에서, 주방에서 실제 요리(비즈니스 로직)를 하는 요리사 역할을 맡는 어노테이션은 무엇인가?",
    "option1": "@RestController — 요청을 받는 카운터 직원",
    "option2": "@Service — 주방에서 실제 요리를 하는 요리사",
    "option3": "@Repository — 창고(DB)에서 재료를 꺼내오는 담당",
    "option4": "@Configuration — 주방 설비를 설치하고 빈을 등록하는 담당",
    "answerIndex": 1,
    "explanation": "섹션 2에서 @Service는 \"주방에서 실제 요리(비즈니스 로직)를 하는 요리사\"로 설명됩니다. @RestController는 주문을 받는 카운터, @Repository는 창고(DB) 담당이에요."
  },
  {
    "courseSlug": "spring-intro",
    "question": "코스에서 Controller에 비즈니스 로직을 쓰지 말라고 하는 이유로 든 것은 무엇인가?",
    "option1": "각 층이 자기 일만 한다는 원칙이 깨져서, 나중에 규칙을 바꾸려 할 때 어디를 고쳐야 할지 찾기 어려워지기 때문",
    "option2": "비즈니스 로직이 들어가는 순간 서버가 켜지지 않고 시작하자마자 에러가 나기 때문",
    "option3": "스프링이 그 클래스를 더 이상 빈(Bean)으로 등록해 주지 않게 되기 때문",
    "option4": "Controller에서는 Service를 생성자 주입으로 받을 수 없게 되기 때문",
    "answerIndex": 0,
    "explanation": "섹션 3의 핵심입니다. 코스는 \"당장은 돌아가지만\" 검증·로그 같은 주방의 일이 카운터에 쌓이고, 나중에 \"로그인 규칙만 바꾸고 싶은데\" 할 때 어디를 고쳐야 할지 아무도 모르게 된다고 설명합니다."
  },
  {
    "courseSlug": "spring-intro",
    "question": "application.yml에서 ${DB_URL:jdbc:postgresql://localhost:5432/mirim} 같은 ${환경변수:기본값} 문법이 뜻하는 것은?",
    "option1": "기본값을 먼저 사용하고, 기본값이 비어 있을 때만 환경변수를 읽는다",
    "option2": "환경변수의 값과 기본값을 이어 붙여서 하나의 최종 값으로 만든다",
    "option3": "환경변수는 참고용일 뿐이고 실제로는 항상 기본값만 사용된다",
    "option4": "환경변수가 있으면 그 값을 쓰고, 없으면 콜론 뒤의 기본값을 쓴다",
    "answerIndex": 3,
    "explanation": "섹션 4의 핵심 문법입니다. 환경변수가 있으면 그걸 쓰고 없으면 기본값을 쓰기 때문에, 같은 코드가 내 PC에서는 로컬 PostgreSQL로, 운영 서울 EC2에서는 환경변수로 덮어쓴 진짜 DB로 돌아갑니다."
  },
  {
    "courseSlug": "spring-intro",
    "question": "수습 개발자가 이메일과 역할(role)이 모두 일치하는 사용자를 찾으려고 JPA 메서드 findByEmailAndRole(String email, String role)을 만들었다. 코스에서 배운 이름 규칙대로라면 스프링이 대신 써 주는 SQL은?",
    "option1": "SELECT * FROM users WHERE email = ? OR role = ?",
    "option2": "SELECT * FROM users WHERE email = ? AND role = ?",
    "option3": "SELECT * FROM users WHERE email = ? ORDER BY role DESC",
    "option4": "SELECT COUNT(*) > 0 FROM users WHERE email = ? AND role = ?",
    "answerIndex": 1,
    "explanation": "섹션 5의 규칙 응용입니다. findBy 뒤의 필드명은 WHERE 조건이 되고, And는 두 조건을 함께 거는 접속사예요. COUNT(*) > 0 형태는 existsBy로 시작할 때 만들어지는 SQL입니다."
  },
  {
    "courseSlug": "sql-intermediate",
    "question": "GROUP BY로 행을 묶은 뒤, 제출 수가 20건 넘는 '팀(그룹)'만 남기고 싶다. 어떤 키워드 뒤에 COUNT(*) > 20 조건을 써야 할까?",
    "option1": "WHERE — 행을 하나씩 검사하는 입장 검사이므로",
    "option2": "ORDER BY — 조건에 맞는 팀을 앞으로 정렬하므로",
    "option3": "HAVING — 팀이 다 만들어진 뒤 팀 단위로 거르므로",
    "option4": "GROUP BY — 묶는 기준에 조건을 함께 적으므로",
    "answerIndex": 2,
    "explanation": "WHERE는 묶기 전 행 하나하나를 거르는 '입장 검사'라서 그 시점엔 셀 팀이 없고, 묶은 뒤 팀 단위로 거르는 문은 HAVING이다. 코스 2섹션(HAVING)의 실행 순서 WHERE → GROUP BY → HAVING → ORDER BY 개념이다."
  },
  {
    "courseSlug": "sql-intermediate",
    "question": "submission 테이블에서 아직 채점되지 않은(score가 NULL인) 제출을 조회하려면 어떤 조건을 써야 할까?",
    "option1": "WHERE score = NULL — 등호로 NULL과 직접 비교한다",
    "option2": "WHERE score IS NULL — IS로 NULL 여부를 검사한다",
    "option3": "WHERE score = 0 — NULL은 0점과 같은 값이므로",
    "option4": "WHERE COALESCE(score) — NULL을 자동으로 찾아준다",
    "answerIndex": 1,
    "explanation": "NULL은 '모름'이라서 = 로 비교하면 결과도 NULL이 되어 항상 0건이 나오고, NULL 여부는 IS NULL로 검사해야 한다. 코스 4섹션(NULL의 함정)의 함정 ①에 해당한다."
  },
  {
    "courseSlug": "sql-intermediate",
    "question": "제출이 5건 있고 score가 (90, 80, NULL, 70, NULL)일 때, AVG(score)의 결과는?",
    "option1": "80 — NULL 두 건은 건너뛰고 240을 3으로 나눈다",
    "option2": "48 — NULL을 0으로 쳐서 240을 5로 나눈다",
    "option3": "NULL — NULL이 하나라도 끼면 집계 전체가 NULL이 된다",
    "option4": "60 — NULL을 최솟값으로 바꾼 뒤 평균을 낸다",
    "answerIndex": 0,
    "explanation": "집계 함수는 NULL을 조용히 건너뛰므로 AVG는 채점된 3건만으로 240÷3=80을 계산한다. 코스 4섹션의 함정 ② — 미채점을 0점으로 칠지 말지는 DB가 아니라 내가 정해야 한다는 내용이다."
  },
  {
    "courseSlug": "sql-intermediate",
    "question": "\"검색이 빨라진다니 모든 컬럼에 인덱스를 걸자\"는 생각이 코스에서 '안 됩니다'라고 한 이유는?",
    "option1": "인덱스는 테이블당 한 개만 만들 수 있기 때문에",
    "option2": "인덱스를 걸면 SELECT 결과의 정렬 순서가 바뀌기 때문에",
    "option3": "인덱스가 많으면 EXPLAIN이 실행 계획을 못 세우기 때문에",
    "option4": "INSERT/UPDATE 때마다 인덱스도 고쳐야 해서 쓰기가 느려지기 때문에",
    "answerIndex": 3,
    "explanation": "인덱스는 디스크를 차지하고, 데이터를 넣거나 고칠 때마다 유지 비용이 들어 쓰기가 느려지는 읽기-쓰기 거래(trade-off)다. 그래서 코스 5섹션은 'WHERE나 JOIN에 자주 등장하는 컬럼에만' 거는 것을 원칙으로 든다."
  },
  {
    "courseSlug": "sql-intermediate",
    "question": "수십만 행의 submission 테이블에서 WHERE student_id = 7 조회가 느리다는 말을 듣고 EXPLAIN을 붙였더니 'Seq Scan on submission'이 나왔다. 코스가 알려준 다음 행동은?",
    "option1": "WHERE를 HAVING으로 바꿔 팀 단위로 거르게 한다",
    "option2": "student_id 컬럼에 CREATE INDEX를 걸까 검토한다",
    "option3": "쿼리를 트랜잭션(BEGIN~COMMIT)으로 감싸 다시 실행한다",
    "option4": "COALESCE로 student_id의 NULL을 0으로 바꿔 준다",
    "answerIndex": 1,
    "explanation": "데이터가 많은데 Seq Scan이면 책을 한 장씩 넘기는 중이라는 점검 신호이므로, 자주 검색하는 컬럼에 인덱스를 걸지 고민하는 것이 다음 단계다. 코스 6섹션의 'EXPLAIN 붙이기 → Seq Scan 찾기 → 인덱스 고민' 3단 콤보다."
  },
  {
    "courseSlug": "sql",
    "question": "이 코스에서는 데이터베이스를 엑셀에 비유합니다. 엑셀의 '시트 한 장'에 해당하는 것은 무엇인가요?",
    "option1": "행(row) — 데이터 한 건",
    "option2": "테이블(table)",
    "option3": "열(column) — 속성 하나",
    "option4": "데이터베이스(DB) 전체",
    "answerIndex": 1,
    "explanation": "섹션 1의 비유에서 엑셀 파일 하나가 데이터베이스, 시트 한 장이 테이블, 행이 데이터 한 건, 열이 속성입니다. 우리 플랫폼 DB에는 users 등 테이블(시트) 5개가 들어 있어요."
  },
  {
    "courseSlug": "sql",
    "question": "WHERE kind = 'MAIN'처럼 SQL에서 문자열 값을 조건에 쓸 때 올바른 표기 방법은 무엇인가요?",
    "option1": "큰따옴표(\"MAIN\")로 감싼다",
    "option2": "따옴표 없이 MAIN이라고 그대로 쓴다",
    "option3": "작은따옴표('MAIN')로 감싼다",
    "option4": "괄호((MAIN))로 감싼다",
    "answerIndex": 2,
    "explanation": "섹션 3의 '따옴표 주의'에서 배운 내용으로, 문자열은 작은따옴표로 감싸고 큰따옴표는 열·테이블 이름용이라 뜻이 달라집니다. 숫자는 week = 1처럼 따옴표 없이 씁니다."
  },
  {
    "courseSlug": "sql",
    "question": "submission 테이블에서 '최근 제출 5건'을 보려고 합니다. SELECT 문 뒤에 이어 붙일 내용으로 옳은 것은 무엇인가요?",
    "option1": "ORDER BY submitted_at DESC LIMIT 5",
    "option2": "ORDER BY submitted_at ASC LIMIT 5",
    "option3": "LIMIT 5 ORDER BY submitted_at DESC",
    "option4": "WHERE submitted_at DESC LIMIT 5",
    "answerIndex": 0,
    "explanation": "섹션 4에서 DESC가 내림차순(최신순), LIMIT이 위에서 N개 자르기이며, 조합 순서는 FROM → WHERE → ORDER BY → LIMIT입니다. 관리자 화면의 '최근 제출물' 목록이 바로 이 쿼리예요."
  },
  {
    "courseSlug": "sql",
    "question": "submission 테이블의 user_id처럼, 다른 테이블의 행을 번호표(id)로 가리키는 열을 부르는 이름은 무엇인가요?",
    "option1": "기본키(primary key)",
    "option2": "별명(alias)",
    "option3": "와일드카드(wildcard)",
    "option4": "외래키(foreign key)",
    "answerIndex": 3,
    "explanation": "섹션 6에서 이름은 users에 한 번만 두고 다른 테이블은 번호표(id)로 가리키게 하는데, 이 번호표 열을 '다른(외) 테이블의 열쇠(키)'라는 뜻의 외래키라고 부릅니다. JOIN은 이 번호표를 실제 이름으로 바꿔 보는 일이에요."
  },
  {
    "courseSlug": "sql",
    "question": "수습생 A가 로컬 DB에서 BEGIN;을 실행한 뒤 UPDATE submission SET status = 'DONE';을 쳤는데, WHERE를 빼먹어 모든 행이 DONE으로 바뀐 걸 발견했습니다. 아직 확정 전이라면 원래대로 되돌리는 명령은 무엇인가요?",
    "option1": "COMMIT; — 지금까지의 변경을 저장한다",
    "option2": "DELETE FROM submission; — 잘못 바뀐 행을 지운다",
    "option3": "ROLLBACK; — BEGIN 이후의 변경을 전부 되돌린다",
    "option4": "SELECT status FROM submission; — 상태를 다시 조회한다",
    "answerIndex": 2,
    "explanation": "섹션 5의 트랜잭션은 '게임 보스전 앞 세이브 포인트' 비유 그대로, BEGIN 이후의 변경은 가안 상태라 ROLLBACK 한 방에 전부 원상복구됩니다. 반대로 확정하고 싶을 때만 COMMIT을 씁니다."
  },
  {
    "courseSlug": "tcp-udp",
    "question": "TCP 3-way 핸드셰이크에서 세 마디가 오가는 올바른 순서는 무엇인가요?",
    "option1": "ACK → SYN → SYN+ACK",
    "option2": "SYN → ACK → SYN+ACK",
    "option3": "SYN → SYN+ACK → ACK",
    "option4": "SYN+ACK → SYN → ACK",
    "answerIndex": 2,
    "explanation": "섹션 2에서 배운 대로, 내 PC가 SYN으로 대화를 청하고 서버가 SYN+ACK으로 맞잡은 뒤 내 PC가 ACK으로 악수를 완성해야 연결이 수립됩니다."
  },
  {
    "courseSlug": "tcp-udp",
    "question": "코스에서 설명한 'UDP가 빠른 진짜 이유'로 가장 알맞은 것은 무엇인가요?",
    "option1": "연결 맺기·확인 답장·재전송 같은 일을 생략하기 때문에",
    "option2": "패킷을 TCP보다 훨씬 큰 크기로 쪼개서 보내기 때문에",
    "option3": "전용 회선을 미리 예약해서 다른 데이터가 못 끼어들기 때문에",
    "option4": "받는 쪽이 영수증(ACK)을 더 빠른 주기로 보내 주기 때문에",
    "answerIndex": 0,
    "explanation": "섹션 4의 핵심은 UDP의 속도가 '안 하는 일이 많아서' 나온다는 것입니다. 악수(3-way)도, ACK 확인도, 재전송도 하지 않는 대신 신뢰성을 포기한 대가로 속도를 얻습니다."
  },
  {
    "courseSlug": "tcp-udp",
    "question": "TCP로 상자 #1~#5를 보내다가 #3이 길에서 실종됐습니다. 이때 벌어지는 일로 옳은 것은 무엇인가요?",
    "option1": "받는 쪽이 #3 자리를 비워 둔 채 데이터를 미완성으로 조립한다",
    "option2": "보내는 쪽이 #1부터 #5까지 전부 처음부터 다시 보낸다",
    "option3": "받는 쪽이 연결을 끊고 3-way 핸드셰이크부터 다시 시작한다",
    "option4": "받는 쪽이 ACK로 알려 주면 보내는 쪽이 #3만 골라 재전송한다",
    "answerIndex": 3,
    "explanation": "섹션 3의 재전송·순서 보장 개념입니다. 받는 쪽이 '몇 번까지 잘 받았는지' 영수증(ACK)으로 알려 주므로, 실종된 #3만 다시 보내고 늦게 온 #3을 제자리에 끼워 넣어 원본과 100% 동일하게 복원합니다."
  },
  {
    "courseSlug": "tcp-udp",
    "question": "netstat 실습에서 원격 주소가 ':443'이고 상태가 ESTABLISHED인 줄이 뜻하는 것은 무엇인가요?",
    "option1": "내 PC가 SSH로 EC2 서버에 원격 접속해 있다는 뜻",
    "option2": "상대 서버의 HTTPS 방과 3-way 악수가 끝나 연결이 살아 있다는 뜻",
    "option3": "UDP 방송 수신이 진행 중이라 상태가 표시되고 있다는 뜻",
    "option4": "PostgreSQL 데이터베이스와 질의를 주고받는 중이라는 뜻",
    "answerIndex": 1,
    "explanation": "섹션 5~6에서 443번은 HTTPS의 방 번호이고, ESTABLISHED는 3-way 악수가 성공적으로 끝나 연결이 살아 있다는 표시라고 배웠습니다. 참고로 UDP는 연결 개념이 없어 이 명단에 상태가 뜨지 않습니다."
  },
  {
    "courseSlug": "tcp-udp",
    "question": "온라인 게임을 만들고 있습니다. 캐릭터의 실시간 위치는 UDP로 보내는데, '아이템 구매' 버튼까지 UDP로 보내자는 의견이 나왔다면 어떻게 판단해야 할까요?",
    "option1": "게임 데이터끼리는 같은 프로토콜을 써야 하므로 전부 UDP로 통일한다",
    "option2": "구매가 위치 정보보다 자주 발생하지 않으므로 UDP로 보내도 무방하다",
    "option3": "결제는 유실되면 안 되는 데이터이므로 느려도 TCP로 보내야 한다",
    "option4": "UDP로 보내되 헤더를 20바이트로 늘리면 유실을 막을 수 있다",
    "answerIndex": 2,
    "explanation": "섹션 7 토론(Q1)의 핵심 기준인 '이 데이터는 유실돼도 되는가?'를 적용한 문제입니다. 위치는 최신 정보가 곧 다시 오니 유실을 감수해도 되지만, 결제는 한 건이라도 유실되면 안 되므로 TCP를 써야 합니다."
  },
  {
    "courseSlug": "testing",
    "question": "단위 테스트의 3막 구조 given-when-then에서 'when' 단계가 하는 일은 무엇인가?",
    "option1": "테스트에 필요한 객체와 데이터를 만들어 무대를 준비한다",
    "option2": "assertThat으로 실제값과 기대값이 같은지 검증한다",
    "option3": "검사하려는 동작을 딱 하나만 실행한다",
    "option4": "테스트가 끝난 뒤 사용한 데이터를 깨끗하게 정리한다",
    "answerIndex": 2,
    "explanation": "코스의 연극 비유에서 when은 '사건 발생', 즉 주인공의 행동으로 테스트하려는 딱 그 동작 하나만 호출하는 단계다. 두세 가지를 한꺼번에 하면 실패했을 때 범인을 못 찾는다고 설명한다."
  },
  {
    "courseSlug": "testing",
    "question": "이 코스가 테스트 코드를 '미래의 나를 지키는 보험'에 비유한 핵심 이유는?",
    "option1": "한 번 작성해 두면 수정 없이 영원히 코드를 지켜 주기 때문에",
    "option2": "사고가 나기 전에 미리 들어 두고, 나중에 코드가 부서지면 빨간불로 즉시 알려 주기 때문에",
    "option3": "테스트가 있으면 버그가 아예 발생하지 않도록 원천 차단해 주기 때문에",
    "option4": "배포 전에 사용자가 대신 기능을 눌러 보고 검사해 주기 때문에",
    "answerIndex": 1,
    "explanation": "섹션 1에서 보험은 사고가 나기 전에 드는 것이고, 3개월 뒤의 내가 코드를 부수면 테스트가 빨간불로 즉시 알려 준다고 설명한다. 보험료(작성 시간)는 조금 내고 보험금(밤샘 디버깅 면제)은 크게 돌려받는다는 비유다."
  },
  {
    "courseSlug": "testing",
    "question": "코스가 입문 단계에서 꼭 챙기라고 특별히 강조한 좋은 단위 테스트의 두 가지 조건은?",
    "option1": "반복 가능할 것, 자가 검증이 될 것",
    "option2": "코드가 짧을 것, 메서드 이름을 한글로 쓸 것",
    "option3": "커버리지가 높을 것, 실제 DB로 검증할 것",
    "option4": "빠를 것, 테스트끼리 서로 독립적일 것",
    "answerIndex": 3,
    "explanation": "섹션 5에서 특히 강조한 두 조건은 빠르다(Fast)와 독립적이다(Independent)이다. 빨라야 고칠 때마다 돌리는 습관이 생기고, 독립적이어야 순서나 공유 데이터 때문에 터지는 '도미노 테스트'가 되지 않는다. 반복 가능·자가 검증은 '그 외' 조건으로만 언급된다."
  },
  {
    "courseSlug": "testing",
    "question": "ProgressServiceTest의 '이미 완료한 강의를 다시 완료해도 진도율은 그대로다' 테스트에 대한 설명으로 옳은 것은?",
    "option1": "완료 버튼 연타로 진도율이 두 배로 오르던 실제 버그의 재발을 막으려고 남긴 테스트다",
    "option2": "DB와 서버를 실제로 띄워 진도율 API를 호출해 보는 통합 테스트다",
    "option3": "강의를 전부 완료했을 때 정확히 100%가 나오는지 확인하는 테스트다",
    "option4": "같은 강의를 두 번 완료하면 진도율이 20%가 되는 것이 정상임을 증명하는 테스트다",
    "answerIndex": 0,
    "explanation": "섹션 4의 해부 ④에서 이 테스트는 실화 기반으로, 완료 버튼을 연타하면 진도율이 2배로 오르던 버그를 고친 뒤 재발하지 말라고 못 박아 둔 것이라고 설명한다. 테스트는 '버그의 묘비명이자 부활 방지 부적'이라는 표현이 나온다."
  },
  {
    "courseSlug": "testing",
    "question": "출석률(0~100%)을 계산하는 함수를 새로 만들었고 테스트 케이스를 3개만 고를 수 있다. 코스에서 배운 경계값 요령에 가장 충실한 선택은?",
    "option1": "37%, 52%, 68%처럼 서로 다른 중간값 3개를 골라 검사한다",
    "option2": "매번 무작위로 값 3개를 새로 뽑아 다양하게 검사한다",
    "option3": "출석 0번(0%), 딱 1번 출석, 전부 출석(100%)의 가장자리 값을 검사한다",
    "option4": "가장 자주 나올 평균적인 값 하나를 골라 3번 반복해 검사한다",
    "answerIndex": 2,
    "explanation": "섹션 6에서 버그는 평범한 가운데 값이 아니라 범위가 시작되고 끝나는 가장자리에서 태어난다고 했다. 실전 요령으로 '텅 빈 경우 / 딱 1개인 경우 / 꽉 찬(최대) 경우' 세 칸을 먼저 챙기라고 안내한다."
  },
  {
    "courseSlug": "tool-capture",
    "question": "Win+Shift+S로 화면을 캡처하면 결과물은 기본적으로 어디로 가나요?",
    "option1": "내 PC의 '사진' 폴더에 파일로 자동 저장된다",
    "option2": "클립보드에 복사되어 Ctrl+V로 바로 붙여넣을 수 있다",
    "option3": "그림판이 자동으로 열리면서 이미지가 삽입된다",
    "option4": "Gitea 과제 저장소에 자동으로 업로드된다",
    "answerIndex": 1,
    "explanation": "Win+Shift+S 캡처는 파일로 자동 저장되지 않고 클립보드로 복사됩니다. 그래서 '찍기 → Ctrl+V로 붙이기'까지 3초면 끝난다는 게 섹션 2의 핵심입니다."
  },
  {
    "courseSlug": "tool-capture",
    "question": "화면보다 세로로 긴 웹 페이지 전체를 PNG 한 장으로 캡처하려면 코스에서 알려준 방법은 무엇인가요?",
    "option1": "PrtScn을 스크롤하면서 여러 번 눌러 이어 붙인다",
    "option2": "Win+Shift+S의 '전체' 모드로 모니터를 통째로 찍는다",
    "option3": "F12 → Ctrl+Shift+P → 'screenshot' 입력 후 'Capture full size screenshot'을 선택한다",
    "option4": "Win+G 게임 바로 페이지를 스크롤하며 녹화한다",
    "answerIndex": 2,
    "explanation": "섹션 3의 치트시트대로 브라우저 개발자 도구(F12)의 명령 팔레트에서 'Capture full size screenshot'을 실행하면 페이지 전체가 한 장으로 저장됩니다. 화면에 보이는 부분만 찍는 다른 방법과 달리 긴 페이지를 통째로 담을 수 있어요."
  },
  {
    "courseSlug": "tool-capture",
    "question": "3~10초짜리 짧은 버그 재현을 공유할 때 코스가 MP4 대신 GIF를 추천하는 이유는 무엇인가요?",
    "option1": "GIF가 MP4보다 화질이 항상 더 좋기 때문",
    "option2": "GIF에는 소리를 함께 담을 수 있기 때문",
    "option3": "GIF는 프레임 수 제한이 없어 긴 영상에 유리하기 때문",
    "option4": "채팅이나 Gitea 이슈에서 클릭 없이 자동 재생되기 때문",
    "answerIndex": 3,
    "explanation": "섹션 4에서 개발자들이 GIF를 좋아하는 이유는 받는 사람이 플레이어를 열 필요 없이 스크롤하다가 바로 볼 수 있다는 점이라고 설명합니다. 소리가 필요하거나 30초를 넘는 재현은 MP4를 쓰는 게 코스의 기준입니다."
  },
  {
    "courseSlug": "tool-capture",
    "question": "캡처에 찍힌 API 토큰이나 비밀번호를 가릴 때 코스가 권장하는 방법은 무엇인가요?",
    "option1": "그림판에서 완전 불투명한 단색 박스로 덮는다",
    "option2": "모자이크 처리를 해서 알아볼 수 없게 만든다",
    "option3": "흐림(블러) 효과를 진하게 여러 번 겹쳐 적용한다",
    "option4": "이미지를 JPG로 저장해 글자가 뭉개지게 한다",
    "answerIndex": 0,
    "explanation": "섹션 6에서 모자이크나 블러는 도구로 복원된 사례가 있으므로, 완전 불투명한 단색 박스로 확실히 덮으라고 강조합니다. 가장 안전한 방법은 애초에 민감한 창을 닫고 다시 찍는 것이라고도 덧붙입니다."
  },
  {
    "courseSlug": "tool-capture",
    "question": "수습생 A가 마우스를 올려야만 보이는 드롭다운 메뉴를 캡처하려고 Win+Shift+S를 눌렀더니 메뉴가 닫혀 버렸습니다. 코스의 치트시트가 알려주는 해결법은 무엇인가요?",
    "option1": "Win+Shift+S의 '창' 모드로 바꿔서 다시 시도한다",
    "option2": "F12 개발자 도구에서 전체 페이지 스크린샷을 내려받는다",
    "option3": "PrtScn으로 화면 전체를 찍은 뒤 필요한 부분만 나중에 잘라낸다",
    "option4": "Win+G 게임 바로 화면을 녹화한 뒤 정지 화면을 캡처한다",
    "answerIndex": 2,
    "explanation": "섹션 3의 치트시트에 따르면 Win+Shift+S는 누르는 순간 열려 있던 메뉴가 닫히므로, 메뉴가 열린 상태에서 PrtScn으로 전체 화면을 클립보드에 담고 나중에 잘라내는 것이 요령입니다."
  },
  {
    "courseSlug": "tool-dbeaver",
    "question": "코스에서 설명하는 DBeaver의 정체로 가장 알맞은 것은?",
    "option1": "PostgreSQL을 대체하는 새로운 종류의 데이터베이스",
    "option2": "DB에 접속해 화면으로 보여 주는 GUI 클라이언트",
    "option3": "Docker 컨테이너를 관리하는 서버 모니터링 도구",
    "option4": "SQL 없이 데이터를 저장하는 스프레드시트 프로그램",
    "answerIndex": 1,
    "explanation": "코스 1섹션에서 DBeaver는 DB 그 자체가 아니라, 컨테이너 안에서 돌고 있는 PostgreSQL에 접속해 보여 주기만 하는 GUI 클라이언트(창문)라고 설명합니다."
  },
  {
    "courseSlug": "tool-dbeaver",
    "question": "우리 로컬 개발 DB에 연결할 때 입력하는 접속 정보로 옳은 것은?",
    "option1": "Host: localhost, Port: 5432, Database: mirim, Username: mirim",
    "option2": "Host: mirim-postgres, Port: 8080, Database: public, Username: admin",
    "option3": "Host: 127.0.0.1, Port: 3306, Database: mirim, Username: postgres",
    "option4": "Host: localhost, Port: 5432, Database: public, Username: mirim123",
    "answerIndex": 0,
    "explanation": "3섹션의 접속 정보에 따르면 내 PC에서 도는 DB이므로 Host는 localhost, Port는 PostgreSQL 기본값 5432, Database와 Username은 둘 다 mirim입니다."
  },
  {
    "courseSlug": "tool-dbeaver",
    "question": "SQL 편집기에서 커서가 있는 문장 하나만 실행하는 단축키는?",
    "option1": "Alt+X",
    "option2": "Ctrl+]",
    "option3": "Ctrl+Enter",
    "option4": "Win+Shift+S",
    "answerIndex": 2,
    "explanation": "6섹션의 단축키 정리에서 커서가 있는 문장 하나 실행은 Ctrl+Enter, 편집기 전체 실행은 Alt+X입니다. Ctrl+]는 SQL 편집기를 여는 단축키예요."
  },
  {
    "courseSlug": "tool-dbeaver",
    "question": "Data 탭에서 셀을 더블클릭해 값을 고쳤을 때, 그 변경이 진짜 DB에 반영되는 시점은?",
    "option1": "셀에서 엔터를 누르는 순간 바로 반영된다",
    "option2": "테이블 탭을 닫으면 자동으로 반영된다",
    "option3": "DBeaver를 재시작해야 반영된다",
    "option4": "아래쪽 Save(저장) 버튼을 눌러야 반영된다",
    "answerIndex": 3,
    "explanation": "5섹션에 따르면 셀 더블클릭으로 값을 수정해도 엔터만으로는 저장되지 않고, 아래쪽 Save(저장)를 눌러야 그때서야 진짜 DB에 반영됩니다."
  },
  {
    "courseSlug": "tool-dbeaver",
    "question": "수습 개발자 지연이가 운영 서버(EC2) 데이터를 확인할 일이 생겨, 자기 노트북 DBeaver에 운영 DB 접속 정보를 넣으려 한다. 코스의 철칙에 따르면 올바른 행동은?",
    "option1": "조회(SELECT)만 하고 수정하지 않는다는 조건으로 연결한다",
    "option2": "연결하지 말고 먼저 멘토에게 이야기해서 방법을 상의한다",
    "option3": "Test Connection까지만 눌러 보고 Finish는 누르지 않는다",
    "option4": "확인이 끝난 뒤 저장된 연결을 바로 삭제하는 조건으로 연결한다",
    "answerIndex": 1,
    "explanation": "7섹션의 철칙은 개인 PC의 DBeaver로는 로컬 개발 DB에만 연결하고 운영 DB 접속 정보는 넣지 않는 것입니다. 실수 한 줄로 실제 사용자 사고가 나고 접속 정보가 유출 통로가 될 수 있어, 필요하면 반드시 멘토에게 먼저 이야기해야 합니다."
  },
  {
    "courseSlug": "tool-devtools",
    "question": "화면에서 지금 보고 있는 특정 버튼의 코드가 궁금할 때, 그 요소로 바로 이동하는 가장 빠른 방법은 무엇인가요?",
    "option1": "F12를 눌러 Elements 탭에서 트리를 하나씩 펼쳐 찾는다",
    "option2": "Ctrl + Shift + M을 눌러 기기 프리셋을 고른다",
    "option3": "그 요소를 우클릭한 뒤 \"검사\"를 클릭한다",
    "option4": "Console 탭에 document.title을 입력한다",
    "answerIndex": 2,
    "explanation": "코스의 '여는 법 3가지' 중 우클릭 → \"검사\"는 클릭한 그 요소로 바로 점프하는 지름길로 소개됩니다. F12는 도구를 열 뿐 특정 요소로 직행하지는 않아요."
  },
  {
    "courseSlug": "tool-devtools",
    "question": "Elements 탭에서 뉴스 사이트 헤드라인을 \"우리 반 급식 최고\"로 바꾼 뒤 새로고침(F5)을 하면 어떻게 되나요?",
    "option1": "서버 파일도 함께 바뀌어 다른 사람에게도 바뀐 화면이 보인다",
    "option2": "수정 내용이 전부 사라지고 원래 화면으로 돌아온다",
    "option3": "브라우저에 저장되어 내 컴퓨터에서는 계속 바뀐 채로 보인다",
    "option4": "개발자도구를 닫기 전까지는 수정한 내용이 그대로 유지된다",
    "answerIndex": 1,
    "explanation": "Elements에서의 수정은 '내 브라우저의 복사본'에만 적용되고 서버의 진짜 파일은 전혀 바뀌지 않아요. 그래서 새로고침 한 번이면 전부 원상복구됩니다."
  },
  {
    "courseSlug": "tool-devtools",
    "question": "Network 탭에서 어떤 API 요청의 Status가 401로 표시되었습니다. 코스의 '빠른 판독법'에 따르면 이 상황의 의미는 무엇인가요?",
    "option1": "서버 내부에서 오류가 발생했다는 뜻이다",
    "option2": "요청이 성공적으로 처리되었다는 뜻이다",
    "option3": "요청한 주소가 존재하지 않는다는 뜻이다",
    "option4": "로그인이 안 된 상태로 요청을 보냈다는 뜻이다",
    "answerIndex": 3,
    "explanation": "코스에서 401은 \"로그인 안 됨\"을 뜻하는 단골 상태 코드로 소개됩니다. 4xx는 내(요청) 잘못, 5xx는 서버 잘못, 404는 \"그런 주소 없음\"이에요."
  },
  {
    "courseSlug": "tool-devtools",
    "question": "Lighthouse 검사 결과에서 점수 아래에 나오는 'Opportunities' 항목이 알려 주는 것은 무엇인가요?",
    "option1": "어떤 부분을 어떻게 고치면 좋아지는지에 대한 개선 제안",
    "option2": "페이지에서 발생한 자바스크립트 에러의 전체 목록",
    "option3": "서버와 주고받은 모든 요청과 응답의 기록",
    "option4": "다른 사이트와 비교한 우리 페이지의 점수 순위",
    "answerIndex": 0,
    "explanation": "Opportunities에는 \"이미지가 너무 커요, 이만큼 줄이면 몇 초 빨라져요\" 같은 구체적인 개선 방법이 적혀 있어요. 그래서 코스는 Lighthouse를 브라우저에 내장된 무료 컨설턴트에 비유합니다."
  },
  {
    "courseSlug": "tool-devtools",
    "question": "수습 개발자가 만든 게시글 목록 페이지에 목록이 안 나옵니다. Network 탭에서 해당 요청을 확인하니 Status는 200이고 Response에는 게시글 JSON이 제대로 들어 있었어요. 코스의 판별법에 따르면 어디를 먼저 의심해야 하나요?",
    "option1": "Spring Boot 서버 쪽 코드",
    "option2": "데이터베이스 연결 설정",
    "option3": "React 화면 쪽 코드",
    "option4": "네트워크 회선 상태",
    "answerIndex": 2,
    "explanation": "코스의 판별법대로 Response에 데이터가 멀쩡히 있는데 화면에 안 나오면 React(화면) 쪽 문제예요. Status가 4xx/5xx일 때가 요청이나 서버 쪽 문제입니다."
  },
  {
    "courseSlug": "tool-docker",
    "question": "Docker Desktop 왼쪽 메뉴의 세 탭을 코스의 비유로 짝지은 것 중 옳은 것은?",
    "option1": "Containers=설계도 창고, Images=돌아가는 기계, Volumes=데이터 금고",
    "option2": "Containers=데이터 금고, Images=돌아가는 기계, Volumes=설계도 창고",
    "option3": "Containers=돌아가는 기계, Images=설계도 창고, Volumes=데이터 금고",
    "option4": "Containers=돌아가는 기계, Images=데이터 금고, Volumes=설계도 창고",
    "answerIndex": 2,
    "explanation": "섹션 2에서 이미지는 설계도(붕어빵 틀), 컨테이너는 그 틀로 찍어낸 지금 돌아가는 기계, 볼륨은 지워지면 안 되는 데이터를 보관하는 금고에 비유했습니다."
  },
  {
    "courseSlug": "tool-docker",
    "question": "Windows에 Docker Desktop을 설치할 때 WSL2를 함께 켜는 이유로 코스가 설명한 것은?",
    "option1": "Windows 보안 업데이트를 컨테이너에도 자동으로 적용하기 위해서",
    "option2": "컨테이너 기술이 리눅스 기능을 쓰므로, Windows 안에 작은 리눅스 방을 만들어 그 안에서 엔진을 돌리기 위해서",
    "option3": "Docker Desktop의 GUI 화면을 그리는 그래픽 가속이 WSL2에서만 지원되기 때문에",
    "option4": "여러 PC의 컨테이너를 하나의 네트워크로 묶어 주는 기능이 WSL2에 들어 있어서",
    "answerIndex": 1,
    "explanation": "섹션 1의 핵심 — Docker는 리눅스에서 태어난 기술이라, Windows에서는 WSL2라는 '리눅스 방'을 만들고 그 안에서 진짜 엔진을 돌립니다."
  },
  {
    "courseSlug": "tool-docker",
    "question": "docker ps와 docker ps -a의 차이, 그리고 Docker Desktop Containers 탭의 기본 동작에 대한 설명으로 옳은 것은?",
    "option1": "docker ps는 멈춘 컨테이너까지 보여 주고, Containers 탭은 실행 중인 것만 보여 준다",
    "option2": "둘은 같은 결과를 내며, Containers 탭은 이미지 목록까지 한 화면에 같이 보여 준다",
    "option3": "docker ps -a는 볼륨 목록까지 함께 보여 주고, Containers 탭은 docker ps와 똑같다",
    "option4": "docker ps는 지금 도는 것만, docker ps -a는 멈춘 것까지 보여 주며 Containers 탭은 -a 쪽에 가깝다",
    "answerIndex": 3,
    "explanation": "섹션 4에서 docker ps는 실행 중인 컨테이너만, -a는 멈춘 것까지 전부 보여 주고, Desktop의 Containers 탭은 멈춘 컨테이너도 회색으로 표시하는 -a 쪽이라고 설명합니다."
  },
  {
    "courseSlug": "tool-docker",
    "question": "docker system prune에 --volumes 옵션을 붙일 때 특히 조심해야 하는 이유는?",
    "option1": "안 쓰는 볼륨까지 지워지는데, 볼륨에는 DB 데이터처럼 잃으면 안 되는 것이 들어 있을 수 있어서",
    "option2": "실행 중인 컨테이너까지 강제로 중지시킨 뒤 목록에서 전부 삭제해 버려서",
    "option3": "받아 둔 모든 이미지가 삭제되어 다시 다운로드하는 데 시간이 오래 걸려서",
    "option4": "WSL2 설정 파일(.wslconfig)까지 초기화되어 메모리 상한선이 사라져서",
    "answerIndex": 0,
    "explanation": "섹션 6의 경고 — prune 자체는 안전한 편이지만 --volumes를 붙이면 안 쓰는 볼륨(DB 데이터가 들어 있을 수 있는 금고)까지 지우므로 확인 문구를 꼭 읽어야 합니다."
  },
  {
    "courseSlug": "tool-docker",
    "question": "아침에 백엔드(Spring Boot)를 실행했더니 DB 연결 에러가 났다. 코스의 아침 루틴대로라면 가장 먼저 해야 할 일은?",
    "option1": "백엔드 코드의 DB 접속 설정 파일을 열어 오타부터 찾아본다",
    "option2": "docker system prune으로 도커를 한 번 정리한 뒤 백엔드를 다시 실행한다",
    "option3": "docker compose ps(또는 Containers 탭)로 DB 컨테이너가 Up 상태인지부터 확인한다",
    "option4": "PC를 재부팅해서 WSL2 메모리를 비운 다음 백엔드를 다시 시도한다",
    "answerIndex": 2,
    "explanation": "섹션 5에서 DB 연결 에러가 나면 '코드 문제인가?' 고민하기 전에 docker compose up -d / ps 루틴으로 DB가 살아 있는지부터 확인하는 것이 순서라고 강조합니다."
  },
  {
    "courseSlug": "tool-gitea",
    "question": "이 코스에서 설명하는 Gitea는 어떤 것인가요?",
    "option1": "GitHub가 만든 유료 클라우드 서비스",
    "option2": "GitHub 같은 서비스를 우리 회사 서버(AWS EC2 위 Docker 컨테이너)에 직접 설치해 쓰는 것",
    "option3": "코드를 자동으로 리뷰해 주는 인공지능 도구",
    "option4": "커밋 메시지를 대신 작성해 주는 편집기 확장 프로그램",
    "answerIndex": 1,
    "explanation": "섹션 1 '우리 Git 서버 투어'의 핵심입니다. 남의 건물(GitHub)에 세 들지 않고 우리 건물에 작업실을 차린 것이라, 회사 코드가 바깥으로 나가지 않습니다."
  },
  {
    "courseSlug": "tool-gitea",
    "question": "코스에서 배운 이슈(Issue)의 상태로 맞는 것은 무엇인가요?",
    "option1": "Open, In Progress, Done의 세 단계",
    "option2": "Draft, Ready, Merged의 세 단계",
    "option3": "Open(진행 중)과 Closed(끝)의 딱 두 가지",
    "option4": "New, Assigned, Resolved, Verified의 네 단계",
    "answerIndex": 2,
    "explanation": "섹션 3 '이슈로 할 일 관리'에서 이슈의 상태는 Open(진행 중)과 Closed(끝) 딱 두 가지라고 설명했습니다. 병원 접수증처럼 번호가 붙고 처리가 끝나면 닫힙니다."
  },
  {
    "courseSlug": "tool-gitea",
    "question": "코드 한 줄만 살짝 고쳐서 커밋했을 때, diff 화면에는 어떻게 표시되나요?",
    "option1": "노란색으로 강조된 한 줄로 표시된다",
    "option2": "회색 문맥 줄로만 표시되어 눈에 띄지 않는다",
    "option3": "초록(+) 한 줄만 새로 표시된다",
    "option4": "빨강(-) 한 줄과 초록(+) 한 줄의 짝으로 표시된다",
    "answerIndex": 3,
    "explanation": "섹션 5 'diff 읽는 법'에서 배운 내용입니다. diff에서 수정은 '삭제+추가'로 취급되어, 한 줄만 바뀌어도 지워진 빨강 줄과 새로 생긴 초록 줄이 짝으로 나타납니다."
  },
  {
    "courseSlug": "tool-gitea",
    "question": "PR 리뷰어가 바뀐 코드의 특정 줄에 정확히 코멘트를 달고 싶을 때, 올바른 방법은 무엇인가요?",
    "option1": "Conversation 탭의 댓글 본문에 줄 번호를 직접 적어서 설명한다",
    "option2": "Files Changed 탭에서 줄 번호에 마우스를 올려 나타나는 + 버튼을 클릭한다",
    "option3": "Commits 탭에서 해당 커밋의 메시지를 수정해 의견을 남긴다",
    "option4": "새 이슈를 만들어 줄 번호를 라벨(Label)로 붙여 전달한다",
    "answerIndex": 1,
    "explanation": "섹션 4 'Pull Request와 리뷰 화면'에서 리뷰어의 주무대는 Files Changed 탭이고, 줄 번호에 마우스를 올리면 나오는 + 버튼으로 그 줄에 정확히 코멘트를 달 수 있다고 배웠습니다. 교과서 여백에 포스트잇을 붙이는 것과 같은 방식입니다."
  },
  {
    "courseSlug": "tool-gitea",
    "question": "수습 개발자 민지는 어제까지 잘 되던 로그인 기능이 오늘 갑자기 안 되는 것을 발견했습니다. 이 코스에서 배운 대로라면 가장 먼저 확인해야 할 곳은 어디인가요?",
    "option1": "저장소의 가장 최근 커밋을 열어 diff에서 바뀐 줄을 확인한다",
    "option2": "위키(Wiki)에 트러블슈팅 페이지를 새로 만들어 기록한다",
    "option3": "Releases 탭에서 최신 버전 태그의 체인지로그를 다시 작성한다",
    "option4": "프로필의 잔디밭(활동 히트맵)에서 어제의 활동량을 살펴본다",
    "answerIndex": 0,
    "explanation": "섹션 5에서 '어제까지 되던 게 왜 안 되지?' 할 때는 마지막 커밋의 diff부터 확인하는 습관을 강조했습니다. 범인은 대부분 가장 최근에 바뀐 줄에 있기 때문입니다."
  },
  {
    "courseSlug": "tool-intellij",
    "question": "IntelliJ에서 Shift 키를 빠르게 두 번 누르면 실행되는 기능은 무엇인가?",
    "option1": "코드 정렬(Reformat) — 흐트러진 코드를 자동으로 정리한다",
    "option2": "프로젝트 전체 텍스트 검색 — 특정 문자열이 있는 파일을 찾는다",
    "option3": "뭐든지 검색(Search Everywhere) — 클래스·파일·설정·액션을 한 창에서 찾는다",
    "option4": "이름 바꾸기(Rename) — 변수나 메서드 이름을 사용처까지 전부 바꾼다",
    "answerIndex": 2,
    "explanation": "코스 1섹션의 필수 단축키 표에서 Shift Shift는 '뭐든지 검색(Search Everywhere)', 즉 만능 검색창이다. 코드 정렬은 Ctrl+Alt+L, 전체 텍스트 검색은 Ctrl+Shift+F, 이름 바꾸기는 Shift+F6이다."
  },
  {
    "courseSlug": "tool-intellij",
    "question": "라이브 템플릿에서 sout을 입력하고 Tab을 누르면 만들어지는 코드는 무엇인가?",
    "option1": "System.out.println(); — 디버깅용 출력문 한 줄",
    "option2": "public static void main(String[] args) { } — main 메서드",
    "option3": "for (int i = 0; i < ; i++) { } — 기본 for 반복문",
    "option4": "for (String item : items) { } — 컬렉션을 도는 향상된 for문",
    "answerIndex": 0,
    "explanation": "2섹션 라이브 템플릿 표에서 sout은 System.out.println();을 소환하는 주문이다. main 메서드는 psvm, 기본 for문은 fori, 향상된 for문은 iter가 만들어 준다."
  },
  {
    "courseSlug": "tool-intellij",
    "question": "IntelliJ의 로컬 히스토리(Local History)에 대한 설명으로 옳은 것은?",
    "option1": "Git 커밋과 자동으로 동기화되어 팀 전체에 공유된다",
    "option2": "PC를 바꿔도 계정에 연결되어 영구히 보관된다",
    "option3": "git commit 명령을 실행해야만 스냅샷이 기록된다",
    "option4": "파일을 저장할 때마다 IDE가 자동으로 스냅샷을 남기며, 보관 기간은 며칠 수준이다",
    "answerIndex": 3,
    "explanation": "3섹션에서 로컬 히스토리는 Git 커밋과 별개로 IDE가 저장 시마다 몰래 남기는 '비밀 일기장'이라고 설명한다. 다만 내 PC에만 며칠만 남는 임시 보험이므로 진짜 기록은 Git 커밋 + Gitea 푸시다."
  },
  {
    "courseSlug": "tool-intellij",
    "question": "브레이크포인트(빨간 점)를 찍었는데 프로그램이 그 줄에서 멈추지 않는다. 코스에서 '십중팔구'라고 말한 가장 흔한 원인은?",
    "option1": "Active profiles에 local을 넣지 않아 설정 파일이 잘못 적용되어서",
    "option2": "Debug(벌레 아이콘)가 아니라 Run(▶)으로 실행해서",
    "option3": "환경변수 칸에 DB_PASSWORD를 주입하지 않아서",
    "option4": "브레이크포인트에 조건을 걸지 않아 무시되어서",
    "answerIndex": 1,
    "explanation": "5섹션 경고에 따르면 빨간 점이 작동하려면 반드시 Debug(벌레 아이콘)로 실행해야 하며, 안 멈추는 경우는 십중팔구 Run(▶)으로 실행한 경우다. Run과 Debug는 형제지만 다르다."
  },
  {
    "courseSlug": "tool-intellij",
    "question": "반복문이 1,000번 도는데 i가 743일 때만 버그가 생긴다. 코스에서 소개한 방법 중 이 순간만 콕 집어 멈추는 가장 효율적인 방법은?",
    "option1": "sout으로 매번 i 값을 출력해 콘솔에서 눈으로 743을 찾는다",
    "option2": "브레이크포인트에서 F8(Step Over)을 743번 눌러 한 줄씩 전진한다",
    "option3": "빨간 점을 우클릭해 i == 743 조건을 적어 조건이 참일 때만 멈추게 한다",
    "option4": "i를 Add to Watches에 등록하면 743번째 반복에서 자동으로 멈춘다",
    "answerIndex": 2,
    "explanation": "4섹션 팁의 조건부 브레이크포인트 개념이다. 빨간 점을 우클릭해 조건(예: i == 743)을 적으면 그 조건이 참일 때만 멈춘다. Add to Watches는 값을 감시만 할 뿐 멈추게 하지는 않는다."
  },
  {
    "courseSlug": "tool-notion",
    "question": "노션에서 '접었다 펼치는 목록'으로 셀프 퀴즈를 만들 때 좋은 토글 블록을 만드는 입력법은 무엇인가요?",
    "option1": "/표 를 입력한다",
    "option2": "> 를 입력하고 스페이스를 누른다",
    "option3": "``` 를 입력한다",
    "option4": "# 을 입력하고 스페이스를 누른다",
    "answerIndex": 1,
    "explanation": "섹션 2의 기본 블록 4가지 표에서 토글 블록은 '> + 스페이스'로 만든다고 소개했어요. 제목만 보고 답을 떠올린 뒤 펼쳐서 확인하는 셀프 퀴즈에 딱 맞는 블록입니다."
  },
  {
    "courseSlug": "tool-notion",
    "question": "코스에서 노션 데이터베이스가 일반 표보다 강력한 이유로 꼽은 세 가지는 무엇인가요?",
    "option1": "필터, 정렬, 보기(표·칸반·캘린더 전환)",
    "option2": "수식 계산, 차트 생성, 자동 백업",
    "option3": "댓글 달기, 버전 기록, 권한 관리",
    "option4": "파일 첨부, 알림 설정, 외부 연동",
    "answerIndex": 0,
    "explanation": "섹션 5에서 데이터베이스가 표보다 강한 이유로 필터('상태 = 진행 중'만 보기), 정렬(마감일 순 줄 세우기), 보기(같은 데이터를 칸반·캘린더로 전환) 세 가지를 들었어요."
  },
  {
    "courseSlug": "tool-notion",
    "question": "코스에서 명령어를 스크린샷 대신 코드 블록으로 저장하라고 강조하는 가장 큰 이유는 무엇인가요?",
    "option1": "스크린샷보다 저장 용량을 훨씬 적게 차지하기 때문에",
    "option2": "노션이 이미지 업로드 개수를 제한하기 때문에",
    "option3": "복사 버튼으로 바로 붙여넣고 검색도 되는 '살아 있는 기록'이기 때문에",
    "option4": "코드 블록은 다른 사람이 수정할 수 없게 잠기기 때문에",
    "answerIndex": 2,
    "explanation": "섹션 4에서 스크린샷 속 명령어는 복사도 검색도 안 되는 '죽은 기록'이고, 코드 블록은 복사 버튼 클릭 한 번으로 터미널에 붙여넣을 수 있으며 언어 지정 시 색까지 입혀지는 '살아 있는 기록'이라고 설명했어요."
  },
  {
    "courseSlug": "tool-notion",
    "question": "추천 페이지 구조에서 'TIL 일지'와 '트러블슈팅 모음'을 섞지 말고 따로 두라고 한 이유는 무엇인가요?",
    "option1": "한 페이지에 블록이 많아지면 노션이 느려지기 때문에",
    "option2": "트러블슈팅은 멘토에게만 공유해야 하기 때문에",
    "option3": "일지는 데이터베이스로 만들 수 없기 때문에",
    "option4": "일지는 날짜순, 해결법은 주제순으로 찾는 방법이 서로 다르기 때문에",
    "answerIndex": 3,
    "explanation": "섹션 3의 페이지 구조에서 '일지는 날짜순, 해결법은 주제순 — 찾는 방법이 다르니까요'라고 설명했어요. 기록은 나중에 꺼내 쓰는 방식에 맞춰 정리해야 합니다."
  },
  {
    "courseSlug": "tool-notion",
    "question": "수습생 지우가 'port is already allocated' 에러로 막혔던 Docker 포트 충돌 문제를 해결하고 트러블슈팅 페이지를 쓰려고 해요. '3개월 뒤의 나는 남이다'라는 코스의 규칙에 가장 맞는 제목은 무엇인가요?",
    "option1": "오늘의 삽질 대탈출기",
    "option2": "2026-07-16 문제 해결 기록",
    "option3": "Docker 포트 충돌(port is already allocated) 해결",
    "option4": "드디어 빨간 에러를 잡았다!",
    "answerIndex": 2,
    "explanation": "섹션 6의 '검색되는 기록' 규칙에 따르면 에러 메시지 원본과 핵심 단어를 제목에 넣어야 나중의 내가 검색으로 바로 찾을 수 있어요. 나머지 제목들은 3개월 뒤에 무슨 내용인지 검색할 단서가 없습니다."
  },
  {
    "courseSlug": "tool-postman",
    "question": "이 코스에서 설명하는 Postman의 핵심 역할은 무엇인가?",
    "option1": "프론트 화면을 자동으로 만들어 주는 UI 생성 도구다",
    "option2": "백엔드 코드의 문법 오류를 찾아 고쳐 주는 편집기다",
    "option3": "프론트 화면 없이 백엔드 API 엔드포인트를 직접 호출해 보는 도구다",
    "option4": "데이터베이스 테이블을 시각적으로 관리해 주는 도구다",
    "answerIndex": 2,
    "explanation": "섹션 1에서 Postman은 프론트라는 중간 단계를 건너뛰고 백엔드의 문(엔드포인트)을 직접 두드리는 도구라고 설명한다. 이렇게 하면 버그가 프론트 탓인지 백엔드 탓인지 빠르게 가려낼 수 있다."
  },
  {
    "courseSlug": "tool-postman",
    "question": "로그인 API(POST /api/auth/login)를 호출할 때 이메일과 비밀번호 JSON을 담아 보내는 올바른 방법은?",
    "option1": "Body 탭에서 raw를 선택하고 형식을 JSON으로 바꾼 뒤 입력한다",
    "option2": "Params 탭에 email과 password를 쿼리 값으로 추가한다",
    "option3": "Headers 탭에 email과 password 값을 직접 적어 넣는다",
    "option4": "주소창 끝에 이메일과 비밀번호를 이어 붙여서 보낸다",
    "answerIndex": 0,
    "explanation": "섹션 4의 실습 순서대로 POST 요청은 Body 탭 → raw 선택 → 오른쪽 드롭다운을 JSON으로 바꾼 뒤 데이터를 입력한다. POST는 서버에 데이터를 실어 보내는 요청이고 그 짐칸이 Body다."
  },
  {
    "courseSlug": "tool-postman",
    "question": "수습(INTERN) 계정으로 로그인한 상태에서 관리자 전용 API를 호출했다. 코스 내용에 따르면 정상적으로 받게 되는 응답은?",
    "option1": "400 Bad Request — 요청 형식이 잘못되었기 때문",
    "option2": "401 Unauthorized — 서버가 내가 누구인지 모르기 때문",
    "option3": "404 Not Found — 그런 주소가 존재하지 않기 때문",
    "option4": "403 Forbidden — 누구인지는 알지만 권한이 없기 때문",
    "answerIndex": 3,
    "explanation": "섹션 7에서 401은 '너 누군지 몰라'(로그인 안 됨), 403은 '너 누군지 아는데 이건 안 돼'(권한 부족)라고 구분한다. 수습 계정으로 관리자 API를 부르면 403이 오는 게 정상이다."
  },
  {
    "courseSlug": "tool-postman",
    "question": "요청 주소를 {{baseUrl}}/api/checklist처럼 변수로 써 두면 얻는 이점으로 코스가 설명한 것은?",
    "option1": "요청 처리 속도가 빨라져서 응답 Time이 줄어든다",
    "option2": "오른쪽 위 드롭다운에서 환경만 바꾸면 컬렉션 전체가 다른 서버를 향한다",
    "option3": "쿠키가 자동으로 보관되어 로그인 상태가 계속 유지된다",
    "option4": "주소가 암호화되어 운영 서버로 더 안전하게 전송된다",
    "answerIndex": 1,
    "explanation": "섹션 6의 환경(Environment) 개념으로, baseUrl 같은 변수를 중괄호 두 겹 문법으로 빼 두면 local과 prod를 스위치 하나로 전환할 수 있다. 주소를 매번 고치다 생기는 실수를 막는 방식이다."
  },
  {
    "courseSlug": "tool-postman",
    "question": "브라우저 화면에서 체크리스트가 이상하게 보여서 Postman으로 GET /api/checklist를 직접 호출했더니 설계서와 똑같은 올바른 JSON이 돌아왔다. 코스의 식당 비유에 따르면 다음으로 해야 할 판단은?",
    "option1": "백엔드 코드에 버그가 있으므로 컨트롤러부터 수정한다",
    "option2": "DB 데이터가 잘못되었으므로 테이블을 직접 고친다",
    "option3": "백엔드는 정상이므로 프론트(React) 쪽 코드를 의심해 본다",
    "option4": "네트워크 장애가 원인이므로 서버를 재시작해 본다",
    "answerIndex": 2,
    "explanation": "식당 비유에서 주방(백엔드)에 직접 주문해 제대로 된 음식이 나오면 문제는 홀(프론트)에 있다. 백엔드 응답이 명세와 같다면 용의자에서 백엔드를 지우고 프론트를 살펴보는 것이 코스가 가르치는 수사 순서다."
  },
  {
    "courseSlug": "tool-shortcuts",
    "question": "회사에서 자리를 비울 때 반드시 눌러야 하는 '보안 기본기' 단축키는 무엇인가요?",
    "option1": "Win + Z — 스냅 레이아웃 열기",
    "option2": "Win + L — 화면 잠금",
    "option3": "Win + Tab — 작업 보기 열기",
    "option4": "Win + D — 바탕화면 보기",
    "answerIndex": 1,
    "explanation": "코스 2섹션(창 관리)에서 Win+L은 화면 잠금이며, 잠그지 않고 자리를 뜨는 건 현관문을 열어 두고 외출하는 것과 같다고 강조합니다."
  },
  {
    "courseSlug": "tool-shortcuts",
    "question": "브라우저에서 실수로 중요한 문서 탭을 닫아 버렸을 때, 코스가 '실수 복구의 은인'이라고 소개한 단축키는?",
    "option1": "Ctrl + T — 새 탭 열기",
    "option2": "Ctrl + W — 현재 탭 닫기",
    "option3": "Ctrl + Shift + T — 방금 닫은 탭 되살리기",
    "option4": "Ctrl + 9 — 마지막 탭으로 이동",
    "answerIndex": 2,
    "explanation": "4섹션(탐색기·브라우저)에서 Ctrl+Shift+T는 닫은 탭을 되살리는 단축키로, 여러 번 누르면 닫은 순서의 역순으로 계속 되살아난다고 설명합니다."
  },
  {
    "courseSlug": "tool-shortcuts",
    "question": "텍스트 편집의 '조합 공식'에 따르면, Ctrl + Shift + End를 눌렀을 때 일어나는 일은 무엇인가요?",
    "option1": "커서 위치부터 문서 끝까지 전부 선택된다",
    "option2": "커서가 있는 줄의 끝까지만 선택된다",
    "option3": "문서 전체가 한 번에 선택된다",
    "option4": "커서가 문서 맨 아래로 이동만 하고 선택은 되지 않는다",
    "answerIndex": 0,
    "explanation": "3섹션의 공식대로 Ctrl은 '문서 단위로 크게 이동', Shift는 '이동하면서 선택'이라 둘을 곱하면 커서부터 문서 끝까지 통째로 선택됩니다."
  },
  {
    "courseSlug": "tool-shortcuts",
    "question": "코스에서 Win + V(클립보드 히스토리)를 쓸 때 특히 주의하라고 한 것은 무엇인가요?",
    "option1": "히스토리를 켜면 복사 속도가 느려지므로 필요할 때만 켠다",
    "option2": "고정(pin)한 항목은 재부팅하면 모두 사라지므로 다시 등록한다",
    "option3": "목록에는 텍스트만 저장되고 이미지 복사는 남지 않는다",
    "option4": "복사한 비밀번호나 토큰도 목록에 남으므로 바로 삭제한다",
    "answerIndex": 3,
    "explanation": "5섹션의 주의 박스에서, 민감한 값을 복사하면 히스토리 목록에 그대로 남아 남이 볼 수 있으니 바로 삭제하고 비밀번호는 관리자의 자동 입력으로 다루라고 안내합니다."
  },
  {
    "courseSlug": "tool-shortcuts",
    "question": "수습 개발자 지민이는 코딩 중에 메신저 알림이 눈에 띌 때마다 집중이 끊깁니다. 이 코스의 권장 해결법은 무엇인가요?",
    "option1": "알림이 올 때마다 Win + D로 바탕화면을 보며 흐름을 끊고 쉰다",
    "option2": "메신저 창을 Win + ↓로 최소화해 두고 필요할 때만 다시 연다",
    "option3": "Win + Ctrl + D로 가상 데스크톱을 만들어 코딩 책상과 메신저 책상을 분리한다",
    "option4": "Alt + Tab으로 재빨리 전환해 알림을 그때그때 확인하고 돌아온다",
    "answerIndex": 2,
    "explanation": "2섹션의 '책상을 여러 개 쓰는' 비유처럼, 가상 데스크톱으로 코딩(1번)과 메신저(3번)를 분리하면 알림에 홀려 딴 길로 새는 걸 물리적으로 막을 수 있다고 설명합니다."
  },
  {
    "courseSlug": "tool-terminal",
    "question": "코스의 '검은 창 삼형제' 정리에 따르면, 윈도우에서 평소에 기본으로 쓰기 좋은 셸과 리눅스 명령을 그대로 따라 하고 싶을 때 쓰는 셸의 조합으로 맞는 것은?",
    "option1": "평소엔 cmd, 리눅스 명령은 PowerShell",
    "option2": "평소엔 PowerShell, 리눅스 명령은 Git Bash",
    "option3": "평소엔 Git Bash, 리눅스 명령은 cmd",
    "option4": "평소엔 cmd, 리눅스 명령은 Git Bash",
    "answerIndex": 1,
    "explanation": "섹션 1의 결론이 '평소엔 PowerShell, 리눅스 명령이 필요하면 Git Bash'입니다. cmd는 아주 오래된 배치 파일을 돌릴 때 정도로, 요즘은 거의 쓰지 않는다고 정리했어요."
  },
  {
    "courseSlug": "tool-terminal",
    "question": "코스에서 '터미널'과 '셸(shell)'의 관계를 설명한 내용으로 옳은 것은?",
    "option1": "터미널은 명령을 해석하는 프로그램이고, 셸은 그 결과를 그려 주는 화면이다",
    "option2": "터미널과 셸은 같은 것을 부르는 두 가지 이름일 뿐 구분할 필요가 없다",
    "option3": "터미널은 글자가 표시되는 창이고, 셸은 그 안에서 명령을 해석해 주는 통역 프로그램이다",
    "option4": "터미널은 윈도우 전용 용어이고, 셸은 리눅스 전용 용어라서 서로 섞어 쓸 수 없다",
    "answerIndex": 2,
    "explanation": "섹션 1에서 터미널은 글자가 표시되는 창(카페), 셸은 그 안에서 명령을 해석하는 통역 프로그램(바리스타)이라고 비유했습니다. 그래서 한 Windows Terminal 창에 PowerShell 탭과 Git Bash 탭을 동시에 열 수 있어요."
  },
  {
    "courseSlug": "tool-terminal",
    "question": "블로그에서 복사한 rm -rf 폴더 명령이 PowerShell에서 에러가 나는 이유로, 코스의 설명과 일치하는 것은?",
    "option1": "PowerShell에는 rm 명령 자체가 없어서 New-Item으로 지워야 하기 때문이다",
    "option2": "관리자 권한 터미널이 아니어서 액세스가 거부되었기 때문이다",
    "option3": "rm은 Git Bash 전용 명령이라 PowerShell 탭에서는 아예 입력이 막히기 때문이다",
    "option4": "PowerShell의 rm은 Remove-Item의 별명(alias)이라 -rf 옵션이 없고, rm -r -fo처럼 써야 하기 때문이다",
    "answerIndex": 3,
    "explanation": "섹션 3에서 PowerShell의 ls·cd·cat·rm은 진짜 리눅스 명령이 아니라 별명(alias)이라고 했어요. 간단한 사용은 통하지만 -rf 같은 옵션부터는 달라서, PowerShell식으로는 rm -r -fo(Recurse + Force)를 써야 합니다."
  },
  {
    "courseSlug": "tool-terminal",
    "question": "코스에서 배운 파이프(|) 기호를 기준으로, tasklist | findstr node 명령의 의미로 옳은 것은?",
    "option1": "tasklist의 전체 출력 중에서 node가 들어간 줄만 걸러서 보여 준다",
    "option2": "tasklist와 findstr node를 동시에 병렬로 실행해서 속도를 높인다",
    "option3": "node 프로세스를 찾아낸 뒤 그 프로세스를 자동으로 강제 종료한다",
    "option4": "tasklist의 결과를 node라는 이름의 파일로 저장해 두고 나중에 연다",
    "answerIndex": 0,
    "explanation": "섹션 4에서 파이프(|)는 '앞 명령의 출력을 뒤 명령에게 넘겨주는 배관'이라고 설명했어요. 전체 프로세스 목록을 뽑아서(tasklist) 그중 node가 들어간 줄만 걸러 주는(findstr) 것이지, 종료나 저장을 하지는 않습니다."
  },
  {
    "courseSlug": "tool-terminal",
    "question": "개발 서버를 분명히 껐는데 npm run dev를 다시 켜니 '포트가 이미 사용 중입니다(5173)' 에러가 납니다. 코스에서 배운 3단계 해결법대로라면 가장 먼저 해야 할 일은?",
    "option1": "taskkill /f /im node.exe 로 node 프로세스를 전부 즉시 종료한다",
    "option2": "netstat -ano | findstr :5173 으로 그 포트를 쓰고 있는 프로세스의 PID를 찾는다",
    "option3": "관리자 권한 터미널을 열어 방화벽에서 5173 포트 규칙을 바꾼다",
    "option4": "ipconfig 로 내 IP 주소와 게이트웨이 정보부터 확인한다",
    "answerIndex": 1,
    "explanation": "섹션 4의 실전 3단계는 1) netstat -ano | findstr :5173 으로 범인 PID 찾기, 2) tasklist로 어떤 프로그램인지 확인, 3) taskkill /f /pid 로 종료 순서예요. 무엇이 포트를 잡고 있는지 확인하기 전에 무작정 전부 죽이는 건 순서가 아닙니다."
  },
  {
    "courseSlug": "tool-vscode",
    "question": "코스에서 \"이 중에 딱 하나만 고르라면\"이라고 강조한, VS Code의 모든 기능을 검색해서 실행할 수 있는 단축키는 무엇인가요?",
    "option1": "Ctrl+P",
    "option2": "Ctrl+Shift+F",
    "option3": "Ctrl+Shift+P",
    "option4": "Ctrl+Shift+K",
    "answerIndex": 2,
    "explanation": "Ctrl+Shift+P는 명령 팔레트를 여는 단축키로, 다른 단축키를 까먹어도 팔레트에 기능 이름만 치면 실행할 수 있다고 코스 섹션 1에서 강조합니다."
  },
  {
    "courseSlug": "tool-vscode",
    "question": "추천 확장 4종 중 Prettier가 하는 일로 코스에서 설명한 것은 무엇인가요?",
    "option1": "코드 한 줄마다 누가 언제 왜 고쳤는지 표시해 준다",
    "option2": "저장할 때마다 들쭉날쭉한 코드를 자동으로 정렬해 준다",
    "option3": "rafce를 치고 Tab을 누르면 컴포넌트 뼈대를 완성해 준다",
    "option4": "메뉴 전체를 한국어로 바꿔 진입장벽을 낮춰 준다",
    "answerIndex": 1,
    "explanation": "Prettier는 format on save 설정을 켜면 Ctrl+S를 누를 때마다 코드를 자동 정렬해 주는 확장입니다. 나머지 보기는 각각 GitLens, ES7+ React Snippets, Korean Language Pack의 설명이에요."
  },
  {
    "courseSlug": "tool-vscode",
    "question": "섹션 6 '검색의 기술'에서 설명한 Ctrl+P와 Ctrl+Shift+F의 용도 구분으로 옳은 것은 무엇인가요?",
    "option1": "Ctrl+P는 파일 이름으로 찾고, Ctrl+Shift+F는 파일 내용으로 프로젝트 전체를 검색한다",
    "option2": "Ctrl+P는 현재 파일 안을 검색하고, Ctrl+Shift+F는 파일 이름으로 찾는다",
    "option3": "Ctrl+P는 정규식 검색 전용이고, Ctrl+Shift+F는 특정 확장자만 검색한다",
    "option4": "Ctrl+P는 최근 연 파일을 전환하고, Ctrl+Shift+F는 명령 팔레트를 연다",
    "answerIndex": 0,
    "explanation": "코스는 Ctrl+P를 '도서관에서 책 제목으로 찾기'(파일 이름), Ctrl+Shift+F를 '책 속 문장으로 찾기'(내용 전체 검색)에 비유하며 두 검색의 용도를 구분합니다."
  },
  {
    "courseSlug": "tool-vscode",
    "question": "VS Code 소스 제어(Git) 패널에서 파일 옆의 + 버튼을 누르는 것은 어떤 git 명령과 같다고 코스에서 설명하나요?",
    "option1": "git push — 원격 저장소로 올리기",
    "option2": "git commit — 변경 내용을 기록하기",
    "option3": "git status — 작업 상태 확인하기",
    "option4": "git add — 변경 파일을 스테이징하기",
    "answerIndex": 3,
    "explanation": "섹션 4에서 파일 옆 + 버튼은 스테이징, 즉 git add와 같다고 설명합니다. 커밋은 위 입력창의 체크 버튼, push+pull은 '변경 내용 동기화' 버튼이에요."
  },
  {
    "courseSlug": "tool-vscode",
    "question": "수습생 지우는 집 PC에서 확장 설치와 단축키·설정을 공들여 맞춰 두었는데, 내일부터는 학교 PC에서도 똑같은 환경으로 작업하고 싶습니다. 코스에서 배운 내용대로라면 어떻게 해야 할까요?",
    "option1": "확장 목록을 종이에 적어 두고 학교 PC에서 하나씩 다시 설치한다",
    "option2": "설정 파일을 USB에 복사해서 학교 PC의 폴더에 붙여넣는다",
    "option3": "설정 동기화를 켜고 GitHub 계정으로 로그인해 두면 학교 PC에서도 로그인 한 번으로 복원된다",
    "option4": "Git 패널에서 설정을 커밋해 Gitea에 올린 뒤 학교 PC에서 내려받는다",
    "answerIndex": 2,
    "explanation": "섹션 5의 설정 동기화(Settings Sync)는 설정·단축키·확장 목록·스니펫을 계정에 백업해 주는 기능으로, 코스에서 '게임의 클라우드 세이브'에 비유합니다. 어떤 PC든 로그인 한 번이면 내 환경이 복원돼요."
  },
  {
    "courseSlug": "troubleshooting",
    "question": "코스에서 작업 관리자를 여는 방법 세 가지 중 '가장 빠른 지름길'로 소개한 것은?",
    "option1": "Ctrl + Alt + Delete를 누른 뒤 작업 관리자를 선택한다",
    "option2": "Ctrl + Shift + Esc를 누른다",
    "option3": "작업 표시줄을 우클릭한 뒤 작업 관리자를 선택한다",
    "option4": "Windows 키 + R을 누른 뒤 taskmgr을 입력한다",
    "answerIndex": 1,
    "explanation": "섹션 2에서 작업 관리자를 여는 세 가지 방법 중 Ctrl+Shift+Esc를 '가장 빠른 지름길'로 소개했습니다. 나머지 두 방법보다 단계가 적어 한 번에 열립니다."
  },
  {
    "courseSlug": "troubleshooting",
    "question": "백업의 3-2-1 원칙에서 숫자 '1'이 뜻하는 것은 무엇인가?",
    "option1": "백업은 하루에 1번만 하면 충분하다는 뜻이다",
    "option2": "원본 파일은 반드시 1개만 유지해야 한다는 뜻이다",
    "option3": "복사본 중 1개는 다른 장소에 보관해야 한다는 뜻이다",
    "option4": "저장 매체는 1가지 종류로 통일해야 한다는 뜻이다",
    "answerIndex": 2,
    "explanation": "섹션 4의 3-2-1 원칙은 복사본 3개, 서로 다른 매체 2가지, 그중 1개는 다른 장소 보관입니다. 노트북과 외장하드를 같은 가방에 넣으면 가방을 잃는 순간 둘 다 사라지기 때문입니다."
  },
  {
    "courseSlug": "troubleshooting",
    "question": "블루스크린(BSOD)이 떴을 때 코스에서 폰 카메라로 찍어서라도 꼭 챙기라고 한 '단서 두 개'는?",
    "option1": "중지 코드와 실패한 .sys 파일명",
    "option2": "윈도우 버전과 마지막 업데이트 날짜",
    "option3": "CPU 온도와 램(메모리) 사용량",
    "option4": "에러 발생 시각과 실행 중이던 프로그램 목록",
    "answerIndex": 0,
    "explanation": "섹션 3에서 단서 ①은 중지 코드(사망 원인명), 단서 ②는 .sys 파일명(용의자 실명)이라고 했습니다. 이 두 줄을 그대로 검색하는 것이 문제 해결의 출발점입니다."
  },
  {
    "courseSlug": "troubleshooting",
    "question": "\"껐다 켜 보셨어요?\"라는 재부팅이 자주 통하는 이유로 코스가 설명한 원리는 무엇인가?",
    "option1": "디스크에 쌓인 임시 파일이 자동으로 삭제되기 때문이다",
    "option2": "CPU가 식으면서 스로틀링이 풀리기 때문이다",
    "option3": "윈도우가 재부팅 중에 손상된 파일을 복구해 주기 때문이다",
    "option4": "전원이 꺼지면 램의 내용이 사라져 꼬인 상태(state)가 초기화되기 때문이다",
    "answerIndex": 3,
    "explanation": "섹션 6의 핵심 단어는 상태(state)입니다. 램은 전원이 꺼지면 내용이 전부 사라지는 메모리라서, 재부팅하면 쌓인 찌꺼기와 꼬임이 통째로 리셋됩니다. 단, 증상만 지울 뿐 원인은 못 고칩니다."
  },
  {
    "courseSlug": "troubleshooting",
    "question": "수습 과제 중 콘솔에 \"Objects are not valid as a React child\" 에러가 떴고, 메시지 뒤에 내 프로젝트 폴더 경로도 붙어 있다. 코스의 검색 요령대로라면 가장 좋은 방법은?",
    "option1": "\"리액트 하다가 화면에 에러남\"처럼 상황을 내 말로 요약해서 검색한다",
    "option2": "에러 문장을 따옴표로 감싸 그대로 검색하되, 내 폴더 경로 같은 부분은 빼고 react 키워드를 더한다",
    "option3": "내 폴더 경로까지 포함한 전체 메시지를 한 글자도 빼지 않고 통째로 검색한다",
    "option4": "에러 메시지 대신 화면을 스크린샷으로 찍어 이미지 검색을 한다",
    "answerIndex": 1,
    "explanation": "섹션 7의 요령은 에러 메시지를 번역하지 말고 그대로 붙여넣고, 핵심 문구를 따옴표로 감싸고, 상황 키워드를 1~2개 더하는 것입니다. 단, 파일 경로처럼 '나만의 부분'은 빼야 같은 에러를 겪은 사람의 글을 찾을 수 있습니다."
  },
  {
    "courseSlug": "typography",
    "question": "코스에서 설명하는 세리프(Serif)와 산세리프(Sans-serif)를 구분하는 기준은 무엇인가?",
    "option1": "글자 기울기가 있는지 없는지",
    "option2": "획 끝에 장식(삐침)이 있는지 없는지",
    "option3": "글자 폭이 모두 같은지 다른지",
    "option4": "한글을 지원하는지 안 하는지",
    "answerIndex": 1,
    "explanation": "세리프는 획 끝에 장식(삐침)이 있는 폰트, 산세리프는 획 끝이 깔끔하게 잘린 폰트입니다. 섹션 1에서 다룬 폰트의 가장 큰 두 갈래 분류 기준이에요."
  },
  {
    "courseSlug": "typography",
    "question": "한글 폰트를 만들기 어려운 이유로 코스가 제시한 완성형 한글의 글자 수는 몇 자인가?",
    "option1": "약 100자",
    "option2": "2,350자",
    "option3": "11,172자",
    "option4": "24,000자",
    "answerIndex": 2,
    "explanation": "한글은 초성×중성×종성 조합으로 완성형 11,172자를 전부 그려야 합니다. 알파벳이 100자 남짓인 것과 비교해 만드는 비용이 커서 좋은 무료 한글 폰트가 귀하다는 것이 섹션 2의 핵심이에요."
  },
  {
    "courseSlug": "typography",
    "question": "문장에서 무언가를 강조할 때 코스가 제시한 우선순위를 순서대로 바르게 나열한 것은?",
    "option1": "색 변경 → 굵기 올리기 → 크기 키우기",
    "option2": "크기 키우기 → 색 변경 → 굵기 올리기",
    "option3": "색 변경 → 크기 키우기 → 굵기 올리기",
    "option4": "굵기 올리기 → 크기 키우기 → 색 변경",
    "answerIndex": 3,
    "explanation": "섹션 5의 강조 우선순위는 1순위 굵기, 2순위 크기, 3순위 색입니다. 굵기는 색 조화를 깨지 않으면서 확실하게 눈에 띄고, 색은 남용하면 뭐가 중요한지 모르는 화면이 되기 때문이에요."
  },
  {
    "courseSlug": "typography",
    "question": "웹 서비스 UI 본문에 거의 다 산세리프를 쓰는 이유로 코스가 설명한 것은?",
    "option1": "화면 픽셀에서는 가는 삐침이 뭉개져 오히려 지저분해 보이기 때문",
    "option2": "세리프 폰트는 대부분 유료 라이선스라 비용이 많이 들기 때문",
    "option3": "산세리프는 어떤 폰트든 파일 용량이 세리프보다 작기 때문",
    "option4": "세리프 폰트는 굵기 단계를 여러 개 제공하지 않기 때문",
    "answerIndex": 0,
    "explanation": "픽셀로 그리는 화면에서는 세리프의 가는 삐침이 뭉개지기 쉬워 산세리프가 훨씬 또렷합니다. 세리프는 종이에 인쇄된 긴 글에서 시선을 자연스럽게 이어 주는 데 유리하다는 것이 섹션 1의 설명이에요."
  },
  {
    "courseSlug": "typography",
    "question": "수습인 내가 '무료 다운로드'라고 적힌 예쁜 폰트를 발견해 회사 학습 플랫폼에 추가하고 싶다. 코스 기준으로 가장 올바른 행동은?",
    "option1": "무료로 배포되는 폰트이므로 바로 프로젝트에 추가한다",
    "option2": "출처와 폰트 이름을 화면 하단에 표기하고 사용한다",
    "option3": "라이선스 원문에서 상업 이용·웹 임베딩 허용을 확인하고, 애매하면 멘토와 상의한다",
    "option4": "회사 PC에 이미 설치된 폰트인지 확인되면 그대로 사용한다",
    "answerIndex": 2,
    "explanation": "섹션 7에서 '무료 다운로드 가능'과 '무료 사용 가능'은 다른 말이라고 강조합니다. 개인 사용만 무료인 폰트를 회사 서비스에 쓰면 라이선스 위반이므로, 라이선스 원문을 확인하고 근거를 가져와 멘토와 상의하는 것이 기준이에요."
  },
  {
    "courseSlug": "uiux",
    "question": "코스의 케첩병 비유에서 '반짝반짝 고급스럽지만 탁탁 쳐도 케첩이 안 나오는 유리병'은 어떤 제품을 보여주는 예인가?",
    "option1": "UI는 나쁘지만 UX는 좋은 제품",
    "option2": "UI와 UX가 모두 좋은 제품",
    "option3": "UI는 좋지만 UX는 나쁜 제품",
    "option4": "UI와 UX가 모두 나쁜 제품",
    "answerIndex": 2,
    "explanation": "유리병은 식탁 위에서 예쁘지만(UI 승) 케첩이 안 나와 바닥을 쳐야 하므로 겪는 경험은 나쁩니다(UX 패). 섹션 1의 'UI는 보이는 것, UX는 겪는 것' 개념입니다."
  },
  {
    "courseSlug": "uiux",
    "question": "사용자의 눈이 화면을 F패턴으로 스캔한다는 사실에서 코스가 내리는 결론은 무엇인가?",
    "option1": "중요한 것일수록 위쪽·왼쪽에 크게 둬야 한다",
    "option2": "중요한 것일수록 아래쪽·오른쪽에 작게 둬야 한다",
    "option3": "모든 내용을 같은 크기로 배치해 공평하게 읽히게 해야 한다",
    "option4": "내용을 화면 가운데에만 모아 눈길을 고정해야 한다",
    "answerIndex": 0,
    "explanation": "F패턴에서 눈은 맨 윗줄을 끝까지 읽고 점점 짧게 훑다가 왼쪽 가장자리만 내려가며 스캔합니다. 그래서 섹션 2는 중요한 것일수록 '위쪽·왼쪽·크게' 두라고 결론짓습니다."
  },
  {
    "courseSlug": "uiux",
    "question": "과제 제출 버튼 코드에서 submitting이 true인 동안 버튼을 disabled로 잠그는 가장 중요한 이유는?",
    "option1": "서버 응답 속도를 더 빠르게 만들기 위해",
    "option2": "연타로 과제가 두 번 제출되는 사고를 막기 위해",
    "option3": "버튼 색을 파랑에서 회색으로 자연스럽게 바꾸기 위해",
    "option4": "제출 중에 다른 페이지로 이동하지 못하게 막기 위해",
    "answerIndex": 1,
    "explanation": "섹션 4의 제출 버튼 해부에 따르면 disabled 잠금은 연타로 인한 중복 제출을 막는 '실수 방지'이고, '제출 중...' 글자 변경은 '피드백'입니다. 버튼 하나에 UX 두 가지가 들어 있습니다."
  },
  {
    "courseSlug": "uiux",
    "question": "코스에서 '실패' 표시를 빨간색 하나에만 의존하면 안 되고 글자나 아이콘을 함께 줘야 한다고 하는 이유는?",
    "option1": "빨간색은 행동(눌러도 됨)을 나타내는 전용 색이라서",
    "option2": "빨간색은 어떤 배경에서도 명도 대비가 항상 낮아서",
    "option3": "화면 밝기를 낮추면 빨간색이 아예 보이지 않게 되어서",
    "option4": "색약인 사용자는 빨강과 초록을 구분하기 어려울 수 있어서",
    "answerIndex": 3,
    "explanation": "섹션 5의 접근성 개념입니다. 색약 사용자를 위해 색 하나에만 의존하지 말고 '실패'라는 글자나 아이콘을 함께 줘야 하며, 신호등이 색과 위치로 두 번 알려 주는 것과 같은 원리입니다."
  },
  {
    "courseSlug": "uiux",
    "question": "수습생 A가 로그인 화면 비평에 \"왠지 답답해요\"라고 썼다. 이 코스의 화면 비평 규칙에 맞게 고친 문장은?",
    "option1": "\"누가 이렇게 만들었는지 모르겠지만 전체적으로 별로예요\"",
    "option2": "\"그냥 첫인상이 마음에 안 들어서 다시 만들면 좋겠어요\"",
    "option3": "\"여백이 부족해서 라벨과 입력칸의 그룹핑이 안 보여요(근접성)\"",
    "option4": "\"색 조합이 제 취향이 아니라서 더 화려하게 바꾸고 싶어요\"",
    "answerIndex": 2,
    "explanation": "섹션 8의 핵심 규칙은 '느낌이 아니라 용어로' 말하고 근거(어느 섹션의 어떤 원칙인지)를 붙이는 것입니다. 답답함의 원인을 섹션 6의 여백·근접성 개념으로 설명한 문장만 근거 있는 비평입니다."
  },
  {
    "courseSlug": "user-research",
    "question": "인터뷰에서 \"이 기능 편리하죠?\" 같은 질문이 나쁜 질문인 가장 큰 이유는 무엇인가요?",
    "option1": "질문이 너무 길어서 응답자가 지루해지기 때문",
    "option2": "과거가 아니라 현재의 습관을 물어보기 때문",
    "option3": "듣고 싶은 답을 질문에 심어 '예의상 네'라는 답만 얻게 되기 때문",
    "option4": "응답자가 다음 질문에 답하기를 거부하게 되기 때문",
    "answerIndex": 2,
    "explanation": "코스 섹션 2의 '유도 질문'에 해당합니다. 내가 듣고 싶은 답을 질문에 심으면 상대는 면전에서 거절하기 어려워 \"네...\"라고 답하게 되고, 이는 데이터가 아니라 '예의'를 수집한 것이라고 설명합니다."
  },
  {
    "courseSlug": "user-research",
    "question": "이 코스에서 말하는 '페르소나'의 올바른 정의는 무엇인가요?",
    "option1": "아직 만나 보지 못한 이상적인 사용자를 상상해서 만든 가상 인물",
    "option2": "리서치에서 만난 여러 실제 사용자의 공통점을 한 명의 대표 인물로 뭉친 카드",
    "option3": "플랫폼을 가장 오래 사용한 실제 사용자 한 명의 상세 프로필",
    "option4": "설문 응답자 전체의 평균 나이와 취향을 정리한 통계표",
    "answerIndex": 1,
    "explanation": "섹션 4에서 페르소나는 리서치 데이터의 공통점을 한 명의 '대표 인물' 카드로 뭉친 것이라고 정의합니다. 상상으로 지어내면 소설이 되고, 리서치 데이터로 만들면 나침반이 된다고 설명하죠."
  },
  {
    "courseSlug": "user-research",
    "question": "인터뷰만으로는 부족해서 '관찰'이 꼭 필요한 이유로 코스가 제시하는 것은 무엇인가요?",
    "option1": "사람은 거짓말을 하려는 게 아니라 자기 행동을 정확히 기억하지 못하기 때문",
    "option2": "인터뷰 답변은 대부분 의도적으로 꾸며낸 거짓말이기 때문",
    "option3": "관찰이 인터뷰보다 시간이 덜 들고 준비가 간단하기 때문",
    "option4": "사용자는 말로 설명하는 것보다 글로 쓰는 것을 더 잘하기 때문",
    "answerIndex": 0,
    "explanation": "섹션 3 '관찰의 힘'의 핵심입니다. 사람은 자기가 '그랬으면 하는 모습'으로 자신을 기억하기 때문에 말(인터뷰)과 행동(관찰)이 어긋나며, 그래서 리서치는 묻기와 지켜보기 두 발로 서야 한다고 설명합니다."
  },
  {
    "courseSlug": "user-research",
    "question": "설문과 인터뷰·관찰의 역할 분담에 대한 설명으로 코스 내용과 일치하는 것은 무엇인가요?",
    "option1": "설문은 '왜'를 재고, 인터뷰는 '무엇'과 '얼마나'를 잰다",
    "option2": "설문과 인터뷰 모두 '왜'를 알아내는 데 가장 뛰어난 도구다",
    "option3": "인터뷰는 숫자를 모으는 도구이고, 설문은 이야기를 모으는 도구다",
    "option4": "설문은 '무엇'과 '얼마나'를 재는 도구이고, '왜'는 인터뷰와 관찰이 잘한다",
    "answerIndex": 3,
    "explanation": "섹션 6 '설문의 함정'의 정리 문장 그대로입니다. 설문은 what과 how many를 재는 도구이고 why는 인터뷰와 관찰이 잘하므로, 설문에서 이상한 숫자를 발견하면 그것이 인터뷰 주제가 된다고 설명합니다."
  },
  {
    "courseSlug": "user-research",
    "question": "동료 수습생에게 \"학습 센터에서 네트워크 코스를 찾아 DNS 섹션으로 이동해 보세요\"라는 과제를 주고 5분 사용성 테스트를 진행 중입니다. 참가자가 메뉴를 헤매다 \"이거 맞아요?\"라고 물었을 때, 코스에서 배운 진행자의 가장 알맞은 대응은 무엇인가요?",
    "option1": "\"위 메뉴에서 학습 센터를 누르면 돼요\"라고 방법을 바로 알려준다",
    "option2": "\"평소라면 어떻게 하실 것 같아요?\"라고 되묻고 계속 지켜본다",
    "option3": "참가자가 헤매지 않도록 과제를 더 쉬운 것으로 바꿔서 다시 시작한다",
    "option4": "테스트를 그 자리에서 멈추고 왜 헤맸는지 인터뷰로 전환한다",
    "answerIndex": 1,
    "explanation": "섹션 5의 '마법의 주문'입니다. 실제 사용자 옆에는 도와줄 사람이 없으므로 도와주는 순간 테스트는 끝나며, 답을 알려주지 않으면서 상대를 존중하는 되묻기가 진행자의 필살기라고 설명합니다."
  },
  {
    "courseSlug": "vpn-proxy",
    "question": "이 코스에서 프록시(Proxy)를 한마디로 비유한 표현은 무엇인가?",
    "option1": "내용물을 아무도 못 읽게 잠그는 밀봉된 강철 파이프",
    "option2": "나 대신 요청과 응답을 전달해 주는 대리인(연예인의 매니저)",
    "option3": "자주 쓰는 데이터를 오래 보관해 주는 창고 관리인",
    "option4": "가장 빠른 경로를 골라 안내해 주는 내비게이션",
    "answerIndex": 1,
    "explanation": "코스 3섹션에서 프록시는 영어로 '대리인'이라는 뜻이며, 모든 연락이 매니저를 거치는 연예인 비유로 설명된다. 밀봉된 강철 파이프는 프록시가 아니라 VPN 터널의 비유다."
  },
  {
    "courseSlug": "vpn-proxy",
    "question": "VPN을 켜면 '내 위치가 바뀐 것처럼' 보이는 이유로 코스가 설명한 것은?",
    "option1": "터널이 데이터 전송 경로의 속도를 바꿔 놓기 때문",
    "option2": "브라우저가 위치 정보 제공을 자동으로 꺼 버리기 때문",
    "option3": "목적지 서버가 터널의 출구인 VPN 서버의 IP만 보기 때문",
    "option4": "DNS 서버가 내 주소를 주기적으로 새로 발급해 주기 때문",
    "answerIndex": 2,
    "explanation": "1섹션의 '출구 변경' 개념이다. 목적지 입장에서는 VPN 서버가 보낸 것처럼 보이고, 내 IP는 터널 입구 뒤에 숨어 있기 때문이다."
  },
  {
    "courseSlug": "vpn-proxy",
    "question": "정방향(Forward) 프록시와 리버스(Reverse) 프록시의 차이를 가장 잘 설명한 것은?",
    "option1": "정방향은 요청을 보내는 쪽(클라이언트)을, 리버스는 요청을 받는 쪽(서버)을 대신한다",
    "option2": "정방향은 통신을 암호화해 주지만, 리버스는 암호화를 전혀 하지 않는다",
    "option3": "정방향은 응답만 전달하고, 리버스는 요청만 전달하는 구조다",
    "option4": "정방향은 캐시 기능이 있지만, 리버스는 캐시를 사용할 수 없다",
    "answerIndex": 0,
    "explanation": "4섹션의 한 줄 정리 그대로다. 같은 '대리인'이지만 누구 편에 서 있느냐가 달라서, 정방향은 클라이언트가 숨겨지고 리버스는 서버 내부가 숨겨진다."
  },
  {
    "courseSlug": "vpn-proxy",
    "question": "공용 와이파이에서 HTTPS를 쓰더라도 여전히 새어 나갈 수 있는 정보로 코스가 꼽은 것은?",
    "option1": "로그인 화면에서 입력한 비밀번호의 실제 내용",
    "option2": "웹 페이지 본문 안에 들어 있는 개인 정보",
    "option3": "결제 과정에서 오가는 신용카드 번호",
    "option4": "어느 사이트에 접속하는지 알려 주는 도메인·DNS 질문 내용",
    "answerIndex": 3,
    "explanation": "5섹션에서 HTTPS 덕분에 '내용'은 암호화되지만, 어느 사이트에 가는지 같은 '행적'(도메인, DNS 질문)은 여전히 보일 수 있다고 설명한다. VPN을 켜면 행선지까지 터널 안에 숨는다."
  },
  {
    "courseSlug": "vpn-proxy",
    "question": "수습사원 A가 학습 플랫폼에서 F12로 Network 탭을 열어 응답 헤더의 'Server: Caddy'를 확인했고, Spring Boot 백엔드의 IP나 포트는 어디에서도 찾지 못했다. 이 관찰이 보여 주는 것은?",
    "option1": "백엔드 서버가 꺼져 있어서 Caddy가 임시로 대신 응답한 것이다",
    "option2": "브라우저가 캐시된 페이지를 보여 줘서 서버 정보가 생략된 것이다",
    "option3": "리버스 프록시인 Caddy가 백엔드 등 서버 내부를 바깥에서 숨기고 있는 것이다",
    "option4": "HTTPS 암호화 때문에 헤더 정보가 일부만 표시된 것이다",
    "answerIndex": 2,
    "explanation": "7섹션 실습 A의 핵심이다. 브라우저는 끝까지 Caddy하고만 대화하고 백엔드와 DB는 무대 뒤에 있으므로, 백엔드 주소가 안 보이는 것이 리버스 프록시가 서버 내부를 숨긴다는 증거다."
  },
  {
    "courseSlug": "wifi-security",
    "question": "코스에서 와이파이 전파를 '벽 없는 복도에서 말하기'에 비유한 이유로 가장 알맞은 것은?",
    "option1": "전파는 벽을 전혀 통과하지 못해 복도에서만 쓸 수 있기 때문에",
    "option2": "전파는 유선보다 속도가 느려서 데이터가 복도를 걷듯 천천히 가기 때문에",
    "option3": "전파는 범위 안의 누구나 물리적으로 들을 수 있어서 암호화가 필수이기 때문에",
    "option4": "전파는 공유기 한 대당 한 명만 연결할 수 있어 순서를 기다려야 하기 때문에",
    "answerIndex": 2,
    "explanation": "섹션 1의 핵심 비유로, 랜선은 꽂은 사람만 신호를 받지만 전파는 범위 안의 모두에게 퍼진다. 그래서 무선에서는 내용을 암호문으로 바꾸는 암호화(WPA)가 태생적으로 필수라는 결론으로 이어진다."
  },
  {
    "courseSlug": "wifi-security",
    "question": "문자로 받은 링크의 주소가 'edu.awesomedevapp.com.evil.ru'일 때, 이 도메인의 진짜 주인은 누구인가?",
    "option1": "맨 앞의 edu가 주인이므로 교육기관 공식 사이트다",
    "option2": "맨 뒤의 evil.ru가 진짜 주인이므로 가짜 사이트다",
    "option3": "가운데의 awesomedevapp.com이 주인이므로 회사 공식 사이트다",
    "option4": "도메인만으로는 주인을 알 수 없으므로 접속해서 확인해야 한다",
    "answerIndex": 1,
    "explanation": "섹션 4의 피싱 체크리스트 2번 — 도메인은 뒤에서부터 읽어야 하며, 마지막 두 덩어리가 진짜 주인이다. 앞부분은 공격자가 얼마든지 그럴듯하게 붙일 수 있다."
  },
  {
    "courseSlug": "wifi-security",
    "question": "WPA3가 WPA2보다 비밀번호 추측 공격에 강한 이유는 무엇인가?",
    "option1": "한 번 틀릴 때마다 공유기와 다시 대면해야 해서 무한 대입이 불가능하기 때문에",
    "option2": "비밀번호 길이를 최소 20자 이상으로 강제해서 추측 자체가 어렵기 때문에",
    "option3": "틀린 비밀번호를 입력한 기기를 공유기가 영구적으로 차단하기 때문에",
    "option4": "비밀번호를 아예 쓰지 않고 QR 코드로만 접속을 허용하기 때문에",
    "answerIndex": 0,
    "explanation": "섹션 2의 SAE 설명 — WPA2는 악수 과정을 녹음해 가서 집에서 무한 대입할 수 있지만, WPA3는 한 번 틀릴 때마다 공유기와 다시 대면해야 하므로 무한 대입 공격이 통하지 않는다."
  },
  {
    "courseSlug": "wifi-security",
    "question": "브라우저 주소창의 자물쇠(HTTPS) 아이콘이 보장해 주는 것으로 옳은 것은?",
    "option1": "이 사이트는 피싱이 아닌 믿을 수 있는 착한 사이트라는 보증",
    "option2": "이 사이트가 정부 기관의 안전 심사를 통과했다는 인증",
    "option3": "이 사이트에 입력한 비밀번호는 절대 유출되지 않는다는 약속",
    "option4": "브라우저와 서버 사이의 대화가 암호화되고 있다는 사실",
    "answerIndex": 3,
    "explanation": "섹션 3·4의 핵심 — 자물쇠는 '대화가 암호화됐다'는 뜻일 뿐 '상대가 착하다'는 보증이 아니며, 피싱 사이트도 자물쇠를 달 수 있다. 그래서 암호화 확인과 도메인 확인을 둘 다 해야 안전하다."
  },
  {
    "courseSlug": "wifi-security",
    "question": "수습사원 A가 카페에서 급히 사내 Gitea에 접속해야 한다. 'OO카페 공식 와이파이'라는 이름의 신호가 가장 센 와이파이가 잡혔을 때, 코스 내용에 따른 가장 안전한 행동은?",
    "option1": "이름에 '공식'이 들어 있으므로 진짜 공유기라 믿고 바로 로그인한다",
    "option2": "그 와이파이 대신 내 폰의 LTE/5G 테더링을 쓰거나 VPN을 켠 뒤 접속한다",
    "option3": "신호가 가장 세다는 것은 카페 안에 있는 진짜 공유기라는 증거이므로 연결한다",
    "option4": "주소창에 자물쇠만 떠 있으면 어떤 와이파이든 회사 시스템 접속도 안전하다",
    "answerIndex": 1,
    "explanation": "섹션 3의 중간자 공격 — SSID는 누구나 마음대로 지을 수 있고 공격자는 오히려 신호가 센 가짜 AP를 세우므로, 이름과 신호 세기는 아무 증명이 아니다. 공용 와이파이에서 회사 시스템 접속은 미루고, 급하면 테더링이나 VPN(회사 수칙 A7)을 쓰는 것이 3중 방어 습관이다."
  },
  {
    "courseSlug": "wireframe",
    "question": "코스에서 말하는 와이어프레임이 답해야 하는 질문으로 가장 알맞은 것은?",
    "option1": "\"얼마나 예쁜가\" — 색과 폰트가 잘 어울리는지",
    "option2": "\"얼마나 빠른가\" — 화면이 얼마나 빨리 뜨는지",
    "option3": "\"무엇이 어디에 있는가\" — 위치와 크기가 맞는지",
    "option4": "\"누가 쓰는가\" — 사용자가 어떤 사람인지",
    "answerIndex": 2,
    "explanation": "섹션 1의 한 줄 정의처럼 와이어프레임은 '무엇이 어디에 있는가'에만 답하는 뼈대 그림입니다. '얼마나 예쁜가'는 다음 단계인 디자인 시안의 질문이에요."
  },
  {
    "courseSlug": "wireframe",
    "question": "와이어프레임이 3단계로 진화하는 올바른 순서는?",
    "option1": "회색 박스 → 손그림 → 디자인 시안",
    "option2": "손그림 → 회색 박스 → 디자인 시안",
    "option3": "디자인 시안 → 회색 박스 → 손그림",
    "option4": "손그림 → 디자인 시안 → 회색 박스",
    "answerIndex": 1,
    "explanation": "섹션 3에서 배운 순서는 손그림(Lo-Fi) → 회색 박스(Mid-Fi) → 디자인 시안(Hi-Fi)입니다. 흐릿한 사진의 초점이 점점 맞아 가듯 단계마다 해상도를 올려요."
  },
  {
    "courseSlug": "wireframe",
    "question": "코스에서 와이어프레임을 '일부러 대충' 그리라고 하는 가장 큰 이유는?",
    "option1": "버리는 비용을 낮춰 여러 아이디어를 싸고 빠르게 비교하기 위해",
    "option2": "그림 실력이 없는 사람도 부담 없이 참여하게 하기 위해",
    "option3": "개발자가 코드를 더 빨리 짤 수 있도록 시간을 아끼기 위해",
    "option4": "디자인 도구 사용법을 몰라도 회의를 진행하게 하기 위해",
    "answerIndex": 0,
    "explanation": "섹션 2의 핵심은 '빨리 버리기'입니다. 손그림 1장은 10분이라 버려도 안 아깝지만, 공들인 시안은 매몰 비용의 함정 때문에 갈아엎자는 말을 꺼내기 어려워요."
  },
  {
    "courseSlug": "wireframe",
    "question": "플로우를 그릴 때 '해피 패스의 함정'을 피하려면 무엇까지 그려야 한다고 했나요?",
    "option1": "모든 화면의 색상과 폰트 조합까지",
    "option2": "화면마다 사용할 사진과 아이콘까지",
    "option3": "각 화면을 만들 개발 일정표까지",
    "option4": "에러·빈 화면·로딩 같은 예외 화면까지",
    "answerIndex": 3,
    "explanation": "섹션 5에서 모든 게 잘 풀리는 해피 패스만 그리지 말라고 경고합니다. 실제 사용자는 오타를 내고 검색 결과 0개를 만나므로, 에러·빈 화면·로딩까지 그려야 진짜 설계예요."
  },
  {
    "courseSlug": "wireframe",
    "question": "7주차 종합 프로젝트에서 아이디어 회의가 끝나자마자 팀원 A가 React 컴포넌트부터 만들기 시작했습니다. 코스의 가르침에 따르면 팀이 밟아야 할 올바른 순서는?",
    "option1": "피그마 시안 → 손그림 → 플로우 → React 코드",
    "option2": "플로우 → 피그마 회색 박스 → 손그림 → React 코드",
    "option3": "손그림 → 플로우 → 피그마 회색 박스 → React 코드",
    "option4": "피그마 회색 박스 → React 코드 → 손그림 → 플로우",
    "answerIndex": 2,
    "explanation": "섹션 8의 순서대로 손그림으로 후보를 쏟아내고, 플로우로 구멍을 찾고, 피그마 회색 박스로 배치를 확정한 다음에야 React로 옮깁니다. '그리기는 분 단위, 코딩은 일 단위'니까 싼 것부터예요."
  },
  {
    "courseSlug": "app-landscape",
    "question": "네이티브 앱을 만들 때 안드로이드와 iOS의 대표 언어를 옳게 짝지은 것은?",
    "option1": "안드로이드 = Kotlin, iOS = Swift",
    "option2": "안드로이드 = Swift, iOS = Kotlin",
    "option3": "안드로이드 = Dart, iOS = JavaScript",
    "option4": "안드로이드 = HTML, iOS = Java",
    "answerIndex": 0,
    "explanation": "3번 섹션 — 네이티브는 안드로이드가 Kotlin, iOS가 Swift가 대표 언어다."
  },
  {
    "courseSlug": "app-landscape",
    "question": "본문이 든, React Native가 웹 React 개발자에게 특히 익숙한 이유는?",
    "option1": "언어가 Dart로 완전히 바뀌기 때문",
    "option2": "useState·props·JSX 같은 사고방식은 그대로이고 주로 태그 이름(<div>→<View>)만 바뀌기 때문",
    "option3": "브라우저 안에서 그대로 실행되기 때문",
    "option4": "앱스토어 등록이 전혀 필요 없기 때문",
    "answerIndex": 1,
    "explanation": "4번 섹션 — 컴포넌트·props·state·JSX는 그대로 쓰이고 <div>→<View>, <p>→<Text>처럼 태그 이름 위주로만 달라진다."
  },
  {
    "courseSlug": "app-landscape",
    "question": "PWA(Progressive Web App)에 대한 설명으로 옳은 것은?",
    "option1": "Kotlin으로 작성하는 네이티브 안드로이드 앱이다",
    "option2": "코드 한 벌로 안드로이드·iOS의 진짜 네이티브 위젯을 그린다",
    "option3": "manifest.json과 service worker로 설치·오프라인이 되지만 앱스토어에는 정식 등록되지 않는다",
    "option4": "맥(Xcode)에서만 빌드할 수 있다",
    "answerIndex": 2,
    "explanation": "5번 섹션 — PWA는 manifest.json과 service worker로 설치·오프라인을 지원하되 앱스토어 정식 등록은 되지 않는다."
  },
  {
    "courseSlug": "app-landscape",
    "question": "본문의 선택 기준표에서 '안드로이드·iOS 둘 다 필요하고 인력·시간은 빠듯하며 팀이 React/JS에 능숙'할 때 유력한 길은?",
    "option1": "네이티브",
    "option2": "크로스플랫폼(React Native)",
    "option3": "iOS 전용 Swift 개발",
    "option4": "순수 정적 HTML 사이트",
    "answerIndex": 1,
    "explanation": "6번 섹션 처방전 — 두 플랫폼 + 빠듯한 자원 + React 능숙이면 크로스플랫폼(React Native)이 유력하다."
  },
  {
    "courseSlug": "app-landscape",
    "question": "우리 회사 관점에서 앱으로 확장할 때 본문이 강조한 핵심 사실은?",
    "option1": "앱으로 가려면 Spring 백엔드를 처음부터 다시 만들어야 한다",
    "option2": "어느 길을 가든 백엔드(Spring API)는 그대로 재사용되고, 앱은 같은 서버에 요청하는 또 하나의 화면이다",
    "option3": "앱은 HTTP가 아니라 완전히 다른 통신 방식을 쓴다",
    "option4": "네이티브로만 우리 서버에 접속할 수 있다",
    "answerIndex": 1,
    "explanation": "7번 섹션 — 앱도 같은 Spring API에 HTTP 요청을 보내는 또 하나의 화면일 뿐이라 백엔드는 그대로 재사용된다."
  },
  {
    "courseSlug": "android-basics",
    "question": "안드로이드에서 'Activity'는 무엇을 가리키나요?",
    "option1": "앱의 배경에서 조용히 도는 작업(예: 음악 재생)",
    "option2": "화면 한 장(로그인 화면, 목록 화면 등)",
    "option3": "앱끼리 데이터를 주고받는 공유 통로",
    "option4": "배터리 부족 같은 시스템 알림을 받는 부품",
    "answerIndex": 1,
    "explanation": "본문 2섹션에서 Activity는 '화면 한 장'으로 정의했고, 배경 작업은 Service, 데이터 공유는 ContentProvider, 알림 수신은 BroadcastReceiver다."
  },
  {
    "courseSlug": "android-basics",
    "question": "한 화면에서 다른 화면으로 이동하며 데이터까지 넘길 때 쓰는 것은?",
    "option1": "Gradle",
    "option2": "Logcat",
    "option3": "Intent",
    "option4": "AndroidManifest",
    "answerIndex": 2,
    "explanation": "2섹션에서 Intent를 '어디로 갈지 + 무엇을 들고 갈지 담은 쪽지'로 설명했고 startActivity로 화면을 전환한다."
  },
  {
    "courseSlug": "android-basics",
    "question": "실행 중인 앱에서 폰을 가로로 회전하면 액티비티에 기본적으로 무슨 일이 일어나나요?",
    "option1": "아무 일도 없고 변수 값이 그대로 유지된다",
    "option2": "onPause만 한 번 호출되고 끝난다",
    "option3": "onDestroy 후 onCreate로 액티비티가 통째로 다시 만들어진다",
    "option4": "앱이 자동으로 강제 종료된다",
    "answerIndex": 2,
    "explanation": "3섹션 경고대로 회전 시 액티비티가 재생성되므로 변수에만 담아 둔 값은 사라진다."
  },
  {
    "courseSlug": "android-basics",
    "question": "Jetpack Compose가 우리 React와 가장 닮은 점은?",
    "option1": "XML 파일에 화면 구조를 미리 적어 둔다",
    "option2": "상태가 바뀌면 화면이 자동으로 다시 그려지는 선언형이다",
    "option3": "findViewById로 요소를 찾아 직접 손으로 바꾼다",
    "option4": "자바스크립트로만 작성해야 한다",
    "answerIndex": 1,
    "explanation": "4섹션에서 Compose는 '화면 = f(상태)' 선언형이라 상태 변경 시 재구성되어 React의 재렌더와 발상이 같다고 설명했다."
  },
  {
    "courseSlug": "android-basics",
    "question": "카메라 같은 '위험 권한'은 안드로이드 6.0(API 23) 이후 어떻게 얻나요?",
    "option1": "AndroidManifest에 선언만 하면 설치 시 자동으로 허용된다",
    "option2": "앱 실행 중에 사용자에게 팝업으로 허락을 요청해야 한다",
    "option3": "구글 심사팀이 검토해 대신 켜 준다",
    "option4": "위험 권한은 코드로는 요청할 수 없다",
    "answerIndex": 1,
    "explanation": "6섹션대로 INTERNET 같은 일반 권한은 설치 시 자동 허용되지만 카메라·위치 등 위험 권한은 실행 중 런타임 요청이 필요하다."
  },
  {
    "courseSlug": "kotlin-intro",
    "question": "Kotlin에서 한 번 값을 담으면 다시 대입할 수 없어, JS의 const와 대응되는 변수 키워드는?",
    "option1": "val",
    "option2": "var",
    "option3": "let",
    "option4": "def",
    "answerIndex": 0,
    "explanation": "val은 value(값 고정)로 재대입이 불가능해 JS의 const와 짝지어진다. var가 재대입 가능한 let 쪽이다."
  },
  {
    "courseSlug": "kotlin-intro",
    "question": "null일 수 있는 값 maybe에 대해 maybe?.length를 실행했는데 maybe가 null이면 결과는?",
    "option1": "앱이 NPE로 크래시된다",
    "option2": "그냥 null이 반환된다(크래시 없음)",
    "option3": "무조건 0이 반환된다",
    "option4": "컴파일 자체가 안 된다",
    "answerIndex": 1,
    "explanation": "?.(안전 호출)은 앞이 null이면 크래시 없이 통째로 null을 반환한다. 크래시가 나는 건 !!다."
  },
  {
    "courseSlug": "kotlin-intro",
    "question": "maybe ?: \"손님\" 처럼 쓰이는 ?:(엘비스) 연산자의 역할은?",
    "option1": "왼쪽이 null이면 오른쪽 기본값으로 대체한다",
    "option2": "무조건 즉시 크래시를 낸다",
    "option3": "두 숫자를 더한다",
    "option4": "타입을 String으로 강제 변환한다",
    "answerIndex": 0,
    "explanation": "엘비스 연산자 ?:는 왼쪽 값이 null일 때 오른쪽 기본값으로 대체해 준다."
  },
  {
    "courseSlug": "kotlin-intro",
    "question": "일반 클래스 앞에 data를 붙여 data class로 만들 때, 본문 기준 '공짜로 따라오는 것'이 아닌 것은?",
    "option1": "equals(값 비교)",
    "option2": "toString(보기 좋은 출력)",
    "option3": "copy(하나만 바꿔 복제)",
    "option4": "서버 API 자동 호출",
    "answerIndex": 3,
    "explanation": "data class는 equals·toString·copy를 자동 생성해 주지만 네트워크 호출 같은 기능은 제공하지 않는다."
  },
  {
    "courseSlug": "kotlin-intro",
    "question": "본문에서 Kotlin의 when이 JS의 switch보다 나은 점으로 든 것은?",
    "option1": "각 갈래마다 break를 반드시 붙여야 한다",
    "option2": "break가 필요 없고, 값을 돌려주는 식(expression)으로도 쓸 수 있다",
    "option3": "숫자만 비교할 수 있다",
    "option4": "else 갈래를 쓸 수 없다",
    "answerIndex": 1,
    "explanation": "when은 break 없이 맞는 갈래 하나만 실행하고, 결과를 바로 변수에 담는 식으로도 쓸 수 있다."
  },
  {
    "courseSlug": "ios-basics",
    "question": "iOS를 '담장 친 정원(walled garden)'이라 부르는 이유로 본문 설명에 가장 맞는 것은?",
    "option1": "애플이 기기·OS·앱 스토어·칩까지 한 회사가 직접 만들어 통제하기 때문",
    "option2": "앱을 무료로만 배포할 수 있기 때문",
    "option3": "안드로이드보다 기기 종류가 훨씬 많기 때문",
    "option4": "개발에 윈도우 PC만 쓸 수 있기 때문",
    "answerIndex": 0,
    "explanation": "애플은 기기·OS·스토어·칩을 수직 통합해 규칙을 통제하므로 '담장 친 정원'이라 부른다."
  },
  {
    "courseSlug": "ios-basics",
    "question": "React의 useState와 가장 비슷한 사고방식을 가진 iOS 기술은?",
    "option1": "UIKit에서 라벨을 직접 찾아 글자를 바꾸는 명령형 코드",
    "option2": "SwiftUI의 @State — 상태만 바꾸면 화면이 자동으로 다시 그려짐",
    "option3": "Info.plist의 권한 이유 문구",
    "option4": "Xcode의 시뮬레이터",
    "answerIndex": 1,
    "explanation": "SwiftUI의 @State는 상태를 바꾸면 body가 재렌더되어 '화면=상태의 함수'인 useState와 판박이다."
  },
  {
    "courseSlug": "ios-basics",
    "question": "본문은 iOS의 '뷰 계층(view hierarchy)'을 무엇과 같은 뼈대라고 설명했는가?",
    "option1": "데이터베이스 테이블",
    "option2": "브라우저의 DOM 트리이자 React의 컴포넌트 트리",
    "option3": "네트워크 패킷의 흐름",
    "option4": "파일 시스템의 접근 권한",
    "answerIndex": 1,
    "explanation": "화면 위 모든 뷰가 부모-자식 트리로 쌓이는 구조는 DOM 트리·컴포넌트 트리와 같은 뼈대라고 설명했다."
  },
  {
    "courseSlug": "ios-basics",
    "question": "iOS 개발 환경에 대한 본문 설명과 일치하는 것은?",
    "option1": "Xcode는 윈도우·맥·리눅스 모두에서 실행된다",
    "option2": "Xcode는 macOS에서만 실행되어 개발용 맥이 사실상 필수다",
    "option3": "시뮬레이터로 카메라 하드웨어까지 완벽히 테스트된다",
    "option4": "App Store 정식 출시는 완전 무료다",
    "answerIndex": 1,
    "explanation": "Xcode가 macOS 전용이라 개발용 맥이 사실상 필수이며, 윈도우만으로는 공식 빌드가 안 된다고 했다."
  },
  {
    "courseSlug": "ios-basics",
    "question": "iOS에서 카메라 권한을 쓰는데 Info.plist에 이유 문구(NSCameraUsageDescription)를 적지 않으면?",
    "option1": "시스템이 자동으로 권한을 허용한다",
    "option2": "심사에서 경고만 받고 정상 동작한다",
    "option3": "앱이 그 자리에서 죽는다(크래시)",
    "option4": "안드로이드에서만 문제가 된다",
    "answerIndex": 2,
    "explanation": "iOS는 이유 문구가 없으면 해당 자원을 쓰는 순간 앱이 그대로 크래시한다고 본문이 강조했다."
  },
  {
    "courseSlug": "swift-intro",
    "question": "Swift에서 한 번 정하면 다시 값을 넣을 수 없는(상수) 키워드는 무엇인가요?",
    "option1": "let",
    "option2": "var",
    "option3": "const",
    "option4": "val",
    "answerIndex": 0,
    "explanation": "let은 상수라 재할당이 불가하고 var가 바뀔 수 있는 변수입니다(const·val은 Swift 키워드가 아님)."
  },
  {
    "courseSlug": "swift-intro",
    "question": "옵셔널 타입 String? 이 뜻하는 것으로 가장 정확한 것은?",
    "option1": "언제나 문자열이 들어 있다",
    "option2": "String 이거나 nil 일 수 있다",
    "option3": "문자열들의 배열이다",
    "option4": "자동으로 숫자로 바뀐다",
    "answerIndex": 1,
    "explanation": "타입 뒤 물음표(?)는 '값이 있거나 nil일 수 있음'을 타입에 표시한 옵셔널입니다."
  },
  {
    "courseSlug": "swift-intro",
    "question": "옵셔널이 nil일 때 대신 쓸 기본값을 지정하는, JS에도 있는 그 연산자는?",
    "option1": "?. (옵셔널 체이닝)",
    "option2": "! (강제 언래핑)",
    "option3": "?? (nil 병합)",
    "option4": "-> (반환 타입)",
    "answerIndex": 2,
    "explanation": "??는 nil 병합 연산자로 값이 없으면 오른쪽 기본값을 씁니다. !는 위험한 강제 언래핑, ?.는 안전한 접근입니다."
  },
  {
    "courseSlug": "swift-intro",
    "question": "struct(값 타입)에서 var s2 = s1 로 복사한 뒤 s2.x를 99로 바꾸면 s1.x는 어떻게 되나요?",
    "option1": "함께 99로 바뀐다",
    "option2": "그대로 1이다(복사되어 서로 남남)",
    "option3": "컴파일 에러가 난다",
    "option4": "nil이 된다",
    "answerIndex": 1,
    "explanation": "struct는 대입할 때 복사본이 생겨 s1과 s2가 독립적이므로 s1.x는 원래 값 그대로입니다(공유되는 class와 반대)."
  },
  {
    "courseSlug": "swift-intro",
    "question": "Swift의 protocol은 TypeScript나 Java의 무엇과 같은 역할을 하나요?",
    "option1": "class",
    "option2": "struct",
    "option3": "interface",
    "option4": "enum",
    "answerIndex": 2,
    "explanation": "프로토콜은 '갖춰야 할 프로퍼티·기능'을 정하는 계약으로, interface와 같은 자리에 대응합니다."
  },
  {
    "courseSlug": "react-native",
    "question": "React Native가 웹뷰(WebView) 방식과 근본적으로 다른 점은 무엇인가요?",
    "option1": "자바스크립트 코드가 iOS·안드로이드의 진짜 네이티브 UI 위젯으로 렌더링된다",
    "option2": "웹사이트를 앱 아이콘으로 감싸 앱 속 브라우저 안에서 보여 준다",
    "option3": "HTML 파일을 그대로 폰 화면에 띄운다",
    "option4": "앱 안에 작은 크롬 브라우저를 넣어 웹페이지를 연다",
    "answerIndex": 0,
    "explanation": "RN은 웹을 포장하는 WebView가 아니라, JS로 쓴 <Text>가 iOS의 UILabel, 안드로이드의 TextView 같은 진짜 네이티브 위젯으로 그려진다(섹션 1)."
  },
  {
    "courseSlug": "react-native",
    "question": "웹의 <div>에 대응하는 React Native 컴포넌트는 무엇인가요?",
    "option1": "<Text>",
    "option2": "<View>",
    "option3": "<Image>",
    "option4": "<Box>",
    "answerIndex": 1,
    "explanation": "대응표(섹션 3)에서 레이아웃 상자인 <div>는 RN의 <View>에 대응한다. <Text>는 <span>/<p> 글자에, <Image>는 <img>에 대응한다."
  },
  {
    "courseSlug": "react-native",
    "question": "React Native에서 화면에 글자를 표시할 때 반드시 지켜야 하는 규칙은?",
    "option1": "모든 글자는 반드시 <Text> 컴포넌트 안에 넣어야 한다",
    "option2": "모든 글자는 <View> 안에 맨몸으로 직접 써야 한다",
    "option3": "글자는 className 속성으로만 표시한다",
    "option4": "글자는 웹처럼 <p> 태그로 감싸야 한다",
    "answerIndex": 0,
    "explanation": "RN에서는 글자를 그리는 부품과 레이아웃 상자가 다르기 때문에 모든 글자는 <Text> 안에 있어야 하며, <View>에 맨몸으로 넣으면 에러가 난다(섹션 3)."
  },
  {
    "courseSlug": "react-native",
    "question": "React Native의 StyleSheet가 웹 CSS와 다른 점으로 옳은 것은?",
    "option1": "CSS처럼 부모의 스타일이 자식에게 그대로 상속(캐스케이드)된다",
    "option2": "크기 값에 반드시 px 단위를 붙여야 한다",
    "option3": "flexDirection의 기본값이 웹(row)과 달리 column이다",
    "option4": "속성 이름을 background-color처럼 하이픈으로 쓴다",
    "answerIndex": 2,
    "explanation": "RN은 flexDirection 기본값이 column이다. 반대로 속성은 camelCase(backgroundColor), 숫자엔 단위 없음, 캐스케이드 없음이라 나머지 보기는 틀리다(섹션 4)."
  },
  {
    "courseSlug": "react-native",
    "question": "Expo Go 앱의 역할로 맞는 것은?",
    "option1": "앱을 애플·구글 스토어에 자동으로 심사·출시해 준다",
    "option2": "자바 코드를 자바스크립트로 번역해 준다",
    "option3": "Spring Boot 서버를 대신 실행해 준다",
    "option4": "터미널에 뜬 QR코드를 스캔하면 방금 만든 앱을 내 진짜 폰에서 바로 실행해 준다",
    "answerIndex": 3,
    "explanation": "Expo Go는 QR코드를 스캔해 개발 중인 앱을 실제 폰에서 즉시 실행해 주는 앱이다. 스토어 출시나 코드 번역, 서버 실행과는 무관하다(섹션 5)."
  },
  {
    "courseSlug": "flutter",
    "question": "Flutter가 React Native와 구별되는 가장 결정적인 차이는 무엇인가?",
    "option1": "OS가 제공하는 순정 네이티브 부품을 빌려다 화면을 배치한다",
    "option2": "자체 렌더링 엔진이 버튼 모양까지 픽셀 단위로 직접 그린다",
    "option3": "자바스크립트로 UI를 작성한다",
    "option4": "서버 없이도 데이터를 저장할 수 있다",
    "answerIndex": 1,
    "explanation": "본문 섹션 1·2의 핵심 — RN은 OS 부품을 빌리지만 Flutter는 자체 엔진(Impeller/Skia)이 화면을 직접 그린다."
  },
  {
    "courseSlug": "flutter",
    "question": "Dart의 널 안전성(null safety)에 대한 설명으로 옳은 것은?",
    "option1": "모든 변수가 기본적으로 null이 될 수 있다",
    "option2": "타입에 ?를 붙인 값만 null이 될 수 있다",
    "option3": "final로 선언한 값만 null이 될 수 있다",
    "option4": "Dart에는 null이라는 개념이 아예 없다",
    "answerIndex": 1,
    "explanation": "섹션 3에서 설명하듯 ?를 붙인 타입만 null 허용, 나머지는 컴파일러가 null이 올 수 없음을 보증한다."
  },
  {
    "courseSlug": "flutter",
    "question": "Flutter에서 '모든 게 위젯'이라는 말이 뜻하는 것으로 가장 알맞은 것은?",
    "option1": "버튼·글자뿐 아니라 여백·정렬 같은 레이아웃까지 전부 위젯이다",
    "option2": "위젯은 오직 버튼만 가리키는 용어다",
    "option3": "위젯은 HTML 태그를 부르는 다른 이름일 뿐이다",
    "option4": "위젯은 서버에서만 만들 수 있다",
    "answerIndex": 0,
    "explanation": "섹션 4의 핵심 — Flutter는 버튼·글자는 물론 SizedBox 같은 여백·레이아웃까지 모두 위젯으로 다룬다."
  },
  {
    "courseSlug": "flutter",
    "question": "StatefulWidget에서 count 값을 바꿨는데 화면이 다시 그려지게 하려면?",
    "option1": "count 값을 직접 바꾸기만 하면 자동으로 갱신된다",
    "option2": "변경을 setState()로 감싸야 build()가 다시 불린다",
    "option3": "앱을 껐다 켜야 반영된다",
    "option4": "StatelessWidget으로 바꿔야 한다",
    "answerIndex": 1,
    "explanation": "섹션 5 — React의 setCount처럼, 상태 변경을 setState()로 감싸야 Flutter가 build()를 다시 호출한다."
  },
  {
    "courseSlug": "flutter",
    "question": "우리 상황에서 앱을 React Native 대신 Flutter로 만든다면 백엔드는 어떻게 되나?",
    "option1": "Dart로 서버를 새로 작성해야 한다",
    "option2": "기존 Spring API를 그대로 쓰고, 화면을 그리는 도구만 바뀐다",
    "option3": "서버도 Flutter로 다시 만들어야 한다",
    "option4": "서버가 아예 필요 없어진다",
    "answerIndex": 1,
    "explanation": "섹션 7 — 앱 도구 선택은 '화면을 무엇으로 그릴까'의 문제일 뿐, 데이터는 여전히 우리 Spring API에 요청한다."
  },
  {
    "courseSlug": "app-navigation",
    "question": "앱 내비게이션에서 '지금 화면에 보이는 것'은 무엇으로 결정될까요?",
    "option1": "스택(카드 더미)에 쌓인 것 중 맨 위 한 장",
    "option2": "브라우저 주소창(URL)에 적힌 경로",
    "option3": "드로어에 숨겨진 첫 번째 메뉴 항목",
    "option4": "데이터베이스에 저장된 마지막 로그",
    "answerIndex": 0,
    "explanation": "앱엔 주소창이 없고, 화면을 push로 쌓는 스택의 맨 위 카드가 곧 지금 화면이라고 본문에서 설명했다."
  },
  {
    "courseSlug": "app-navigation",
    "question": "'바깥은 탭, 각 탭 안은 스택'인 앱에서 검색 탭으로 상세에 들어갔다가 홈 탭에 갔다 다시 검색 탭으로 오면?",
    "option1": "항상 앱이 종료된다",
    "option2": "아까 보던 상세 화면이 그대로 유지된다",
    "option3": "검색 탭이 홈 화면으로 바뀐다",
    "option4": "모든 탭의 스택이 초기화된다",
    "answerIndex": 1,
    "explanation": "각 탭이 자기 스택을 따로 기억하므로, 탭을 오가도 이전에 보던 상세 화면이 보존된다고 본문에서 설명했다."
  },
  {
    "courseSlug": "app-navigation",
    "question": "iOS에서 이전 화면으로 뒤로 가는 대표적인 방법은?",
    "option1": "화면 왼쪽 가장자리에서 오른쪽으로 쓸어넘기거나 좌상단 '뒤로'를 누른다",
    "option2": "화면 아래 하드웨어 뒤로가기 버튼을 누른다",
    "option3": "볼륨 버튼을 두 번 누른다",
    "option4": "앱을 강제 종료했다가 다시 켠다",
    "answerIndex": 0,
    "explanation": "iOS는 하드웨어 뒤로가기 버튼이 없어 swipe-back 제스처나 좌상단 뒤로 버튼을 쓴다고 본문에서 설명했다."
  },
  {
    "courseSlug": "app-navigation",
    "question": "화면 A에서 B로 데이터를 넘길 때 본문이 권장한 방식은?",
    "option1": "큰 데이터 객체를 통째로 params에 실어 넘긴다",
    "option2": "id 같은 작은 열쇠만 넘기고 상세 데이터는 그 화면이 다시 조회한다",
    "option3": "전역 변수에 넣고 아무 값도 넘기지 않는다",
    "option4": "비밀번호까지 URL에 담아 넘긴다",
    "answerIndex": 1,
    "explanation": "params엔 id 같은 작은 값만 담고 큰 데이터는 도착 화면이 id로 재조회해야 뒤로가기·딥링크 진입에도 깨지지 않는다고 본문에서 강조했다."
  },
  {
    "courseSlug": "app-navigation",
    "question": "React Native에서 사실상 표준으로 쓰이는 내비게이션 라이브러리는?",
    "option1": "React Navigation",
    "option2": "react-router-dom",
    "option3": "go_router",
    "option4": "Redux",
    "answerIndex": 0,
    "explanation": "본문에 따르면 RN은 React Navigation이 표준이며, react-router-dom은 웹, go_router는 Flutter 쪽이다."
  },
  {
    "courseSlug": "app-state-data",
    "question": "다크모드 on/off 설정처럼 '앱을 껐다 켜도 이 기기에 남아야 하지만, 다른 기기와 공유할 필요는 없는' 값은 데이터의 어느 층에 두어야 할까요?",
    "option1": "화면 상태 (useState)",
    "option2": "기기 로컬 저장 (AsyncStorage)",
    "option3": "서버 (Spring + PostgreSQL)",
    "option4": "어느 층에도 저장하지 않는다",
    "answerIndex": 1,
    "explanation": "앱 재시작에도 남아야 하지만(→화면 상태 아님) 다른 기기와 공유할 필요는 없으므로(→서버 아님) 가운데 층인 기기 로컬 저장이 맞다."
  },
  {
    "courseSlug": "app-state-data",
    "question": "검색창에 입력 중인 글자나 지금 열린 탭처럼 '화면을 나가면 사라져도 되는' 값은 앱에서 무엇으로 관리하나요?",
    "option1": "AsyncStorage",
    "option2": "SQLite",
    "option3": "useState",
    "option4": "iOS Keychain",
    "answerIndex": 2,
    "explanation": "화면 상태는 웹 React와 100% 동일하게 useState로 관리하며, 앱이라고 특별한 도구가 필요하지 않다."
  },
  {
    "courseSlug": "app-state-data",
    "question": "로그인 토큰을 AsyncStorage에 그대로 저장하면 안 되는 이유와 올바른 대안은?",
    "option1": "용량이 작아서 — 대신 SQLite에 저장한다",
    "option2": "암호화되지 않은 평문이라 유출 위험 — iOS Keychain / Android Keystore 같은 보안 저장소에 저장한다",
    "option3": "속도가 느려서 — 대신 서버에만 저장한다",
    "option4": "문제없다 — 토큰은 어디에 저장해도 안전하다",
    "answerIndex": 1,
    "explanation": "AsyncStorage와 SQLite는 기본적으로 평문이라, 토큰 같은 민감정보는 OS 보안 저장소(Keychain/Keystore)에 넣어야 한다."
  },
  {
    "courseSlug": "app-state-data",
    "question": "우리 웹 프론트는 쿠키(withCredentials)로 로그인을 유지합니다. 앱(React Native)에서 서버 API를 호출할 때 로그인을 유지하는 흔한 방식은?",
    "option1": "쿠키가 자동으로 붙으므로 아무것도 하지 않아도 된다",
    "option2": "토큰을 Authorization 헤더에 직접 실어 보낸다",
    "option3": "매 요청마다 비밀번호를 body에 담아 보낸다",
    "option4": "서버가 접속 IP로 사용자를 알아본다",
    "answerIndex": 1,
    "explanation": "앱은 브라우저가 아니라 쿠키 자동첨부가 없으므로, 보안 저장소에 둔 토큰을 요청마다 Authorization 헤더에 직접 넣는다."
  },
  {
    "courseSlug": "app-state-data",
    "question": "오프라인에서 '좋아요'를 눌렀을 때, 서버 응답을 기다리지 않고 화면부터 먼저 바꾼 뒤 요청은 대기 큐에 쌓아 재연결 시 보내는 방식을 무엇이라 하나요?",
    "option1": "비관적 UI",
    "option2": "낙관적 UI (optimistic update)",
    "option3": "강제 새로고침",
    "option4": "서버 사이드 렌더링",
    "answerIndex": 1,
    "explanation": "응답을 기다리지 않고 '어차피 성공하겠지' 하고 화면부터 바꾸는 것이 낙관적 UI이며, 실패하면 되돌리고 요청은 큐에 쌓아 재연결 시 동기화한다."
  },
  {
    "courseSlug": "pwa",
    "question": "PWA를 '설치되는 앱'으로 만들어 주는 세 가지 핵심 재료가 아닌 것은?",
    "option1": "HTTPS(안전한 연결)",
    "option2": "웹 앱 매니페스트",
    "option3": "서비스워커",
    "option4": "앱스토어 심사 통과",
    "answerIndex": 3,
    "explanation": "본문에서 재료는 HTTPS·매니페스트·서비스워커 셋이며, PWA는 스토어 심사 없이 배포하는 게 장점이다."
  },
  {
    "courseSlug": "pwa",
    "question": "매니페스트의 display를 \"standalone\"으로 설정하면 어떤 일이 일어나는가?",
    "option1": "주소창·탭 없이 전체화면으로 앱처럼 열린다",
    "option2": "오프라인에서 무조건 동작한다",
    "option3": "푸시 알림이 자동으로 켜진다",
    "option4": "앱스토어에 자동 등록된다",
    "answerIndex": 0,
    "explanation": "섹션 3에서 display: standalone은 주소창을 숨기고 전체화면으로 열어 겉보기엔 네이티브 앱처럼 보이게 한다고 설명했다."
  },
  {
    "courseSlug": "pwa",
    "question": "서비스워커 덕분에 인터넷이 끊겨도 화면이 뜰 수 있는 이유는?",
    "option1": "서버가 항상 켜져 있어서",
    "option2": "요청을 가로채 캐시에 있으면 캐시로 응답해서",
    "option3": "매니페스트가 데이터를 저장해서",
    "option4": "브라우저가 HTML을 통째로 외워서",
    "answerIndex": 1,
    "explanation": "섹션 4에서 서비스워커는 요청을 가로채 캐시에 있으면 캐시(hit)로 응답하므로, 네트워크가 없어도 화면을 그려 낸다."
  },
  {
    "courseSlug": "pwa",
    "question": "웹 푸시 알림에 대한 설명으로 옳은 것은?",
    "option1": "사용자 허락 없이도 서버가 보낼 수 있다",
    "option2": "사용자가 권한을 거절하면 알림을 보낼 방법이 없다",
    "option3": "서비스워커 없이 앱이 꺼진 상태에서 알림을 띄운다",
    "option4": "매니페스트만 있으면 곧바로 발송된다",
    "answerIndex": 1,
    "explanation": "섹션 5에서 requestPermission은 '물어보는' 것이라, 사용자가 거절하면 알림을 보낼 방법이 아예 없다고 강조했다."
  },
  {
    "courseSlug": "pwa",
    "question": "iOS(아이폰)에서 PWA 웹 푸시에 대한 설명으로 옳은 것은?",
    "option1": "사파리 탭으로 열어 두기만 해도 푸시가 온다",
    "option2": "iOS는 웹 푸시를 전혀 지원하지 않는다",
    "option3": "iOS 16.4부터, 홈 화면에 설치된 PWA에서만 푸시가 동작한다",
    "option4": "웹 기능은 iOS가 안드로이드보다 더 많이 열려 있다",
    "answerIndex": 2,
    "explanation": "섹션 6에서 iOS는 16.4(2023)부터 웹 푸시를 지원하되 홈 화면에 설치된 PWA에서만 작동한다고 정확히 다뤘다."
  },
  {
    "courseSlug": "app-security",
    "question": "이 강좌가 말하는, 앱 보안이 웹 보안과 근본적으로 다른 한 가지 이유는?",
    "option1": "앱은 접속할 때마다 서버가 화면 코드를 새로 내려 준다",
    "option2": "앱 파일(APK/IPA)이 사용자·공격자의 기기에 설치돼 남아 뜯어볼 수 있다",
    "option3": "앱은 HTTPS를 사용할 수 없다",
    "option4": "앱은 아무 권한도 필요로 하지 않는다",
    "answerIndex": 1,
    "explanation": "코드가 기기에 설치돼 남아 디컴파일·변조가 가능하므로 '클라이언트를 믿지 마라'가 제1원칙이 된다."
  },
  {
    "courseSlug": "app-security",
    "question": "앱이 카메라·위치 권한을 다룰 때 지켜야 할, 방화벽 코스의 '필요한 포트만 연다'와 같은 원칙은?",
    "option1": "최대 권한 — 미리 다 받아 둔다",
    "option2": "권한은 설치 때 한 번에 몰아서 받는다",
    "option3": "최소 권한 — 기능이 실제로 필요로 하는 것만 요청한다",
    "option4": "권한은 서버가 대신 받아 준다",
    "answerIndex": 2,
    "explanation": "AWS 보안그룹의 '필요한 포트만 열기'와 같은 최소 권한이 사용자 신뢰와 스토어 심사에서 유리하다."
  },
  {
    "courseSlug": "app-security",
    "question": "앱에서 로그인 토큰을 저장하는 안전한 방법은?",
    "option1": "SharedPreferences/UserDefaults에 평문으로 저장한다",
    "option2": "앱 코드에 상수로 하드코딩한다",
    "option3": "iOS Keychain / 안드로이드 Keystore 같은 OS 금고에 저장한다",
    "option4": "디버그 로그 파일에 남겨 둔다",
    "answerIndex": 2,
    "explanation": "하드웨어로 보호되는 Keychain/Keystore에 맡겨야 하며, 평문 저장이나 하드코딩은 그대로 읽혀 금지된다."
  },
  {
    "courseSlug": "app-security",
    "question": "일반 HTTPS를 넘어, 가짜 CA를 이용한 중간자(MITM) 공격까지 막으려고 '우리 서버의 바로 그 인증서'만 믿도록 앱에 지문을 새기는 기법은?",
    "option1": "인증서 고정(Certificate Pinning)",
    "option2": "평문 전송(cleartext)",
    "option3": "포트포워딩",
    "option4": "캐싱",
    "answerIndex": 0,
    "explanation": "인증서 고정은 정해진 인증서만 신뢰해 MITM까지 막지만, 서버 인증서 갱신 시 앱도 함께 업데이트해야 한다."
  },
  {
    "courseSlug": "app-security",
    "question": "\"관리자 메뉴를 앱 화면에서 숨겼으니 안전하다\"는 생각이 위험한 이유로 이 강좌가 강조한 원칙은?",
    "option1": "앱은 절대 뚫리지 않는다",
    "option2": "화면 숨김은 UX일 뿐이고 진짜 권한 검사는 서버가 다시 해야 한다",
    "option3": "스토어 심사가 모든 위협을 막아 준다",
    "option4": "권한을 많이 받을수록 안전하다",
    "answerIndex": 1,
    "explanation": "설치된 앱은 뜯어볼 수 있어 숨긴 화면도 드러나므로, 진짜 검사는 서버가 담당해야 한다(App.jsx 주석과 동일)."
  },
  {
    "courseSlug": "app-store-deploy",
    "question": "배포 전 준비물 중, 앱이 사용자 데이터를 조금이라도 수집한다면 스토어에 반드시 등록해야 하는 것은?",
    "option1": "공개 URL 형태의 개인정보 처리방침",
    "option2": "512×512 앱 아이콘 한 장이면 충분",
    "option3": "유료 개발자 등급 승급",
    "option4": "별도의 서버 TLS 인증서",
    "answerIndex": 0,
    "explanation": "로그인·광고ID·로그 등 데이터를 조금이라도 모으면 공개 URL로 올린 개인정보 처리방침을 스토어에 등록해야 한다."
  },
  {
    "courseSlug": "app-store-deploy",
    "question": "안드로이드 keystore(서명 도장) 파일과 비밀번호를 잃어버리면 어떤 일이 생기나?",
    "option1": "앱 아이콘 색이 초기화된다",
    "option2": "같은 앱을 더 이상 업데이트할 수 없다",
    "option3": "심사가 자동으로 빨라진다",
    "option4": "앱이 무료로 전환된다",
    "answerIndex": 1,
    "explanation": "업데이트는 '같은 도장'으로 서명해야 기존 앱을 덮어쓸 수 있어, keystore를 잃으면 업데이트가 불가능해진다(그래서 Play App Signing으로 대비)."
  },
  {
    "courseSlug": "app-store-deploy",
    "question": "2021년 8월부터 Google Play에 올리는 '새 앱'이 사용해야 하는 업로드 형식은?",
    "option1": ".apk 한 덩어리",
    "option2": "IPA 패키지",
    "option3": "AAB(Android App Bundle)",
    "option4": "실행 가능한 exe",
    "answerIndex": 2,
    "explanation": "새 앱은 AAB로 올리고, 구글이 각 기기에 맞는 최적 APK로 재조립해 배달하므로 다운로드 용량이 줄어든다."
  },
  {
    "courseSlug": "app-store-deploy",
    "question": "App Store 심사가 Google Play보다 대체로 더 엄격하고 오래 걸리는 핵심 이유는?",
    "option1": "안드로이드보다 개발자 비용이 싸서",
    "option2": "리뷰어가 직접 심사하고, 앱 설치가 App Store로만 이뤄지는 닫힌 생태계라서",
    "option3": "서명 과정을 생략하기 때문에",
    "option4": "스크린샷을 요구하지 않기 때문에",
    "answerIndex": 1,
    "explanation": "애플은 사람이 직접 앱을 실행해 심사하며, 설치 경로가 App Store 하나뿐인 닫힌 생태계라 문지기 역할이 강하다."
  },
  {
    "courseSlug": "app-store-deploy",
    "question": "우리 웹 배포(Caddy·Docker)와 앱 스토어 배포의 '되돌리기(롤백)' 차이로 옳은 것은?",
    "option1": "웹은 이전 Docker 이미지로 즉시 롤백되지만, 앱은 대개 고친 새 버전을 다시 올려 심사받아야 한다",
    "option2": "둘 다 몇 초 만에 즉시 롤백된다",
    "option3": "앱이 웹보다 롤백이 더 빠르다",
    "option4": "웹은 원래 롤백이 불가능하다",
    "answerIndex": 0,
    "explanation": "웹은 이전 이미지로 즉시 되돌릴 수 있지만, 스토어는 즉시 롤백이 어려워 새 버전을 다시 올려 심사를 받아야 하므로 출시 전 테스트가 더 중요하다."
  },
  {
    "courseSlug": "lv1-what-is-a-program",
    "question": "이 강좌에서 말하는 컴퓨터의 가장 큰 특징으로 알맞은 것은?",
    "option1": "상황을 눈치껏 판단해 알아서 일을 처리한다",
    "option2": "시킨 것만, 시킨 그대로 빠르고 정확하게 수행한다",
    "option3": "모호한 지시를 받으면 스스로 빈칸을 채워 이해한다",
    "option4": "사람의 말을 한국어 그대로 알아듣는다",
    "answerIndex": 1,
    "explanation": "컴퓨터는 스스로 판단하거나 눈치껏 빈칸을 채우는 능력이 전혀 없고, 시킨 일을 엄청나게 빠르게·오차 없이 그대로 수행하는 기계입니다. 그래서 '정확한 지시'를 만드는 일이 곧 프로그래밍의 본질이 됩니다."
  },
  {
    "courseSlug": "lv1-what-is-a-program",
    "question": "알람 앱, 엘리베이터, 신호등 등 거의 모든 프로그램이 공통으로 갖는 세 박자 패턴은?",
    "option1": "설계 → 디자인 → 판매",
    "option2": "저장 → 삭제 → 복구",
    "option3": "입력 → 처리 → 출력",
    "option4": "질문 → 대답 → 평가",
    "answerIndex": 2,
    "explanation": "프로그램은 무언가를 받아서(입력), 규칙대로 따져 보고(처리), 결과를 내보내는(출력) 구조로 움직입니다. 알람 앱은 맞춘 시각과 현재 시각을 입력받아 비교(처리)한 뒤 소리를 울리는(출력) 식으로, 일상 속 프로그램 대부분이 이 패턴을 따릅니다."
  },
  {
    "courseSlug": "lv1-what-is-a-program",
    "question": "라면 끓이기 레시피 예시에서 배운 '알고리즘'에 대한 설명으로 옳은 것은?",
    "option1": "문제를 해결하는 단계들의 순서 있는 묶음으로, 순서가 바뀌면 결과도 달라진다",
    "option2": "반드시 프로그래밍 언어로 작성해야만 알고리즘이라고 부를 수 있다",
    "option3": "단계 하나쯤 빠져도 컴퓨터가 알아서 채워 주므로 결과는 같다",
    "option4": "요리처럼 손으로 하는 일에는 적용할 수 없는 개념이다",
    "answerIndex": 0,
    "explanation": "알고리즘은 문제를 푸는 단계들의 순서 있는 묶음이며, 같은 단계라도 순서가 바뀌거나 하나만 빠져도 결과가 달라집니다. 또한 알고리즘은 절차 그 자체이므로 한국어나 그림으로 표현해도 되고, 코드는 그 절차를 컴퓨터 언어로 옮긴 결과물일 뿐입니다."
  },
  {
    "courseSlug": "lv1-what-is-a-program",
    "question": "프로그래밍 언어가 필요한 이유로 이 강좌에서 설명한 것은?",
    "option1": "컴퓨터가 한국어보다 영어를 더 잘 알아듣기 때문에",
    "option2": "0과 1을 완전히 없애고 사람의 말로만 컴퓨터를 움직이기 위해서",
    "option3": "사람의 말은 모호하고 컴퓨터는 결국 0과 1만 이해하므로, 그 사이를 잇는 번역기가 필요해서",
    "option4": "프로그래밍 언어를 쓰면 컴퓨터가 스스로 판단하는 능력이 생기기 때문에",
    "answerIndex": 2,
    "explanation": "사람의 말은 '적당히', '넉넉히'처럼 모호함투성이인데 컴퓨터는 모호함을 견디지 못하고, CPU가 실제로 알아듣는 것은 0과 1뿐입니다. 프로그래밍 언어는 사람이 읽고 쓸 수 있으면서도 기계가 0과 1로 정확히 바꿀 수 있는, 사람과 기계 사이의 번역기 역할을 합니다."
  },
  {
    "courseSlug": "lv1-what-is-a-program",
    "question": "코드가 실행되는 방식에 대한 설명으로 옳은 것은?",
    "option1": "컴퓨터가 코드 전체를 읽고 가장 효율적인 순서로 알아서 재배열해 실행한다",
    "option2": "코드는 위에서 아래로 한 줄씩, 쓴 순서 그대로 실행된다",
    "option3": "에러가 나오면 그 프로그램은 다시는 고칠 수 없다",
    "option4": "개발은 한 번 작성하면 끝이며 결과를 확인할 필요가 없다",
    "answerIndex": 1,
    "explanation": "컴퓨터는 라면 레시피를 따르듯 코드를 위에서 아래로 한 줄씩, 쓴 순서 그대로 실행하며 절대 스스로 순서를 바꾸지 않습니다. 그리고 개발은 작성→실행→결과 확인→고치기의 사이클을 반복하는 과정이며, 에러 메시지는 어디를 왜 못 알아들었는지 알려 주는 힌트입니다."
  },
  {
    "courseSlug": "lv1-computational-thinking",
    "question": "컴퓨팅 사고의 네 기둥이 올바르게 짝지어진 것은 무엇인가요?",
    "option1": "분해 · 패턴 인식 · 추상화 · 알고리즘",
    "option2": "암기 · 계산 · 타자 · 검색",
    "option3": "변수 · 함수 · 조건문 · 반복문",
    "option4": "기획 · 디자인 · 개발 · 테스트",
    "answerIndex": 0,
    "explanation": "컴퓨팅 사고는 큰 문제를 잘게 쪼개는 '분해', 반복되는 모양을 찾는 '패턴 인식', 목적에 필요한 정보만 남기는 '추상화', 순서 있는 해결 절차를 세우는 '알고리즘'의 네 기둥으로 이루어집니다. 변수·함수 등은 나중에 배울 프로그래밍 언어의 요소이지, 생각의 기둥 자체가 아니에요."
  },
  {
    "courseSlug": "lv1-computational-thinking",
    "question": "'생일파티 준비'처럼 큰 문제 앞에서 막막함이 사라지게 하는 분해의 핵심 효과는 무엇인가요?",
    "option1": "문제 자체가 쉬워져서 아무나 풀 수 있게 된다",
    "option2": "쪼개진 작은 조각은 '지금 당장 할 수 있는 크기'가 되어 시작점이 생긴다",
    "option3": "컴퓨터가 문제를 대신 풀어 준다",
    "option4": "문제의 개수가 줄어들어 할 일이 적어진다",
    "answerIndex": 1,
    "explanation": "막막함은 문제가 어려워서가 아니라 '커서' 생깁니다. '파티 준비'는 어디서 손대야 할지 몰라도 '오는 사람 수 세기'는 바로 할 수 있죠. 분해는 문제를 없애거나 개수를 줄이는 게 아니라, 각 조각을 풀 수 있는 크기로 만들어 시작점을 주는 기술입니다."
  },
  {
    "courseSlug": "lv1-computational-thinking",
    "question": "지하철 노선도가 강의 모양이나 실제 거리를 '틀리게' 그려 놓았는데도 유용한 이유는 무엇인가요?",
    "option1": "정밀한 지도를 그릴 기술이 없던 시절에 만들어졌기 때문에",
    "option2": "지도가 단순할수록 인쇄 비용이 싸지기 때문에",
    "option3": "'몇 정거장, 어디서 환승?'이라는 목적에 필요한 정보만 남기고 나머지는 버렸기 때문에",
    "option4": "실제 지형 정보는 보안상 공개할 수 없기 때문에",
    "answerIndex": 2,
    "explanation": "노선도는 추상화의 예시입니다. 목적(역의 순서와 환승 위치 파악)에 필요 없는 강 모양·실제 거리·건물 정보를 과감히 버렸기 때문에 오히려 쓸모 있어진 것이죠. 버려서 부정확해진 게 아니라, 버려서 목적에 딱 맞게 단순해진 것 — 단순화가 곧 이해력입니다."
  },
  {
    "courseSlug": "lv1-computational-thinking",
    "question": "순서도에서 '예/아니오로 갈라지는 질문(판단)'을 나타내는 기호는 무엇인가요?",
    "option1": "둥근 모양(타원)",
    "option2": "네모(사각형)",
    "option3": "화살표",
    "option4": "마름모",
    "answerIndex": 3,
    "explanation": "순서도의 기본 기호는 세 가지입니다. 둥근 모양은 시작/끝, 네모는 실제로 하는 일(처리), 마름모는 예/아니오로 갈라지는 판단이에요. 화살표는 기호가 아니라 기호들을 잇는 흐름입니다. '물이 끓는가?' 같은 질문이 바로 마름모에 들어가죠."
  },
  {
    "courseSlug": "lv1-computational-thinking",
    "question": "1부터 10까지 더하는 문제를 '하나씩 9번 더하기'로도, '11이 되는 5쌍을 만들어 11×5'로도 풀 수 있다는 예시가 보여 주는 것은 무엇인가요?",
    "option1": "같은 문제라도 알고리즘은 여러 개일 수 있고, 그중 더 나은 것을 고르는 안목도 실력이다",
    "option2": "수학 공식을 외우면 알고리즘이 필요 없다",
    "option3": "알고리즘은 문제마다 정답이 딱 하나만 존재한다",
    "option4": "느린 알고리즘은 틀린 알고리즘이다",
    "answerIndex": 0,
    "explanation": "두 방법 모두 정답 55에 도착하므로 둘 다 올바른 알고리즘입니다. 즉 같은 문제라도 해결 절차는 여러 개일 수 있어요. 다만 짝지어 더하는 방법이 더 빠르듯, 여러 길 중 더 빠르고 우아한 길을 알아보는 안목도 컴퓨팅 사고의 일부입니다. 느리다고 '틀린' 것은 아니라는 점도 중요해요."
  },
  {
    "courseSlug": "lv1-first-html",
    "question": "웹페이지의 정체에 대한 설명으로 가장 알맞은 것은 무엇인가요?",
    "option1": "게임처럼 컴파일된 복잡한 실행 프로그램이다",
    "option2": "약속된 표시(태그)가 들어 있는 텍스트 파일이며, 브라우저가 이를 읽어 화면에 그려 준다",
    "option3": "인터넷 서버가 있어야만 만들고 열 수 있는 특수 파일이다",
    "option4": "브라우저 안에서만 작성할 수 있는 문서다",
    "answerIndex": 1,
    "explanation": "웹페이지는 특별한 프로그램이 아니라 HTML이라는 약속된 표시가 담긴 글자 문서(텍스트 파일)입니다. 브라우저는 이 문서를 읽어 제목은 크게, 문단은 나눠서 화면에 '그려' 주는 역할을 하죠. 내 컴퓨터에 저장된 .html 파일도 똑같이 열 수 있으므로, 인터넷이나 서버 없이도 웹페이지를 만들 수 있습니다."
  },
  {
    "courseSlug": "lv1-first-html",
    "question": "HTML 문서에서 <head>와 <body>의 역할을 바르게 짝지은 것은 무엇인가요?",
    "option1": "head는 화면에 보이는 내용, body는 문서에 대한 정보를 담는다",
    "option2": "head는 문서에 대한 정보(탭 제목·charset 등)를 담고, body는 화면에 실제로 보이는 내용을 담는다",
    "option3": "head와 body 모두 화면에 보이는 내용을 담으며 순서만 다르다",
    "option4": "head는 최신 HTML 규칙임을 선언하는 부분이고, body는 주석을 모아 두는 부분이다",
    "answerIndex": 1,
    "explanation": "head는 '문서에 대한 정보'가 사는 곳이라 본문 화면에는 보이지 않습니다. 대신 <title>은 브라우저 탭에 표시되고, charset은 글자 읽는 방식을 알려 주죠. 화면에 보이는 글·제목 등 모든 내용은 body 안에 들어갑니다. 참고로 최신 HTML 규칙임을 선언하는 것은 head가 아니라 파일 첫 줄의 <!DOCTYPE html>입니다."
  },
  {
    "courseSlug": "lv1-first-html",
    "question": "태그의 기본 구조에 대한 설명으로 옳은 것은 무엇인가요?",
    "option1": "여는 태그와 닫는 태그가 내용을 감싸며, 닫는 태그에는 슬래시(/)가 붙는다",
    "option2": "모든 태그는 반드시 닫는 태그가 있어야 하며 예외는 없다",
    "option3": "슬래시(/)는 여는 태그에 붙여서 시작을 표시한다",
    "option4": "여는 태그와 닫는 태그 사이에는 아무 내용도 넣을 수 없다",
    "answerIndex": 0,
    "explanation": "태그는 '여는 태그 + 내용 + 닫는 태그'의 3단 샌드위치 구조이고, 닫는 태그에만 슬래시(/)를 붙여 \"여기까지!\"를 표시합니다(예: <p>내용</p>). 다만 <br />, <hr />처럼 감쌀 내용이 없어 닫는 태그 없이 혼자 다니는 예외 태그도 있으므로 2번은 틀린 설명입니다."
  },
  {
    "courseSlug": "lv1-first-html",
    "question": "h1~h6 제목 태그를 쓰는 올바른 태도는 무엇인가요?",
    "option1": "글자를 크게 만들고 싶을 때마다 아무 h 태그나 골라 쓴다",
    "option2": "h6가 가장 중요한 제목이므로 대표 제목에는 h6를 쓴다",
    "option3": "h 태그는 중요도의 위계를 표현하는 도구이므로, 대표 제목 h1 아래 h2, h3 순으로 건너뛰지 않고 쓴다",
    "option4": "한 페이지에 h1을 많이 쓸수록 구조가 좋아진다",
    "answerIndex": 2,
    "explanation": "h 태그는 글자 크기 버튼이 아니라 책의 장(h1)과 절(h2)처럼 '중요도의 순서'를 매기는 도구입니다. h1이 가장 크고 중요한 대표 제목(보통 페이지에 1개), 그 아래 소제목은 h2, 그 안의 묶음은 h3처럼 건너뛰지 않고 순서대로 쓰는 것이 좋은 습관이라고 배웠습니다."
  },
  {
    "courseSlug": "lv1-first-html",
    "question": "만든 페이지를 브라우저로 열었더니 한글이 'ê¹€ë¯¸ë¦¼'처럼 깨져 보입니다. 가장 먼저 확인해야 할 것은 무엇인가요?",
    "option1": "body 안에 <hr /> 구분선이 있는지 확인한다",
    "option2": "head 안에 <meta charset=\"UTF-8\" />이 있는지 확인한다",
    "option3": "닫는 태그의 슬래시를 모두 지웠는지 확인한다",
    "option4": "브라우저를 다른 회사 제품으로 바꿔서 다시 연다",
    "answerIndex": 1,
    "explanation": "charset은 \"이 문서의 글자를 UTF-8 방식으로 읽어 줘\"라고 브라우저에 알리는 안내입니다. 이 한 줄이 없으면 브라우저가 글자 해석 방식을 잘못 추측해 한글이 외계어처럼 깨질 수 있어요. 그래서 첫 파일의 뼈대를 만들 때부터 head 안에 <meta charset=\"UTF-8\" />을 넣어 두는 것입니다."
  },
  {
    "courseSlug": "lv1-html-links-images-lists",
    "question": "링크를 눌렀을 때 '새 탭'에서 열리게 하려면 a 태그에 무엇을 추가해야 할까요?",
    "option1": "href=\"_blank\"",
    "option2": "target=\"_blank\"",
    "option3": "newtab=\"true\"",
    "option4": "alt=\"_blank\"",
    "answerIndex": 1,
    "explanation": "href는 '어디로 갈지' 목적지 주소를 적는 속성이고, '어떻게 열지'를 정하는 건 target 속성입니다. target=\"_blank\"를 달면 지금 페이지를 그대로 둔 채 새 탭에서 링크가 열려요. 다른 사이트로 보낼 때 자주 쓰는 옵션입니다."
  },
  {
    "courseSlug": "lv1-html-links-images-lists",
    "question": "img 태그의 alt 속성에 대한 설명으로 옳은 것은 무엇일까요?",
    "option1": "이미지 파일이 저장된 위치(주소)를 적는 칸이다",
    "option2": "이미지의 크기를 픽셀 단위로 정하는 칸이다",
    "option3": "이미지가 안 보일 때 대신 표시되고, 화면 낭독기 사용자에게 이미지를 설명해 주는 대체 텍스트다",
    "option4": "이미지를 클릭했을 때 이동할 링크 주소를 적는 칸이다",
    "answerIndex": 2,
    "explanation": "이미지 위치(주소)를 적는 속성은 src이고, alt(alternative)는 '대체' 텍스트입니다. 주소가 틀려 이미지가 깨졌을 때 그 자리에 대신 표시되고, 눈이 불편해 화면 낭독기를 쓰는 사람에겐 그 이미지의 내용 전부가 되기 때문에 '창밖을 보는 고양이'처럼 그림을 말로 옮기듯 적어야 해요."
  },
  {
    "courseSlug": "lv1-html-links-images-lists",
    "question": "라면 끓이는 순서(물 끓이기 → 면 넣기 → 3분 기다리기)처럼 순서가 중요한 목록을 만들 때 알맞은 태그는 무엇일까요?",
    "option1": "ul — 항목마다 ● 점이 찍히는 목록",
    "option2": "li — 목록 전체를 감싸는 바구니 태그",
    "option3": "table — 가로세로 칸으로 정리하는 태그",
    "option4": "ol — 항목마다 1. 2. 3. 번호가 붙는 목록",
    "answerIndex": 3,
    "explanation": "ol은 ordered list(순서 있는 목록)의 줄임말로, 항목에 자동으로 번호가 붙습니다. 라면 끓이기처럼 순서를 뒤섞으면 뜻이 통하지 않는 내용은 ol, 좋아하는 음식처럼 순서를 바꿔도 상관없는 내용은 ul을 씁니다. li는 두 목록 안에 들어가는 '항목 하나하나'를 적는 태그이지 목록 전체를 감싸는 태그가 아니에요."
  },
  {
    "courseSlug": "lv1-html-links-images-lists",
    "question": "표를 만드는 태그들의 역할 분담으로 옳은 것은 무엇일까요?",
    "option1": "tr은 가로 한 줄(행)을, td는 줄 안의 내용 칸 하나를 만든다",
    "option2": "tr은 세로 한 줄(열)을, th는 표 전체를 감싸는 틀이다",
    "option3": "td는 가로 한 줄(행)을, tr은 제목 칸을 만든다",
    "option4": "th는 표 전체를 감싸고, table은 칸 하나하나를 만든다",
    "answerIndex": 0,
    "explanation": "표는 table(표 전체 틀) > tr(table row, 가로 한 줄=행) > td(table data, 내용 칸)/th(table header, 굵게 나오는 제목 칸)의 순서로 역할을 나눕니다. 표를 짤 때는 '가로 한 줄 = tr 하나'라는 규칙으로 위에서부터 한 줄씩 코드로 옮기면 되고, 줄마다 칸 개수가 같아야 표가 반듯하게 나와요."
  },
  {
    "courseSlug": "lv1-html-links-images-lists",
    "question": "div 태그를 화면에 띄워 보면 겉보기에 아무 변화가 없습니다. 그런데도 div를 쓰는 이유는 무엇일까요?",
    "option1": "div를 넣으면 페이지 글자가 자동으로 굵어지기 때문에",
    "option2": "관련 있는 내용을 '투명한 상자'처럼 한데 묶어 두면, 나중에 꾸미거나 배치할 때 상자째로 다룰 수 있기 때문에",
    "option3": "div가 없으면 이미지와 링크가 아예 표시되지 않기 때문에",
    "option4": "div가 목록에 자동으로 번호를 붙여 주기 때문에",
    "answerIndex": 1,
    "explanation": "div(division, 구역)는 화면에 무언가를 그리는 태그가 아니라 내용을 '묶는' 태그입니다. 그래서 넣어도 겉보기엔 변화가 없는 게 정상이에요. 하지만 이사할 때 물건을 상자에 담아 두면 옮기기 쉬운 것처럼, 내용을 div로 묶어 구역을 나눠 두면 나중에 CSS로 꾸미고 배치할 때 상자 단위로 한꺼번에 다룰 수 있습니다. 실제 웹사이트들도 수십, 수백 개의 div 블록으로 짜여 있어요."
  },
  {
    "courseSlug": "lv1-how-browsers-work",
    "question": "주소창에 www.naver.com을 치고 엔터를 눌렀을 때 일어나는 일을 가장 잘 설명한 것은?",
    "option1": "브라우저가 내 컴퓨터 안에 저장된 네이버 화면을 꺼내 보여 준다",
    "option2": "브라우저가 서버에 요청(Request)을 보내고, 서버가 보내 준 응답(Response)의 HTML을 화면에 그린다",
    "option3": "서버가 완성된 그림 파일을 보내 주고, 브라우저는 그 그림을 띄우기만 한다",
    "option4": "브라우저가 스스로 HTML을 만들어 내서 화면에 그린다",
    "answerIndex": 1,
    "explanation": "웹은 '요청하고 응답받는' 대화 구조예요. 브라우저가 멀리 있는 서버(남의 컴퓨터)에 요청을 보내면, 서버는 완성된 그림이 아니라 HTML이라는 글자 문서를 응답으로 보내 줍니다. 그 문서를 읽고 우리 눈에 보이는 화면으로 그려 내는 것(렌더링)이 바로 브라우저의 일이에요."
  },
  {
    "courseSlug": "lv1-how-browsers-work",
    "question": "브라우저의 세 가지 언어와 역할을 사람에 비유했을 때, 짝이 올바른 것은?",
    "option1": "HTML = 움직임, CSS = 뼈대, JavaScript = 옷",
    "option2": "HTML = 옷, CSS = 움직임, JavaScript = 뼈대",
    "option3": "HTML = 뼈대, CSS = 옷, JavaScript = 움직임",
    "option4": "HTML = 뼈대, CSS = 움직임, JavaScript = 옷",
    "answerIndex": 2,
    "explanation": "HTML은 '여기가 제목, 저기가 버튼'처럼 구조를 정하는 뼈대, CSS는 '제목은 크게, 버튼은 파랗게'처럼 모양을 입히는 옷, JavaScript는 '버튼을 누르면 이걸 해라'처럼 동작을 붙이는 움직임 담당이에요. 옷은 뼈대가 있어야 입힐 수 있으니 웹 공부가 HTML부터 시작하는 것도 이 순서 때문입니다."
  },
  {
    "courseSlug": "lv1-how-browsers-work",
    "question": "개발자 도구(F12)의 Elements 탭에서 뉴스 사이트의 제목을 바꿨는데, F5로 새로고침하니 원래대로 돌아왔다. 그 이유는?",
    "option1": "내가 고친 것은 서버의 원본이 아니라 내 브라우저에 도착한 복사본이고, 새로고침은 서버에서 원본을 다시 받아 오기 때문이다",
    "option2": "사이트 관리자가 내가 고친 것을 발견하고 즉시 되돌렸기 때문이다",
    "option3": "개발자 도구에서는 애초에 글자를 바꾸는 것이 불가능하기 때문이다",
    "option4": "브라우저가 고장 나서 수정 내용을 저장하지 못했기 때문이다",
    "answerIndex": 0,
    "explanation": "서버는 HTML 문서를 통째로 보내 주고, 내 브라우저는 그 복사본을 화면에 그려요. Elements 탭에서 고치는 건 이 복사본에 낙서하는 것이라 서버의 원본은 멀쩡합니다. 새로고침은 '복사본을 버리고 원본을 다시 받아 오기'라서 수정이 사라지는 거예요. 그래서 개발자 도구로 아무리 만져도 실제 사이트는 망가지지 않습니다."
  },
  {
    "courseSlug": "lv1-how-browsers-work",
    "question": "개발자 도구의 콘솔(Console) 탭에 대한 설명으로 옳은 것은?",
    "option1": "콘솔은 화면의 HTML 구조를 트리 모양으로 보여 주기만 하는 곳이다",
    "option2": "콘솔에 2+3을 입력하면 브라우저가 즉시 실행해 5라고 답하고, 페이지의 에러도 이곳에 빨간 글씨로 표시된다",
    "option3": "콘솔은 인터넷이 연결되어 있어야만 열 수 있다",
    "option4": "콘솔에 에러가 빨간 글씨로 뜨면 브라우저가 고장 났다는 뜻이므로 재설치해야 한다",
    "answerIndex": 1,
    "explanation": "콘솔은 브라우저와 나누는 대화창(채팅창)이에요. 2+3을 치면 브라우저 속 JavaScript 엔진이 그 자리에서 실행해 5라고 대답합니다. 또한 페이지의 JavaScript가 실패하면 '몇 번째 줄에서 왜' 넘어졌는지가 여기 빨간 글씨로 찍혀요. 에러는 고장 신호가 아니라 원인을 알려 주는 힌트 쪽지입니다. HTML 구조를 보여 주는 곳은 Elements 탭이에요."
  },
  {
    "courseSlug": "lv1-how-browsers-work",
    "question": "내가 만든 HTML 파일을 더블클릭해 file://로 열어 놓고 친구에게 링크를 보냈더니 친구는 볼 수 없었다. 친구도 볼 수 있게 하려면 무엇이 필요할까?",
    "option1": "친구에게 더 좋은 브라우저를 설치하라고 한다",
    "option2": "HTML 파일을 CSS와 JavaScript로 다시 작성한다",
    "option3": "내 컴퓨터의 화면 밝기를 높인다",
    "option4": "요청이 오면 응답해 주는 서버에 내 파일을 올린다 — 즉 배포(deploy)가 필요하다",
    "answerIndex": 3,
    "explanation": "file://은 내 컴퓨터 안의 파일을 직접 여는 방식이라, 그 파일이 없는 친구 컴퓨터에서는 열리지 않아요. 남에게 보여 주려면 누가 요청하든 응답해 줄 수 있는 서버에 파일을 올려 https:// 주소로 접근하게 해야 하고, 이 과정을 배포라고 부릅니다. file://과 https://의 차이는 결국 '문서가 어디에서 왔는가'예요."
  },
  {
    "courseSlug": "lv1-first-javascript",
    "question": "웹 페이지를 만드는 세 가지 언어 중, 버튼 클릭에 반응하거나 팝업을 띄우는 등 페이지에 '움직임'을 담당하는 것은 무엇인가요?",
    "option1": "HTML",
    "option2": "CSS",
    "option3": "JavaScript",
    "option4": "Java",
    "answerIndex": 2,
    "explanation": "HTML은 뼈대(글·버튼·사진 배치), CSS는 꾸밈(색·크기·배치)을 맡고, 클릭 반응·팝업·계산 같은 '움직임'은 모두 JavaScript의 일입니다. Java는 이름만 비슷할 뿐 JavaScript와는 완전히 다른 언어예요."
  },
  {
    "courseSlug": "lv1-first-javascript",
    "question": "콘솔에 console.log(안녕!); 을 입력하면 에러가 납니다. 그 이유로 옳은 것은?",
    "option1": "세미콜론(;)을 붙였기 때문이다",
    "option2": "글자(문자열)를 따옴표로 감싸지 않았기 때문이다",
    "option3": "괄호를 소괄호가 아니라 대괄호로 써야 하기 때문이다",
    "option4": "console.log는 콘솔에서는 실행할 수 없기 때문이다",
    "answerIndex": 1,
    "explanation": "글자 덩어리(문자열)는 반드시 따옴표로 감싸야 합니다. 따옴표가 없으면 컴퓨터는 '안녕'을 글자가 아니라 어떤 이름(변수)으로 오해해서 찾다가 에러를 냅니다. 세미콜론은 문장의 마침표 역할이라 붙여도 문제가 없어요."
  },
  {
    "courseSlug": "lv1-first-javascript",
    "question": "<script> 태그를 보통 </body> 바로 앞(몸통 맨 끝)에 두는 이유는 무엇인가요?",
    "option1": "브라우저가 HTML을 위에서 아래로 읽으므로, 화면이 다 그려진 다음에 코드가 실행되게 하려고",
    "option2": "맨 끝에 두어야 코드 실행 속도가 두 배 빨라지기 때문에",
    "option3": "head 안에 두면 JavaScript가 아예 실행되지 않기 때문에",
    "option4": "body 끝에 두어야만 별도 .js 파일로 분리할 수 있기 때문에",
    "answerIndex": 0,
    "explanation": "브라우저는 HTML을 위에서 아래로 읽으며 화면을 그립니다. 스크립트를 body 끝에 두면 화면이 완성된 뒤 코드가 실행되므로, 코드가 '아직 안 그려진 버튼'을 찾다가 실패하는 사고를 막을 수 있어요. 속도가 빨라지거나 head에서 실행이 안 되는 것은 아닙니다."
  },
  {
    "courseSlug": "lv1-first-javascript",
    "question": "콘솔에 \"1\" + 1 을 입력하면 결과는 무엇인가요?",
    "option1": "2",
    "option2": "\"11\"",
    "option3": "에러가 발생한다",
    "option4": "11 (숫자)",
    "answerIndex": 1,
    "explanation": "따옴표로 감싼 \"1\"은 숫자가 아니라 글자(문자열)입니다. + 연산자는 한쪽이라도 글자면 계산 대신 '이어 붙이기'를 선택하므로 결과는 문자열 \"11\"이 됩니다. prompt()로 받은 답도 항상 글자라서 같은 함정에 빠지기 쉬워요."
  },
  {
    "courseSlug": "lv1-first-javascript",
    "question": "코드를 실행했더니 콘솔에 'Console is not defined'라는 빨간 에러가 떴습니다. 가장 알맞은 대처는?",
    "option1": "브라우저를 재설치한다 — JavaScript가 고장 난 것이다",
    "option2": "빨간 글씨는 무시하고 코드를 처음부터 다시 짠다",
    "option3": "에러 메시지를 힌트로 읽는다 — 대문자 C를 소문자 c로 고치면 해결된다",
    "option4": "세미콜론을 지우면 해결된다",
    "answerIndex": 2,
    "explanation": "JavaScript는 대소문자를 구분하므로 Console과 console은 완전히 다른 단어입니다. 'Console is not defined'는 'Console이란 건 모른다'는 뜻의 힌트이므로, 대문자 C를 소문자로 고치면 됩니다. 콘솔의 빨간 글씨는 벌점이 아니라 몇 번째 줄에 무엇이 이상한지 알려 주는 힌트예요."
  },
  {
    "courseSlug": "lv1-variables-and-types",
    "question": "const로 만든 변수에 새 값을 넣으려고 하면(예: const birthYear = 2009; birthYear = 2010;) 어떤 일이 일어날까요?",
    "option1": "아무 일 없이 값이 2010으로 바뀐다",
    "option2": "오류가 발생하며 값 변경이 막힌다",
    "option3": "값이 undefined로 초기화된다",
    "option4": "2009와 2010이 둘 다 저장된다",
    "answerIndex": 1,
    "explanation": "const는 constant(상수)의 줄임말로, 한번 값을 넣으면 바꿀 수 없는 상자예요. 재할당을 시도하면 TypeError가 발생합니다. 바뀌면 안 되는 값을 const로 선언해 두면, 실수로 값을 바꾸는 사고를 컴퓨터가 대신 막아 주기 때문에 중요합니다."
  },
  {
    "courseSlug": "lv1-variables-and-types",
    "question": "console.log(\"1\" + 1)을 실행하면 결과가 무엇이고, 왜 그럴까요?",
    "option1": "2 — 자바스크립트가 문자열을 자동으로 숫자로 바꿔 덧셈하기 때문",
    "option2": "오류 발생 — 문자열과 숫자는 더할 수 없기 때문",
    "option3": "\"11\" — 문자열이 끼어 있으면 +가 이어 붙이기로 동작하기 때문",
    "option4": "11 — 결과가 항상 숫자로 변환되기 때문",
    "answerIndex": 2,
    "explanation": "\"1\"은 따옴표에 싸인 문자열이에요. +는 양쪽 값의 자료형을 보고 행동을 결정하는데, 문자열이 끼어 있으면 덧셈이 아니라 '이어 붙이기(연결)'로 동작합니다. 그래서 숫자 1까지 글자 취급되어 문자열 \"11\"이 되고, 오류도 없이 조용히 이상한 결과가 나오는 것이 이 함정의 무서운 점입니다."
  },
  {
    "courseSlug": "lv1-variables-and-types",
    "question": "10 % 3의 결과는 무엇일까요?",
    "option1": "1",
    "option2": "3",
    "option3": "3.333...",
    "option4": "0.1",
    "answerIndex": 0,
    "explanation": "%는 퍼센트가 아니라 '나머지 연산자'예요. 10을 3으로 나누면 몫이 3이고 나머지가 1이므로(10 = 3×3 + 1), 10 % 3은 1입니다. 어떤 수를 2로 나눈 나머지(% 2)가 0이면 짝수, 1이면 홀수라서 홀짝 판별에 자주 쓰입니다."
  },
  {
    "courseSlug": "lv1-variables-and-types",
    "question": "7 === 7이나 10 > 3 같은 비교 연산의 결과는 어떤 자료형의 값일까요?",
    "option1": "숫자(number) — 1 또는 0이 나온다",
    "option2": "문자열(string) — \"참\" 또는 \"거짓\"이 나온다",
    "option3": "비교식마다 다른 자료형이 나온다",
    "option4": "불리언(boolean) — true 또는 false가 나온다",
    "answerIndex": 3,
    "explanation": "비교 연산자(>, <, === 등)의 답은 반드시 true 아니면 false, 즉 불리언입니다. 불리언은 값이 이 두 개뿐인 자료형으로, 다음 강좌의 조건문(if)이 이 true/false를 보고 갈림길을 고르기 때문에 프로그램의 판단에 쓰이는 핵심 재료가 됩니다."
  },
  {
    "courseSlug": "lv1-variables-and-types",
    "question": "prompt(\"몇 년에 태어났어요?\")로 사용자에게 2009를 입력받아 나이 계산에 쓰려고 합니다. 왜 Number()로 감싸서 담아야 할까요?",
    "option1": "prompt는 입력값을 항상 문자열로 돌려주기 때문에, 계산에 쓰려면 숫자로 형 변환이 필요해서",
    "option2": "Number()를 쓰지 않으면 prompt 입력창이 아예 뜨지 않아서",
    "option3": "prompt가 입력값을 불리언(true/false)으로 돌려주기 때문에",
    "option4": "Number()가 입력값을 자동으로 올해 연도로 바꿔 주기 때문에",
    "answerIndex": 0,
    "explanation": "prompt는 사용자가 무엇을 입력하든 항상 문자열로 돌려줍니다. 2009를 입력해도 숫자 2009가 아니라 문자열 \"2009\"라서, 그대로 계산에 쓰면 형이 달라 이상한 결과가 날 수 있어요. 그래서 Number(\"2009\")처럼 형 변환을 해서 숫자로 만든 뒤 변수에 담아야 뺄셈(나이 계산)이 올바르게 동작합니다."
  },
  {
    "courseSlug": "lv1-conditionals",
    "question": "if 문의 괄호 안 조건이 false일 때, 중괄호 블록 안의 코드는 어떻게 될까요?",
    "option1": "실행되지 않고 건너뛰어지며, 프로그램은 블록 다음 줄부터 계속 진행된다",
    "option2": "코드가 삭제되어 다시는 실행할 수 없다",
    "option3": "에러가 발생하며 프로그램이 멈춘다",
    "option4": "절반만 실행되고 나머지는 건너뛴다",
    "answerIndex": 0,
    "explanation": "조건이 false면 블록은 '건너뛰어질' 뿐 사라지는 게 아닙니다. 프로그램은 블록 다음 줄부터 아무 일 없었다는 듯 계속 실행돼요. 그래서 if 블록 뒤에 있는 코드는 조건과 상관없이 항상 실행됩니다."
  },
  {
    "courseSlug": "lv1-conditionals",
    "question": "let age = 15; 라고 쓸 때의 = 와, age === 15 라고 쓸 때의 === 의 차이로 옳은 것은?",
    "option1": "둘 다 같은 뜻이고 취향 차이일 뿐이다",
    "option2": "= 는 값을 넣는 대입이고, === 는 같은지 물어보는 비교로, 하는 일이 완전히 다르다",
    "option3": "= 는 숫자에만, === 는 글자에만 쓴다",
    "option4": "=== 는 값을 세 번 넣는다는 뜻이다",
    "answerIndex": 1,
    "explanation": "= 하나는 '상자에 값을 넣어라'라는 대입이고, === 세 개는 '두 값이 같은가?'를 판정해 true/false를 돌려주는 비교입니다. if 조건에 실수로 = 를 쓰면 대입이 일어나 조건이 항상 참이 되고 변수 값까지 바뀌는 버그가 생겨요."
  },
  {
    "courseSlug": "lv1-conditionals",
    "question": "놀이기구 탑승 조건이 if (height >= 120 && age >= 8) 일 때, 키 150cm에 나이 7살인 사람의 결과는?",
    "option1": "키 조건이 true이므로 탑승 가능하다",
    "option2": "&& 는 하나만 true여도 되므로 탑승 가능하다",
    "option3": "&& 는 둘 다 true여야 하는데 나이 조건이 false이므로 탑승 불가다",
    "option4": "키와 나이를 더한 값으로 판정하므로 탑승 가능하다",
    "answerIndex": 2,
    "explanation": "&&(그리고)는 양쪽 조건이 모두 true일 때만 전체가 true가 됩니다. 150 >= 120은 true지만 7 >= 8은 false라서 true && false → false, 즉 else 쪽이 실행되어 탑승 불가입니다. '하나만 통과해도 되는' 연산자는 ||(또는)예요."
  },
  {
    "courseSlug": "lv1-conditionals",
    "question": "점수 등급을 나누는 else if 사다리에서 if (score >= 70)을 맨 위에 두면 95점이 'C 등급'으로 출력됩니다. 그 이유는?",
    "option1": "else if 는 아래에서부터 위로 검사하기 때문이다",
    "option2": "95는 70보다 작다고 판정되기 때문이다",
    "option3": "else if 는 모든 조건을 검사한 뒤 마지막 결과를 쓰기 때문이다",
    "option4": "위에서부터 차례로 검사하다가 처음 true가 된 블록만 실행하고 빠져나오는데, 95도 '70 이상'이라 첫 조건에 걸리기 때문이다",
    "answerIndex": 3,
    "explanation": "else if 사다리는 위에서부터 순서대로 검사하고, 처음으로 true가 나온 블록 하나만 실행한 뒤 나머지는 검사조차 하지 않습니다. 95 >= 70이 true라서 거기서 끝나 버리는 거죠. 그래서 겹치는 조건은 좁은(빡빡한) 조건부터 위에 둬야 합니다."
  },
  {
    "courseSlug": "lv1-conditionals",
    "question": "number % 2 === 0 이라는 조건이 true가 되는 경우와 그 의미로 옳은 것은?",
    "option1": "number를 2로 나눈 나머지가 0일 때 true가 되며, 이는 number가 짝수라는 뜻이다",
    "option2": "number가 2보다 클 때 true가 되며, 이는 양수라는 뜻이다",
    "option3": "number를 2로 나눈 몫이 0일 때 true가 되며, 이는 홀수라는 뜻이다",
    "option4": "number가 0일 때만 true가 된다",
    "answerIndex": 0,
    "explanation": "% 는 나눗셈의 '나머지'를 구하는 연산자입니다. 어떤 수를 2로 나눈 나머지가 0이라는 것은 2로 나누어떨어진다는 뜻이므로 짝수입니다(예: 10 % 2 === 0은 true, 7 % 2는 1이라 false). 참고로 0도 2로 나눈 나머지가 0이므로 이 조건에서는 true가 됩니다."
  },
  {
    "courseSlug": "lv1-loops",
    "question": "다음 while문이 무한루프에 빠지는 이유로 가장 알맞은 것은?\n\nlet count = 1;\nwhile (count <= 5) {\n  console.log(\"멈추지 않아요\");\n}",
    "option1": "while은 원래 멈출 수 없는 반복문이라서",
    "option2": "반복문 안에 count 값을 바꾸는 줄이 없어 조건이 영원히 참이라서",
    "option3": "console.log가 반복을 강제로 계속 실행시키기 때문에",
    "option4": "조건에 <= 대신 < 를 써야 하기 때문에",
    "answerIndex": 1,
    "explanation": "while은 조건이 참인 동안 계속 반복합니다. count를 키우는 줄(count = count + 1)이 없으면 count는 영원히 1이고, 조건 count <= 5가 영원히 참이라 반복이 멈추지 않습니다. 조건 속 변수를 변하게 만드는 줄이 반복문 안에 있어야 언젠가 조건이 거짓이 되어 멈춥니다."
  },
  {
    "courseSlug": "lv1-loops",
    "question": "for (let i = 1; i <= 10; i++) 에서 세 요소의 실행 시점을 올바르게 설명한 것은?",
    "option1": "초기화는 딱 한 번, 조건은 매 바퀴 시작 전 검사, 증감은 매 바퀴 끝에 실행된다",
    "option2": "세 요소 모두 매 바퀴마다 한 번씩 실행된다",
    "option3": "초기화와 증감은 한 번씩만, 조건은 마지막에 한 번 검사된다",
    "option4": "조건은 반복이 다 끝난 뒤에 검사되어 최소 한 번은 무조건 실행된다",
    "answerIndex": 0,
    "explanation": "for문의 3요소는 역할과 시점이 다릅니다. 초기화(let i = 1)는 반복 시작 때 딱 한 번, 조건(i <= 10)은 매 바퀴 시작 전에 검사해 거짓이면 즉시 종료, 증감(i++)은 매 바퀴가 끝날 때마다 실행됩니다. 셋이 첫 줄에 모여 있어 몇 번 도는 반복인지 한눈에 보이는 것이 for의 장점입니다."
  },
  {
    "courseSlug": "lv1-loops",
    "question": "1부터 100까지의 합을 구할 때 누적 변수 sum을 반복 전에 0으로 시작하는 이유는?",
    "option1": "자바스크립트 문법상 변수는 반드시 0으로 초기화해야 해서",
    "option2": "0이 아니면 반복문이 실행되지 않기 때문에",
    "option3": "덧셈에서 0은 더해도 값을 바꾸지 않는 출발점이라, 빈 저금통처럼 시작해야 정확히 쌓은 만큼만 남기 때문에",
    "option4": "0으로 시작하면 반복 횟수가 한 번 줄어들어 더 빠르기 때문에",
    "answerIndex": 2,
    "explanation": "누적 변수는 빈 저금통에서 시작해야 반복하며 더한 값만 정확히 남습니다. 덧셈에서 0은 결과에 아무 영향을 주지 않는 값이라 출발점으로 적합합니다. 같은 원리로 곱셈을 누적한다면 시작값은 1이어야 합니다."
  },
  {
    "courseSlug": "lv1-loops",
    "question": "break와 continue의 차이를 올바르게 설명한 것은?",
    "option1": "break는 이번 바퀴만 건너뛰고, continue는 반복 전체를 끝낸다",
    "option2": "break는 반복 전체를 즉시 끝내고, continue는 이번 바퀴만 건너뛰고 다음 바퀴로 넘어간다",
    "option3": "둘 다 반복 전체를 끝내지만 break가 더 빠르다",
    "option4": "continue는 조건문에서만, break는 반복문에서만 쓸 수 있다",
    "answerIndex": 1,
    "explanation": "break는 반복문을 통째로 탈출합니다(예: i가 4일 때 break하면 4부터는 아예 실행되지 않음). continue는 이번 바퀴의 남은 코드만 건너뛰고 반복 자체는 계속됩니다(예: i가 3일 때 continue하면 3만 빠지고 4, 5는 출력됨). 비유하면 break는 하교, continue는 이 수업만 조퇴입니다."
  },
  {
    "courseSlug": "lv1-loops",
    "question": "const fruits = [\"사과\", \"바나나\", \"포도\", \"귤\"]; 일 때, 다음 중 옳은 것은?",
    "option1": "fruits[1]은 \"사과\"이고, fruits.length는 3이다",
    "option2": "fruits[0]은 \"사과\"이고, 마지막 항목은 fruits[4]이다",
    "option3": "fruits[0]은 \"사과\"이고, fruits.length는 4이며 마지막 항목은 fruits[3]이다",
    "option4": "인덱스는 1부터 시작하므로 fruits[4]가 \"귤\"이다",
    "answerIndex": 2,
    "explanation": "배열의 인덱스는 0부터 시작합니다. 첫 번째 항목이 fruits[0](\"사과\"), 네 번째이자 마지막 항목이 fruits[3](\"귤\")입니다. length는 담긴 개수인 4이지만 마지막 인덱스는 3이므로, for로 순회할 때 조건을 i < fruits.length로 쓰는 것입니다. i <= fruits.length로 쓰면 존재하지 않는 4번 칸까지 접근하는 하나 차이(off-by-one) 실수가 됩니다."
  },
  {
    "courseSlug": "lv1-functions",
    "question": "함수를 function greet() { console.log('안녕!'); } 로 정의만 하고 다른 코드를 쓰지 않았습니다. 실행하면 화면에 무엇이 나올까요?",
    "option1": "안녕! 이 한 번 출력된다",
    "option2": "아무것도 출력되지 않는다",
    "option3": "에러가 발생한다",
    "option4": "안녕! 이 계속 반복 출력된다",
    "answerIndex": 1,
    "explanation": "함수 정의는 '이런 명령어를 등록해 둔다'는 예약일 뿐, 그 자체로는 실행되지 않습니다. 함수 안의 코드가 실제로 돌아가려면 greet()처럼 이름 뒤에 괄호를 붙여 호출해야 합니다. 정의(레시피 적기)와 호출(실제 요리하기)의 구분이 이 강좌의 첫 번째 핵심이에요."
  },
  {
    "courseSlug": "lv1-functions",
    "question": "function add(a, b) { return a + b; } 에서 a와 b, 그리고 add(3, 4)로 호출할 때의 3과 4를 부르는 올바른 용어 짝은 무엇일까요?",
    "option1": "a·b는 인자, 3·4는 매개변수",
    "option2": "a·b는 매개변수, 3·4는 인자",
    "option3": "둘 다 매개변수라고 부른다",
    "option4": "a·b는 전역 변수, 3·4는 지역 변수",
    "answerIndex": 1,
    "explanation": "매개변수(parameter)는 함수를 정의할 때 괄호 안에 적어 두는 '입력 구멍'의 이름(a, b)이고, 인자(argument)는 호출할 때 그 구멍에 실제로 넣는 값(3, 4)입니다. 자판기 비유로는 동전 구멍이 매개변수, 실제로 넣는 동전이 인자예요."
  },
  {
    "courseSlug": "lv1-functions",
    "question": "function addAndShow(a, b) { console.log(a + b); } 를 const r = addAndShow(3, 4); 로 실행한 뒤 console.log(r); 을 하면 무엇이 출력될까요?",
    "option1": "7",
    "option2": "34",
    "option3": "undefined",
    "option4": "에러가 발생한다",
    "answerIndex": 2,
    "explanation": "console.log는 값을 화면에 '보여 주기만' 하고 돌려주지는 않습니다. 함수에 return이 없으면 부른 쪽에 건네지는 값이 없어서, 변수 r에는 undefined가 담겨요. 화면에 7이 찍히는 것과 값을 돌려받는 것은 완전히 다른 일이라는 점이 return 섹션의 핵심입니다."
  },
  {
    "courseSlug": "lv1-functions",
    "question": "함수 실행 중에 return 문을 만나면 어떤 일이 일어날까요?",
    "option1": "값을 돌려준 뒤 함수의 나머지 줄도 계속 실행된다",
    "option2": "값을 돌려주고 함수는 그 자리에서 즉시 끝난다",
    "option3": "함수가 처음부터 다시 실행된다",
    "option4": "return 아래 줄이 한 번만 더 실행되고 끝난다",
    "answerIndex": 1,
    "explanation": "return을 만나는 순간 함수는 값을 돌려주고 즉시 종료되며, 그 아래의 코드는 몇 줄이 있든 실행되지 않습니다. 이 성질을 역이용해 divide 함수처럼 '0으로 나누기 같은 이상한 입력을 먼저 return으로 내보내는' 방어 패턴을 만들 수 있어요."
  },
  {
    "courseSlug": "lv1-functions",
    "question": "function makeSecret() { const secret = '비밀'; } 을 실행한 뒤, 함수 밖에서 console.log(secret); 을 하면 어떻게 될까요?",
    "option1": "비밀 이 정상 출력된다",
    "option2": "undefined가 출력된다",
    "option3": "secret is not defined 에러가 난다",
    "option4": "빈 문자열이 출력된다",
    "answerIndex": 2,
    "explanation": "함수 안에서 만든 변수는 그 함수의 담장(지역 스코프) 안에서만 살기 때문에, 밖에서는 아예 존재하지 않는 이름이라 not defined 에러가 납니다. 이 벽 덕분에 함수마다 같은 이름의 변수를 써도 서로 부딪히지 않아요. 반대로 함수 안에서 밖(전역)의 변수를 읽는 것은 가능합니다."
  },
  {
    "courseSlug": "lv1-reading-errors",
    "question": "에러 메시지 'Uncaught TypeError: user.sayHi is not a function at greet (script.js:12:8)'에서 'script.js:12:8'이 뜻하는 것은 무엇인가요?",
    "option1": "에러가 12월 8일에 발생했다는 기록",
    "option2": "script.js 파일의 12번째 줄, 8번째 칸이라는 위치 정보",
    "option3": "에러가 지금까지 12번 발생했고 8번 무시됐다는 횟수",
    "option4": "script.js 파일의 크기가 12.8KB라는 정보",
    "answerIndex": 1,
    "explanation": "에러 메시지는 이름·설명·위치의 세 부분으로 이루어지는데, 'script.js:12:8'은 그중 위치 부분으로 '파일명:줄번호:칸번호'를 뜻해요. 에러를 읽을 때는 이 줄번호로 먼저 이동해서 코드를 눈으로 확인하는 것이 가장 빠른 첫걸음이기 때문에, 이 표기를 읽을 줄 아는 것이 중요합니다."
  },
  {
    "courseSlug": "lv1-reading-errors",
    "question": "console.log(username)을 실행했더니 'ReferenceError: username is not defined'가 떴습니다. 본문에서 배운 '1등 원인'으로 가장 먼저 의심해야 할 것은 무엇인가요?",
    "option1": "인터넷 연결이 끊겨서 변수를 불러오지 못했다",
    "option2": "컴퓨터 메모리가 부족해서 변수가 지워졌다",
    "option3": "userName처럼 대소문자가 다르게 선언된 이름을 잘못 불렀다",
    "option4": "변수를 너무 많이 만들어서 한도를 초과했다",
    "answerIndex": 2,
    "explanation": "ReferenceError의 'is not defined'는 컴퓨터가 그 이름을 찾지 못했다는 뜻이고, 가장 흔한 원인은 대소문자 오타예요. 컴퓨터에게 userName과 username은 완전히 다른 이름이지만 사람 눈에는 거의 같아 보여서 자주 놓칩니다. 그 다음으로 철자 오타, 선언 전에 사용한 경우를 의심하면 됩니다."
  },
  {
    "courseSlug": "lv1-reading-errors",
    "question": "숫자 변수에 대해 age.toUpperCase()를 호출하면 어떤 에러가 나며, 그 이유는 무엇인가요?",
    "option1": "SyntaxError — 괄호의 짝이 맞지 않아서",
    "option2": "ReferenceError — age라는 이름을 찾을 수 없어서",
    "option3": "TypeError — toUpperCase는 문자열 전용인데 age는 숫자라서",
    "option4": "에러가 나지 않고 숫자가 그대로 출력된다",
    "answerIndex": 2,
    "explanation": "TypeError는 값은 찾았지만 그 값의 형(type)이 기대와 달라 시킨 동작을 할 수 없을 때 발생해요. toUpperCase()는 문자열을 대문자로 바꾸는 문자열 전용 동작이라, 숫자인 age에게 시키면 'is not a function' 형태의 TypeError가 납니다. 이럴 때 typeof로 값의 정체를 확인하면 원인을 빨리 찾을 수 있어요."
  },
  {
    "courseSlug": "lv1-reading-errors",
    "question": "에러 없이 결과만 이상한 100줄짜리 코드에서 범인 줄을 찾을 때, 본문이 권한 '반씩 좁히기' 방법으로 옳은 것은 무엇인가요?",
    "option1": "1번째 줄부터 100번째 줄까지 한 줄씩 차례로 모두 읽는다",
    "option2": "한가운데에 console.log를 심어 값이 정상이면 뒤쪽 절반, 이상하면 앞쪽 절반으로 범위를 좁혀 간다",
    "option3": "코드를 전부 지우고 처음부터 새로 작성한다",
    "option4": "에러가 없으므로 고칠 필요가 없다고 판단한다",
    "answerIndex": 1,
    "explanation": "반씩 좁히기는 코드 한가운데에 console.log를 심어 그 지점까지의 값이 정상인지 확인하고, 정상이면 뒤쪽 절반에, 이상하면 앞쪽 절반에 범인이 있다고 판단해 범위를 절반씩 줄여 가는 방법이에요. 이렇게 하면 100줄짜리 코드도 log 일곱 번 정도면 범인 줄을 특정할 수 있어서, 처음부터 전부 훑는 것보다 훨씬 빠릅니다."
  },
  {
    "courseSlug": "lv1-reading-errors",
    "question": "에러를 해결하려고 검색하거나 AI에게 질문할 때, 본문에서 배운 올바른 태도는 무엇인가요?",
    "option1": "\"코드가 안 돼요\"라고만 짧게 물어보는 것이 예의다",
    "option2": "찾은 답은 이해할 필요 없이 최대한 빨리 복사해 붙여넣는다",
    "option3": "에러 메시지에 내 변수명·파일명을 반드시 포함해서 그대로 검색한다",
    "option4": "코드·에러 메시지·시도해 본 것을 함께 담아 질문하고, 답은 왜 통하는지 이해한 뒤에 옮겨 적는다",
    "answerIndex": 3,
    "explanation": "좋은 질문에는 ① 문제의 코드 ② 에러 메시지 전문 ③ 이미 시도해 본 것, 이 3종 세트가 담겨야 정확한 답을 받을 수 있어요. 검색할 때는 내 변수명·파일명처럼 나만 쓰는 부분은 지워야 남들의 같은 질문과 겹칩니다. 그리고 답을 찾았어도 '왜 이 답이 내 에러를 고치는가'를 설명할 수 있을 때만 옮겨 적어야 같은 에러를 다시 만났을 때 스스로 해결할 수 있어요."
  },
  {
    "courseSlug": "lv1-first-mini-project",
    "question": "Math.floor(Math.random() * 100) + 1 이 만들어 내는 숫자의 범위는?",
    "option1": "0부터 99까지의 정수",
    "option2": "1부터 100까지의 정수",
    "option3": "0부터 100까지의 소수",
    "option4": "1부터 99까지의 정수",
    "answerIndex": 1,
    "explanation": "Math.random()은 0 이상 1 미만의 소수를 주고, 100을 곱하면 0 이상 100 미만, Math.floor로 소수점을 자르면 0~99의 정수가 됩니다. 마지막에 1을 더하기 때문에 최종 범위는 1~100이에요. +1을 빼먹으면 0이 나올 수 있고 100은 절대 안 나오는 게임이 됩니다."
  },
  {
    "courseSlug": "lv1-first-mini-project",
    "question": "HTML 요소에 id=\"result\" 같은 id를 붙이는 가장 중요한 이유는?",
    "option1": "브라우저가 요소를 더 빨리 화면에 그리게 하려고",
    "option2": "요소의 글자 색과 크기를 자동으로 예쁘게 바꾸려고",
    "option3": "자바스크립트가 그 요소를 이름으로 찾아와 다룰 수 있게 하려고",
    "option4": "같은 요소를 여러 번 복사해서 쓰려고",
    "answerIndex": 2,
    "explanation": "id는 요소에 붙이는 '이름표'입니다. document.getElementById('result')처럼 자바스크립트가 이름으로 요소를 찾아와서 값을 읽거나 innerText로 글자를 바꿀 수 있어요. 이름표가 없으면 스크립트가 그 요소를 만질 방법이 없기 때문에 id가 필요합니다."
  },
  {
    "courseSlug": "lv1-first-mini-project",
    "question": "버튼을 눌렀을 때 check() 함수가 실행되게 하려면 버튼에 무엇을 써야 할까요?",
    "option1": "<button onclick=\"check()\">도전!</button>",
    "option2": "<button innerText=\"check()\">도전!</button>",
    "option3": "<button id=\"check()\">도전!</button>",
    "option4": "<button style=\"check()\">도전!</button>",
    "answerIndex": 0,
    "explanation": "onclick은 '이 요소가 클릭되면(on click) 이 코드를 실행해 줘'라고 버튼과 함수를 잇는 전선 역할을 합니다. innerText는 요소의 글자를 바꾸는 속성, id는 이름표, style은 색·크기 등 꾸미기 속성이라 함수 실행과는 관계가 없어요."
  },
  {
    "courseSlug": "lv1-first-mini-project",
    "question": "입력창에서 꺼낸 값을 Number()로 바꿔 주는 이유는 무엇일까요?",
    "option1": "입력창의 값은 항상 글자(문자열)라서, 숫자로 바꿔야 정답과 올바르게 비교할 수 있기 때문",
    "option2": "Number()를 쓰지 않으면 입력창에 아예 값이 들어가지 않기 때문",
    "option3": "Number()가 입력된 숫자를 자동으로 1~100 범위로 잘라 주기 때문",
    "option4": "Number()가 정답 숫자를 새로 뽑아 주기 때문",
    "answerIndex": 0,
    "explanation": "입력창에서 꺼낸 값은 겉보기에 숫자여도 속은 글자('50')입니다. 글자 '50'과 숫자 50은 다른 존재라서, Number()로 숫자로 변신시킨 뒤에야 정답과 정확하게 비교할 수 있어요. Number()는 범위를 자르거나 정답을 뽑는 기능과는 무관합니다."
  },
  {
    "courseSlug": "lv1-first-mini-project",
    "question": "플레이어가 입력창에 글자(예: 'abc')를 넣었을 때 일어나는 일과 올바른 방어 방법은?",
    "option1": "Number('abc')는 0이 되므로 아무 문제 없이 채점된다",
    "option2": "Number('abc')는 NaN이 되므로, isNaN()으로 확인해서 이상한 입력이면 return으로 함수를 끝낸다",
    "option3": "브라우저가 알아서 글자를 숫자로 바꿔 주므로 방어가 필요 없다",
    "option4": "innerText가 자동으로 글자 입력을 막아 준다",
    "answerIndex": 1,
    "explanation": "글자를 Number()로 바꾸면 NaN(Not a Number, '숫자 아님')이 되어 정답과의 비교가 전부 이상해집니다. 그래서 check() 함수 맨 앞에 isNaN()이라는 문지기를 세우고, 이상한 입력이면 안내 문구를 보여 준 뒤 return으로 즉시 함수를 끝내서 아래의 채점 코드가 실행되지 않게 막아야 해요."
  },
  {
    "courseSlug": "lv2-async-javascript",
    "question": "fetch로 요청을 보냈는데 서버가 404 상태 코드로 응답했다. 이때 fetch가 돌려준 Promise는 어떻게 될까요?",
    "option1": "rejected되어 catch 블록으로 실행이 넘어간다",
    "option2": "fulfilled된다 — HTTP 에러 응답은 reject 사유가 아니므로 res.ok를 직접 확인해야 한다",
    "option3": "pending 상태에 계속 머무른다",
    "option4": "브라우저가 자동으로 요청을 3회 재시도한다",
    "answerIndex": 1,
    "explanation": "fetch는 서버와의 연결 자체가 실패(네트워크 단절 등)했을 때만 reject됩니다. 서버가 404나 500으로 '대답'한 것은 fetch 입장에서는 통신 성공이므로 Promise는 fulfilled됩니다. 그래서 HTTP 수준의 실패를 잡으려면 res.ok(상태 코드 200~299)를 확인하고 직접 throw해서 catch 그물에 걸리게 해야 합니다."
  },
  {
    "courseSlug": "lv2-async-javascript",
    "question": "Promise의 상태 전이에 대한 설명으로 옳은 것은?",
    "option1": "fulfilled 상태였다가 나중에 rejected로 바뀔 수 있다",
    "option2": "pending에서 시작해 fulfilled 또는 rejected 중 하나로 단 한 번 정착하며, 정착 후에는 바뀌지 않는다",
    "option3": "rejected된 Promise는 재시도하면 같은 객체가 다시 pending으로 돌아간다",
    "option4": "then을 호출해야 비로소 pending 상태가 시작된다",
    "answerIndex": 1,
    "explanation": "Promise는 pending(대기)에서 출발해 성공하면 fulfilled, 실패하면 rejected로 전이하는데, 이 전이는 단 한 번뿐이고 한번 정착(settled)한 상태는 절대 되돌아가거나 바뀌지 않습니다. 이 '단 한 번, 둘 중 하나' 보장 덕분에 결과를 믿고 기다릴 수 있습니다. 재시도는 새 Promise를 만드는 것이지 기존 객체를 되돌리는 게 아닙니다."
  },
  {
    "courseSlug": "lv2-async-javascript",
    "question": "async/await에 대한 설명으로 옳은 것은?",
    "option1": "await는 Promise와 무관한 완전히 새로운 비동기 실행 방식이다",
    "option2": "await를 쓰면 섹션 1의 while 루프처럼 브라우저 화면 전체가 멈춘다",
    "option3": "await는 Promise를 기다리는 문법 설탕으로, 해당 async 함수만 멈추고 콜 스택은 풀려나 다른 일을 처리한다",
    "option4": "async 함수에서 return 42를 하면 호출한 쪽에서 숫자 42를 바로 받는다",
    "answerIndex": 2,
    "explanation": "await는 새 동작이 아니라 then 체인과 같은 일을 위에서 아래로 읽히게 표기하는 문법 설탕입니다. await에서 멈추는 것은 그 async 함수 하나뿐이고, 일꾼(콜 스택)은 풀려나 다른 주문서를 처리하므로 동기 while 루프와 달리 화면이 얼지 않습니다. 또한 async 함수의 반환값은 항상 Promise로 감싸져 나오므로 호출하는 쪽에서도 await가 필요합니다."
  },
  {
    "courseSlug": "lv2-async-javascript",
    "question": "서로 의존하지 않는 환율 요청 3개(USD·EUR·JPY)를 처리할 때, Promise.all과 Promise.allSettled의 차이로 옳은 것은?",
    "option1": "Promise.all은 하나라도 실패하면 전체가 reject되고, allSettled는 성공·실패를 가리지 않고 전원의 결과 보고서를 배열로 돌려준다",
    "option2": "Promise.all은 요청을 하나씩 순차 실행하고, allSettled만 병렬로 실행한다",
    "option3": "allSettled는 실패한 요청을 자동으로 재시도한 뒤 결과를 준다",
    "option4": "둘 다 하나라도 실패하면 즉시 reject되며, 차이는 결과 배열의 순서뿐이다",
    "answerIndex": 0,
    "explanation": "Promise.all은 '전원 성공 아니면 전체 실패'라서 하나만 rejected돼도 성공한 결과까지 함께 버려집니다 — 결과들이 한 몸일 때 적합합니다. Promise.allSettled는 절대 reject되지 않고 각 Promise의 성공(fulfilled/value)·실패(rejected/reason)를 담은 결과 배열을 돌려주므로, 되는 것만이라도 화면에 보여 주는 부분 실패 허용에 적합합니다. 둘 다 요청은 동시에(병렬로) 출발합니다."
  },
  {
    "courseSlug": "lv2-async-javascript",
    "question": "AbortController를 이용한 요청 취소·타임아웃 패턴에 대한 설명으로 옳은 것은?",
    "option1": "controller.abort()를 호출하면 fetch의 Promise가 fulfilled로 정착하며 빈 응답을 돌려준다",
    "option2": "AbortController는 아직 보내지 않은 요청만 예약 취소할 수 있다",
    "option3": "fetch 옵션에 signal을 연결해 두고 abort()를 호출하면 해당 Promise가 AbortError로 reject되므로, catch에서 err.name으로 의도한 취소와 진짜 실패를 구분해야 한다",
    "option4": "타임아웃은 fetch에 timeout 숫자 옵션을 넘기는 것이 표준 방법이다",
    "answerIndex": 2,
    "explanation": "fetch 옵션의 signal에 controller.signal을 연결해 두면, abort() 호출 시 그 Promise가 'AbortError'라는 이름의 에러로 reject되어 이미 나가 있는 요청도 끊을 수 있습니다. 다만 검색어 변경처럼 우리가 의도적으로 취소한 경우도 catch로 들어오므로, err.name === 'AbortError'를 확인해 의도한 취소는 조용히 처리하고 진짜 실패만 에러 메시지로 보여 줘야 합니다. 타임아웃도 setTimeout에서 abort()를 호출하는 같은 패턴으로 구현합니다."
  },
  {
    "courseSlug": "lv2-array-data-wrangling",
    "question": "res.map((o) => { o.order_id }) 를 실행하면 결과 배열의 요소는 무엇으로 채워질까요?",
    "option1": "각 주문의 order_id 값",
    "option2": "undefined",
    "option3": "원본 주문 객체 그대로",
    "option4": "문법 오류로 아예 실행되지 않는다",
    "answerIndex": 1,
    "explanation": "화살표 함수에서 중괄호 { }를 여는 순간 '블록'이 되어 return을 직접 써야 합니다. 이 코드는 o.order_id를 계산만 하고 아무것도 반환하지 않으므로, map은 요소마다 undefined를 받아 [undefined, undefined, ...] 배열을 만듭니다. 문법 오류는 아니라서 조용히 잘못된 결과가 나오는 것이 이 실수가 무서운 이유입니다."
  },
  {
    "courseSlug": "lv2-array-data-wrangling",
    "question": "객체 배열에 reduce를 쓰면서 초기값을 생략하면 어떤 일이 벌어질까요?",
    "option1": "자동으로 0이 초기값이 되어 문제없이 합계가 구해진다",
    "option2": "항상 TypeError가 발생해 즉시 알아챌 수 있다",
    "option3": "배열의 첫 요소(객체)가 누산기의 출발점이 되어 엉뚱한 결과가 나온다",
    "option4": "누산기가 빈 객체 {}로 시작해 그룹핑이 된다",
    "answerIndex": 2,
    "explanation": "초기값을 생략하면 자바스크립트는 배열의 '첫 요소'를 누산기의 출발점으로 삼습니다. 객체 배열이라면 첫 바퀴의 acc가 주문 객체 자체가 되어 \"[object Object]18000...\" 같은 괴상한 문자열이 만들어집니다. TypeError는 '빈 배열 + 초기값 생략'일 때만 나므로, 데이터가 있으면 조용히 틀립니다. 그래서 초기값은 항상 쓰는 것으로 익혀야 합니다."
  },
  {
    "courseSlug": "lv2-array-data-wrangling",
    "question": "sort에 관한 설명으로 옳은 것은 무엇일까요?",
    "option1": "sort는 map·filter처럼 원본을 두고 새 배열을 돌려준다",
    "option2": "비교 함수를 생략하면 요소를 문자열로 바꿔 비교하므로 [5, 1, 30].sort()는 [1, 30, 5]가 된다",
    "option3": "비교 함수가 양수를 반환하면 a가 앞으로 온다",
    "option4": "toSorted도 sort와 마찬가지로 원본 배열을 바꾼다",
    "answerIndex": 1,
    "explanation": "비교 함수 없이 sort를 부르면 모든 요소를 문자열로 바꿔 사전순 비교하므로 '30'이 '5'보다 앞에 옵니다('3' < '5'). 나머지는 반대로 알아야 합니다 — sort는 원본 자체를 재배열하는 메서드이고(그래서 [...arr].sort나 toSorted로 방어), toSorted는 원본을 두고 새 배열을 돌려주며, 비교 함수가 음수일 때 a가 앞으로 옵니다."
  },
  {
    "courseSlug": "lv2-array-data-wrangling",
    "question": "조건에 맞는 주문이 하나도 없을 때, filter와 find는 각각 무엇을 돌려줄까요?",
    "option1": "filter는 빈 배열 [], find는 undefined",
    "option2": "둘 다 undefined",
    "option3": "둘 다 빈 배열 []",
    "option4": "filter는 undefined, find는 빈 배열 []",
    "answerIndex": 0,
    "explanation": "filter는 조건에 맞는 '전부'를 담은 배열을 돌려주므로 없으면 빈 배열 []이고, find는 조건에 맞는 '첫 하나'의 요소 자체를 돌려주므로 없으면 undefined입니다. 이 차이 때문에 find 결과에 바로 점(.)을 찍으면 TypeError가 날 수 있어, 옵셔널 체이닝(?.)으로 방어해야 합니다."
  },
  {
    "courseSlug": "lv2-array-data-wrangling",
    "question": "주문마다 items 배열이 들어 있는 중첩 데이터에서, 모든 상품을 한 겹의 평평한 배열로 나열하려면 어떤 메서드가 알맞을까요?",
    "option1": "orders.map((o) => o.items) — map만으로 충분하다",
    "option2": "orders.filter((o) => o.items)",
    "option3": "orders.flatMap((o) => o.items)",
    "option4": "orders.reduce((acc, o) => acc + o.items, 0)",
    "answerIndex": 2,
    "explanation": "map만 쓰면 요소마다 items '배열'이 반환되어 [[키보드, 마우스], [모니터]]처럼 배열의 배열(두 겹)이 됩니다. flatMap은 map을 하고 나서 결과를 한 겹 펼쳐(flat) [키보드, 마우스, 모니터] 같은 평평한 배열을 만들어 줍니다. filter는 골라내기, reduce에 초기값 0을 주면 숫자로 접는 것이라 상품 나열이라는 목적과 맞지 않습니다."
  },
  {
    "courseSlug": "lv2-rest-api-design",
    "question": "REST API에서 URL을 설계할 때 올바른 원칙은 무엇인가요?",
    "option1": "URL에 getAllPosts처럼 동사를 넣어 행동을 명확히 표현한다",
    "option2": "URL에는 명사를 복수형으로 쓰고, 행동은 HTTP 메서드로 표현한다",
    "option3": "URL은 짧을수록 좋으므로 /p, /c처럼 축약해서 쓴다",
    "option4": "리소스마다 단수형과 복수형을 자유롭게 섞어 써도 된다",
    "answerIndex": 1,
    "explanation": "REST에서 URL은 자원(리소스)의 '주소' 역할만 합니다. 그래서 /posts처럼 명사 복수형으로 쓰고, 조회·생성·삭제 같은 '행동'은 GET·POST·DELETE 등 HTTP 메서드가 맡습니다. /getAllPosts처럼 동사가 URL에 들어가면 메서드가 할 일을 URL이 뺏은 것이고, 단수·복수를 섞으면 일관성이 깨져 사용하기 어려워집니다."
  },
  {
    "courseSlug": "lv2-rest-api-design",
    "question": "PUT과 PATCH의 차이로 옳은 것은 무엇인가요?",
    "option1": "PUT은 부분 수정, PATCH는 전체 교체다",
    "option2": "PUT은 조회용이고 PATCH는 삭제용이다",
    "option3": "PUT은 전체 교체라서 보내지 않은 필드가 사라질 수 있고, PATCH는 보낸 필드만 바꾼다",
    "option4": "둘은 완전히 같은 동작을 하며 이름만 다르다",
    "answerIndex": 2,
    "explanation": "PUT은 리소스를 보낸 내용으로 '통째로 갈아 끼우는' 전체 교체입니다. 그래서 title만 담아 PUT을 보내면 content나 tags 같은 나머지 필드가 사라지는 사고가 납니다. 반면 PATCH는 보낸 필드만 바꾸고 나머지는 그대로 두는 부분 수정이므로, '제목만 고치기' 같은 기능에는 PATCH가 맞습니다."
  },
  {
    "courseSlug": "lv2-rest-api-design",
    "question": "멱등성(idempotency)에 대한 설명으로 옳은 것은 무엇인가요?",
    "option1": "POST는 멱등해서 네트워크 오류 시 마음 놓고 재시도해도 된다",
    "option2": "멱등성이란 같은 요청을 두 번 보내도 결과가 한 번 보낸 것과 같다는 뜻이며, POST는 멱등하지 않다",
    "option3": "GET은 보낼 때마다 데이터가 변하므로 멱등하지 않다",
    "option4": "멱등성은 요청 속도가 빠른지를 나타내는 성능 지표다",
    "answerIndex": 1,
    "explanation": "멱등성은 '같은 요청을 두 번 보내도 결과가 한 번과 같은가'라는 성질입니다. GET은 몇 번 읽어도 데이터가 변하지 않고 DELETE도 결과 상태가 같아 멱등하지만, POST는 보낼 때마다 새 리소스가 하나 더 생기므로 멱등하지 않습니다. 그래서 네트워크가 끊겨 재시도할 때 POST를 무심코 다시 보내면 글이 두 번 등록되거나 결제가 두 번 나가는 사고가 납니다."
  },
  {
    "courseSlug": "lv2-rest-api-design",
    "question": "HTTP 상태 코드의 쓰임으로 옳은 것은 무엇인가요?",
    "option1": "401은 권한 부족, 403은 로그인이 안 된 상태를 뜻한다",
    "option2": "삭제 성공처럼 돌려줄 바디가 없는 성공에는 204를 쓴다",
    "option3": "클라이언트가 필수 필드를 빠뜨렸을 때도 500으로 응답하는 것이 표준이다",
    "option4": "201은 리소스를 삭제했을 때 쓰는 코드다",
    "answerIndex": 1,
    "explanation": "204 No Content는 '성공했지만 돌려줄 바디가 없다'는 뜻으로 DELETE 성공이 대표 사례입니다. 401은 '누구세요?'(인증 안 됨), 403은 '누군지는 알지만 권한이 없음'으로 1번 보기는 둘이 뒤바뀌었고, 201은 삭제가 아니라 POST로 새 리소스가 만들어졌을 때 씁니다. 500은 서버 코드가 터졌을 때만 써야 하며, 클라이언트 실수까지 500으로 뭉뚱그리면 4xx/5xx로 책임 소재를 가리는 디버깅 나침반이 망가집니다."
  },
  {
    "courseSlug": "lv2-rest-api-design",
    "question": "경로 파라미터와 쿼리 파라미터를 고르는 기준으로 옳은 것은 무엇인가요?",
    "option1": "값이 없으면 대상 자체가 특정되지 않는 것은 경로로, 없어도 기본값으로 동작하는 정렬·페이지 같은 옵션은 쿼리로 표현한다",
    "option2": "숫자는 항상 경로에, 문자는 항상 쿼리에 넣는다",
    "option3": "정렬과 페이지네이션은 반드시 경로 파라미터로 표현해야 한다",
    "option4": "쿼리 파라미터는 한 번에 하나만 쓸 수 있으므로 조합이 필요하면 경로를 쓴다",
    "answerIndex": 0,
    "explanation": "선택 기준은 '그 값이 없으면 대상 자체가 특정되지 않는가'입니다. 3번 글의 댓글은 3이 없으면 성립하지 않으므로 경로(/posts/3/comments)로 쓰고, 정렬·페이지는 없어도 기본 정렬·1페이지로 동작할 수 있으므로 쿼리(?sort=-createdAt&page=2)로 씁니다. 즉 경로는 자원의 '주소', 쿼리는 자원을 보는 '방식'이며, 쿼리는 &로 얼마든지 조합할 수 있습니다."
  },
  {
    "courseSlug": "lv2-express-crud",
    "question": "POST 요청의 JSON 본문(body)이 req.body에서 undefined로 나온다. 강좌에서 배운 내용상 가장 먼저 의심해야 할 원인은?",
    "option1": "nodemon을 설치하지 않아서",
    "option2": "app.use(express.json()) 미들웨어를 등록하지 않아서",
    "option3": "포트 번호를 3000이 아닌 다른 번호로 설정해서",
    "option4": "res.json() 대신 res.send()를 사용해서",
    "answerIndex": 1,
    "explanation": "Express는 기본적으로 요청 본문을 읽지 않기 때문에, express.json() 미들웨어를 등록해야 JSON 본문이 파싱되어 req.body 객체로 들어옵니다. 강좌에서도 \"body가 안 읽혀요\"의 90%가 이 한 줄을 빼먹은 경우라고 배웠습니다. nodemon은 자동 재시작 도구, 포트는 문 번호일 뿐 body 파싱과는 무관합니다."
  },
  {
    "courseSlug": "lv2-express-crud",
    "question": "리소스 생성 성공과 삭제 성공에 어울리는 상태 코드를 순서대로 짝지은 것은?",
    "option1": "200 Created — 404 Not Found",
    "option2": "204 No Content — 201 Created",
    "option3": "201 Created — 204 No Content",
    "option4": "400 Bad Request — 200 OK",
    "answerIndex": 2,
    "explanation": "생성 성공에는 \"만들어졌다\"를 뜻하는 전용 코드 201 Created를 쓰고, 이때 서버가 붙인 id를 알 수 있도록 생성된 리소스를 본문에 담아 돌려줍니다. 삭제 성공은 \"잘 처리했고 돌려줄 내용이 없다\"는 204 No Content가 어울리며, 204에는 본문을 싣지 않으므로 res.json()이 아닌 res.status(204).end()를 사용합니다."
  },
  {
    "courseSlug": "lv2-express-crud",
    "question": "GET /posts/:id 핸들러에서 posts.find((p) => p.id === req.params.id)가 존재하는 글도 못 찾는 이유는?",
    "option1": "find는 배열이 아닌 객체에만 쓸 수 있는 메서드라서",
    "option2": "경로 파라미터는 req.query로 접근해야 해서",
    "option3": ":id 자리표시자는 숫자만 받을 수 있어서",
    "option4": "req.params.id는 항상 문자열이라 숫자 id와 === 비교하면 false가 되기 때문에",
    "answerIndex": 3,
    "explanation": "URL에서 온 값은 전부 문자열입니다. '3' === 3은 false이므로, Number(req.params.id)로 변환한 뒤 비교해야 숫자 id와 일치시킬 수 있습니다. 이는 강좌에서 짚은 대표적인 함정으로, 쿼리스트링(req.query)의 page·limit 값도 같은 이유로 숫자 변환이 필요합니다."
  },
  {
    "courseSlug": "lv2-express-crud",
    "question": "직접 만든 미들웨어 함수 안에서 next()를 호출하지 않으면 어떤 일이 벌어지는가?",
    "option1": "요청이 다음 단계로 넘어가지 못하고 갇혀서, 클라이언트는 응답을 받지 못한 채 계속 기다리게 된다",
    "option2": "Express가 자동으로 다음 미들웨어를 실행해 주므로 아무 문제가 없다",
    "option3": "서버가 즉시 500 에러를 응답한다",
    "option4": "해당 미들웨어가 건너뛰어지고 핸들러가 바로 실행된다",
    "answerIndex": 0,
    "explanation": "next()는 \"내 검문은 끝났으니 다음 순번에게 넘겨라\"는 신호입니다. 미들웨어가 next()도 부르지 않고 응답도 보내지 않으면 요청은 그 검문소에 갇혀 멈추고, 브라우저는 응답 없이 계속 기다립니다. Express가 자동으로 넘겨주지 않으며, 에러를 대신 응답해 주지도 않습니다."
  },
  {
    "courseSlug": "lv2-express-crud",
    "question": "브라우저 주소창만으로는 우리가 만든 API를 전부 테스트할 수 없어서 Postman·REST Client 같은 도구가 필요하다. 그 근본 이유는?",
    "option1": "주소창은 localhost 주소를 입력할 수 없기 때문에",
    "option2": "주소창 입력은 언제나 GET 요청이라 POST·PATCH·DELETE를 보내거나 JSON 본문을 실을 방법이 없기 때문에",
    "option3": "브라우저는 JSON 응답을 화면에 표시하지 못하기 때문에",
    "option4": "주소창으로는 쿼리스트링을 붙일 수 없기 때문에",
    "answerIndex": 1,
    "explanation": "주소창에 URL을 입력하는 행위는 언제나 GET 요청입니다. 메서드를 바꾸거나 JSON 본문·헤더를 실을 수 없으므로 POST·PATCH·DELETE는 주소창으로 테스트 자체가 불가능합니다. 반면 localhost 접속과 쿼리스트링(?q=...&page=1)은 주소창으로도 가능하고, 브라우저는 JSON 응답을 텍스트로 잘 보여 줍니다."
  },
  {
    "courseSlug": "lv2-db-transactions",
    "question": "메모리 배열에 저장하던 블로그 글이 서버를 재시작하면 사라지는 근본 이유는 무엇인가요?",
    "option1": "Node.js가 재시작 시 파일을 자동 삭제하기 때문",
    "option2": "변수는 프로세스의 메모리(RAM)에 살아서, 프로세스가 죽으면 메모리가 운영체제에 반납되기 때문",
    "option3": "HTTP가 무상태 프로토콜이라 요청이 끝나면 데이터를 지우기 때문",
    "option4": "배열의 최대 크기 제한을 초과했기 때문",
    "answerIndex": 1,
    "explanation": "변수(배열)는 디스크가 아니라 프로세스의 RAM 위에 존재합니다. 프로세스가 종료되면 그 메모리는 운영체제에 반납되므로 데이터가 함께 사라져요. DB는 데이터를 디스크에 기록해 이 '영속성' 문제를 해결합니다."
  },
  {
    "courseSlug": "lv2-db-transactions",
    "question": "사용자 입력을 SQL에 넣을 때 문자열 이어붙이기 대신 파라미터 바인딩($1)을 써야 하는 이유로 옳은 것은?",
    "option1": "바인딩된 값은 SQL 문장 구조를 바꿀 수 없는 '그냥 값'으로 취급되어 인젝션이 원천 차단되기 때문",
    "option2": "$1 문법이 문자열 조립보다 실행 속도가 항상 10배 빠르기 때문",
    "option3": "문자열 조립은 PostgreSQL에서 문법 오류를 일으켜 아예 실행되지 않기 때문",
    "option4": "바인딩을 쓰면 커넥션 풀 없이도 연결이 자동 관리되기 때문",
    "answerIndex": 0,
    "explanation": "문자열 조립은 ' OR '1'='1 같은 입력이 SQL 문장의 일부가 되어 전체 데이터 유출·테이블 삭제로 이어질 수 있습니다. 바인딩은 SQL 구조를 먼저 확정하고 값을 별도로 전달하므로, DB가 입력을 어떤 내용이든 단순한 값으로만 취급해 문장을 바꿀 수 없어요."
  },
  {
    "courseSlug": "lv2-db-transactions",
    "question": "UPDATE 쿼리 실행 후 result.rowCount가 0이었다면, API는 이를 어떻게 해석하고 응답하는 것이 적절한가요?",
    "option1": "쿼리가 실패했으므로 500 Internal Server Error로 응답한다",
    "option2": "WHERE 조건에 해당하는 행이 없었다는 뜻이므로 404 Not Found로 응답한다",
    "option3": "수정할 내용이 기존 값과 같았다는 뜻이므로 200 OK로 응답한다",
    "option4": "트랜잭션이 롤백되었다는 뜻이므로 재시도한다",
    "answerIndex": 1,
    "explanation": "rowCount는 쿼리가 실제로 영향을 준 행의 수입니다. UPDATE ... WHERE id = $1에서 0이 나왔다면 그 id의 행이 존재하지 않았다는 뜻이므로, 배열 시절 find()가 undefined를 돌려주던 것과 같은 신호로 보고 404로 응답하는 것이 맞습니다."
  },
  {
    "courseSlug": "lv2-db-transactions",
    "question": "'댓글 작성 INSERT'와 '글의 댓글수 UPDATE'를 하나의 트랜잭션으로 묶었을 때, 댓글수 UPDATE가 실패하면 어떤 일이 일어나나요?",
    "option1": "INSERT는 이미 실행됐으므로 댓글만 저장되고 댓글수는 그대로 남는다",
    "option2": "ROLLBACK에 의해 INSERT까지 함께 취소되어, 두 작업 모두 없던 일이 된다",
    "option3": "DB가 댓글수를 자동으로 다시 계산해 맞춰 준다",
    "option4": "COMMIT이 절반만 실행되어 댓글이 임시 상태로 저장된다",
    "answerIndex": 1,
    "explanation": "트랜잭션의 핵심이 all-or-nothing입니다. BEGIN 이후의 변경은 COMMIT 전까지 '초안' 상태이고, 중간에 하나라도 실패해 ROLLBACK하면 BEGIN 이후의 모든 변경이 통째로 취소됩니다. 그래서 '댓글은 달렸는데 댓글수는 안 오른' 어정쩡한 상태가 존재할 수 없어요."
  },
  {
    "courseSlug": "lv2-db-transactions",
    "question": "트랜잭션 안에서 외부 API(예: 알림 메일 발송)를 호출하면 안 되는 이유로 본문이 든 것은?",
    "option1": "외부 API 응답이 SQL 인젝션 공격에 노출되기 때문",
    "option2": "PostgreSQL이 트랜잭션 중의 네트워크 요청을 차단하기 때문",
    "option3": "느린 외부 호출 동안 연결과 잠금이 계속 점유되고, 롤백해도 이미 나간 외부 호출은 되돌릴 수 없기 때문",
    "option4": "외부 API 호출이 COMMIT을 자동으로 실행해 버리기 때문",
    "answerIndex": 2,
    "explanation": "이유는 두 겹입니다. 첫째, 트랜잭션이 열려 있는 동안 그 연결과 DB 잠금이 계속 점유되어 느린 외부 호출 하나가 커넥션 풀 전체를 말릴 수 있습니다. 둘째, DB 변경은 롤백되지만 이미 발송된 메일 같은 외부 세계의 일은 롤백되지 않아 데이터와 현실이 어긋납니다. 그래서 트랜잭션은 DB 작업만으로 짧게 닫고, 외부 호출은 커밋 확정 후에 합니다."
  },
  {
    "courseSlug": "lv2-auth-session-hash",
    "question": "같은 비밀번호를 bcrypt.hash()로 두 번 해시하면 서로 다른 결과가 나온다. 그 이유로 가장 알맞은 것은?",
    "option1": "bcrypt가 해시할 때마다 현재 시각을 결과에 포함하기 때문에",
    "option2": "bcrypt가 매번 무작위 솔트(salt)를 만들어 비밀번호에 섞은 뒤 해시하기 때문에",
    "option3": "bcrypt가 내부적으로 비밀번호를 암호화했다가 다시 복호화하기 때문에",
    "option4": "해시 함수는 원래 같은 입력에도 항상 다른 출력을 내는 함수이기 때문에",
    "answerIndex": 1,
    "explanation": "bcrypt는 해시할 때마다 무작위 솔트를 생성해 비밀번호에 섞고, 그 솔트를 해시 문자열 안에 함께 저장합니다. 그래서 같은 비밀번호도 매번 다른 해시가 되고, 미리 계산해 둔 해시 사전(레인보 테이블)으로 역추적하는 공격이 막힙니다. 검증할 때는 저장된 해시에서 솔트를 꺼내 입력값을 같은 방식으로 다시 해시해 대조합니다(bcrypt.compare). 해시 함수 자체는 같은 입력+같은 솔트면 항상 같은 출력을 냅니다."
  },
  {
    "courseSlug": "lv2-auth-session-hash",
    "question": "로그인 실패 시 '이메일이 틀렸는지'와 '비밀번호가 틀렸는지'를 구분하지 않고 같은 메시지로 응답하는 이유는?",
    "option1": "두 경우를 구분하는 코드가 복잡해서 서버 성능이 떨어지기 때문에",
    "option2": "HTTP 표준이 401 응답에는 하나의 에러 메시지만 담도록 규정하기 때문에",
    "option3": "구분해서 알려주면 공격자에게 '이 이메일은 가입돼 있다'는 사실을 확인시켜 주기 때문에",
    "option4": "사용자가 에러 메시지를 읽고 혼란스러워하는 것을 막기 위한 UX 원칙이기 때문에",
    "answerIndex": 2,
    "explanation": "'비밀번호가 틀렸습니다'라고 답하는 순간 공격자는 해당 이메일이 가입된 계정임을 공짜로 확인하게 됩니다. 그렇게 모은 유효 계정 목록은 비밀번호 대입 공격의 표적을 좁히는 데 쓰이므로, 에러 메시지는 공격자에게 정보를 흘리지 않도록 두 경우를 똑같이 '이메일 또는 비밀번호가 올바르지 않습니다'로 응답하는 것이 원칙입니다."
  },
  {
    "courseSlug": "lv2-auth-session-hash",
    "question": "세션 방식 로그인에서 브라우저와 서버의 역할 분담으로 옳은 것은?",
    "option1": "브라우저는 뜻 없는 세션ID만 쿠키로 들고 다니고, 사용자가 누구인지는 서버의 세션 저장소가 기억한다",
    "option2": "브라우저가 사용자 이메일과 권한 정보를 쿠키에 담아 두고, 서버는 아무것도 저장하지 않는다",
    "option3": "브라우저와 서버가 각각 비밀번호 해시를 나눠 보관하고 매 요청마다 대조한다",
    "option4": "서버가 세션ID를 URL 파라미터로 발급하고 브라우저는 이를 localStorage에 저장한다",
    "answerIndex": 0,
    "explanation": "세션 방식의 핵심은 코트 보관소 번호표 구조입니다. 로그인에 성공하면 서버가 세션 저장소에 '세션ID=사용자 몇 번'을 기록하고, Set-Cookie로 세션ID만 브라우저에 건넵니다. 브라우저는 이후 요청마다 그 쿠키를 자동으로 실어 보내고, 서버가 세션ID로 저장소를 조회해 사용자를 복원합니다. 실제 정보는 서버 쪽에 있고 브라우저가 가진 것은 무작위 문자열뿐입니다."
  },
  {
    "courseSlug": "lv2-auth-session-hash",
    "question": "로그인한 사용자가 다른 사람이 쓴 글의 수정 API를 호출했다. 서버가 돌려줘야 할 응답으로 가장 알맞은 것은?",
    "option1": "401 Unauthorized — 인증이 안 된 요청이므로",
    "option2": "403 Forbidden — 누구인지는 확인됐지만 권한 밖의 요청이므로",
    "option3": "409 Conflict — 글의 현재 상태와 충돌하는 요청이므로",
    "option4": "404 Not Found — 그 사용자에게는 존재하지 않는 글이므로",
    "answerIndex": 1,
    "explanation": "401은 '너 누군지 모르겠어, 로그인부터 해'(인증 실패), 403은 '너 누군지는 아는데 이건 네 권한 밖이야'(인가 실패)입니다. 이 경우 사용자는 이미 로그인해 인증은 통과했지만 글의 소유자가 아니므로, 소유권 검사(post.authorId !== req.user.id)에 걸려 403으로 거절하는 것이 맞습니다. 409는 이메일 중복 가입처럼 자원 상태 충돌에 쓰는 코드입니다."
  },
  {
    "courseSlug": "lv2-auth-session-hash",
    "question": "로그인 성공 직후 req.session.regenerate()로 세션ID를 새로 발급하는 이유는 무엇을 막기 위해서인가?",
    "option1": "공격자가 미리 심어 둔 세션ID가 로그인 상태로 승격되는 세션 고정(session fixation) 공격",
    "option2": "자주 쓰는 비밀번호를 기계적으로 대입해 보는 무차별 대입(brute force) 공격",
    "option3": "다른 사이트에서 유출된 계정 정보로 로그인을 시도하는 크리덴셜 스터핑",
    "option4": "악성 스크립트가 document.cookie로 쿠키를 읽어 가는 XSS 기반 탈취",
    "answerIndex": 0,
    "explanation": "세션 고정 공격은 공격자가 확보한 세션ID를 피해자 브라우저에 미리 심어 두고, 피해자가 그 ID를 단 채로 로그인하면 같은 ID를 아는 공격자도 로그인 상태가 되는 공격입니다. 로그인 성공 직후 세션ID를 새것으로 교체하면 공격자가 심어 둔 옛 ID는 무효가 됩니다. 무차별 대입은 시도 제한(429)으로, XSS 기반 쿠키 탈취는 httpOnly 옵션으로 각각 방어합니다."
  },
  {
    "courseSlug": "lv2-react-state-effects",
    "question": "장바구니 화면에서 items 배열이 이미 state로 있을 때, 합계 금액(totalPrice)은 어떻게 다루는 것이 가장 좋을까요?",
    "option1": "useState로 별도 state를 만들고 items를 바꿀 때마다 함께 갱신한다",
    "option2": "state로 두지 않고 렌더링 때마다 items에서 reduce로 계산한다",
    "option3": "useEffect에서 items가 바뀔 때마다 setTotalPrice로 동기화한다",
    "option4": "서버에 합계를 요청해서 받아 온다",
    "answerIndex": 1,
    "explanation": "totalPrice는 items에서 파생 가능한 값이므로 state가 아니라 계산으로 둡니다. 별도 state로 저장하면 items와 두 곳에 같은 사실이 존재하게 되어, 삭제 등에서 갱신을 깜빡하는 순간 화면 합계가 실제와 어긋나는 동기화 버그가 생깁니다. 렌더링마다 원본에서 다시 계산하면 어긋날 방법 자체가 없습니다."
  },
  {
    "courseSlug": "lv2-react-state-effects",
    "question": "user.level = 2; setUser(user); 처럼 객체 내부를 직접 수정한 뒤 같은 객체를 set 함수에 넘기면 화면이 갱신되지 않습니다. 그 이유는 무엇일까요?",
    "option1": "직접 수정하면 자바스크립트 엔진이 에러를 던져 렌더링이 중단되기 때문",
    "option2": "React가 객체 내부 값을 전부 비교하는 데 시간이 오래 걸려 갱신을 미루기 때문",
    "option3": "React는 이전 값과 새 값을 참조(같은 객체인지)로만 비교하는데, 같은 객체를 넘기면 변경이 없다고 판단하기 때문",
    "option4": "setUser는 숫자와 문자열만 받을 수 있고 객체는 받을 수 없기 때문",
    "answerIndex": 2,
    "explanation": "React는 성능을 위해 내용물을 뒤져 보지 않고 '이전 객체 === 새 객체'라는 참조 비교로 변경을 감지합니다. 내부 값을 고쳐도 같은 상자(참조)를 다시 건네면 React는 이전과 같다고 보고 리렌더링을 건너뜁니다. 그래서 스프레드 등으로 새 객체를 만들어 교체해야 합니다."
  },
  {
    "courseSlug": "lv2-react-state-effects",
    "question": "검색창(SearchBar)과 글 목록(PostList)이 형제 컴포넌트로서 검색어 상태를 공유해야 할 때, 상태의 주인은 어디에 두어야 할까요?",
    "option1": "SearchBar 안에 두고 PostList가 SearchBar를 직접 참조하게 한다",
    "option2": "두 컴포넌트를 모두 품는 가장 가까운 공통 부모에 두고, 값은 props로, 변경은 콜백으로 내려 준다",
    "option3": "PostList 안에 두고 SearchBar로 복사본 state를 하나 더 만든다",
    "option4": "가능한 한 최상위 컴포넌트(App)에 두는 것이 항상 정답이다",
    "answerIndex": 1,
    "explanation": "React의 데이터는 부모에서 자식으로 한 방향으로만 흐르므로 형제끼리 직접 대화할 수 없습니다. 그래서 상태를 두 형제가 함께 쓰는 최소 공통 부모로 끌어올리고, 부모가 값(props)과 바꿀 권한(콜백)을 내려 줍니다. 필요 이상으로 높이 올리면 관계없는 컴포넌트까지 리렌더링되므로 '가장 가까운' 공통 부모가 기준입니다."
  },
  {
    "courseSlug": "lv2-react-state-effects",
    "question": "useEffect(() => { ... }, [query]) 에서 클린업 함수가 실행되는 정확한 시점으로 옳은 것은?",
    "option1": "컴포넌트가 언마운트될 때 단 한 번만 실행된다",
    "option2": "이펙트 본문이 실행되기 직전에 매번 먼저 실행된다(첫 실행 포함)",
    "option3": "컴포넌트가 언마운트될 때, 그리고 query가 바뀌어 이펙트가 다시 실행되기 직전에 실행된다",
    "option4": "setState가 호출될 때마다 실행된다",
    "answerIndex": 2,
    "explanation": "클린업은 두 시점에 실행됩니다. 컴포넌트가 화면에서 사라질 때(언마운트), 그리고 의존성이 바뀌어 새 이펙트가 실행되기 직전(이전 이펙트의 클린업이 먼저)입니다. 첫 렌더링에는 치울 이전 이펙트가 없으므로 실행되지 않습니다. 이 '새 이펙트 전에 이전 클린업 먼저' 순서 덕분에 타이머·구독이 중복으로 쌓이지 않고, 데이터 페칭의 ignore 깃발 패턴도 이 순서에 기대어 동작합니다."
  },
  {
    "courseSlug": "lv2-react-state-effects",
    "question": "검색어를 빠르게 연달아 입력해 fetch 요청이 여러 개 나갔을 때, 페칭 이펙트에 ignore 깃발(let ignore = false; 클린업에서 ignore = true)을 두는 이유는 무엇일까요?",
    "option1": "요청을 하나로 합쳐서 서버 부하를 줄이기 위해",
    "option2": "응답이 출발 순서대로 도착한다는 보장이 없어, 늦게 도착한 옛 요청의 결과가 최신 화면을 덮어쓰는 레이스 컨디션을 막기 위해",
    "option3": "fetch가 실패했을 때 자동으로 재시도하기 위해",
    "option4": "로딩 스피너가 깜빡이는 것을 막기 위해",
    "answerIndex": 1,
    "explanation": "네트워크 응답은 출발 순서대로 도착한다는 보장이 없습니다. 방어 없이 setPosts를 하면 늦게 도착한 옛 검색어의 결과가 최신 결과를 덮어써서 검색어와 목록이 어긋납니다. query가 바뀌면 이전 이펙트의 클린업이 먼저 실행되어 이전 요청의 ignore가 true가 되고, 늦게 도착한 응답은 if (!ignore) 문턱에서 걸러져 항상 최신 요청만 반영됩니다. 언마운트 후 setState 경고도 같은 깃발로 함께 해결됩니다."
  },
  {
    "courseSlug": "lv2-react-routing",
    "question": "SPA 내부에서 페이지를 이동할 때 <a href> 대신 <Link>를 써야 하는 가장 큰 이유는 무엇인가요?",
    "option1": "<a> 태그는 보안상 취약해서 브라우저가 차단하기 때문에",
    "option2": "<a>는 서버에 새 HTML 문서를 요청해 앱 전체가 새로고침되고 상태가 사라지기 때문에",
    "option3": "<Link>가 <a>보다 렌더링 속도가 항상 2배 이상 빠르기 때문에",
    "option4": "<a> 태그는 React 컴포넌트 안에서 문법 오류를 일으키기 때문에",
    "answerIndex": 1,
    "explanation": "<a href>는 브라우저의 기본 동작인 '서버에 새 문서 요청'을 일으켜 앱 전체가 새로고침되고, 입력 중이던 값 등 메모리의 상태가 전부 사라집니다. <Link>는 History API로 주소만 바꾸고 라우터가 화면을 교체하므로 새로고침 없이 상태가 유지됩니다."
  },
  {
    "courseSlug": "lv2-react-routing",
    "question": "라우트를 <Route path=\"/posts/:id\" ... />로 선언했을 때, 컴포넌트 안에서 :id 자리에 들어온 값을 꺼내는 방법과 그 값의 타입으로 옳은 것은?",
    "option1": "useParams()로 꺼내며, 값은 항상 문자열이다",
    "option2": "useParams()로 꺼내며, 값은 항상 숫자다",
    "option3": "useSearchParams()로 꺼내며, 값은 항상 문자열이다",
    "option4": "props.id로 자동 전달되며, 값은 항상 숫자다",
    "answerIndex": 0,
    "explanation": "경로 파라미터는 useParams()로 꺼내며, /posts/42로 접근해도 값은 숫자 42가 아니라 문자열 \"42\"입니다. 그래서 숫자 비교가 필요하면 Number(id)로 변환해야 하며, 이를 잊으면 id === 42가 조용히 false가 되는 버그가 생깁니다. useSearchParams는 ?keyword= 같은 쿼리스트링용입니다."
  },
  {
    "courseSlug": "lv2-react-routing",
    "question": "중첩 라우트에서 <Outlet />의 역할로 옳은 것은 무엇인가요?",
    "option1": "현재 주소와 일치하는 메뉴를 강조 표시한다",
    "option2": "존재하지 않는 주소로 접근했을 때 404 화면을 그린다",
    "option3": "부모 레이아웃 안에서 자식 라우트가 그려질 자리를 표시한다",
    "option4": "로그인하지 않은 사용자를 로그인 페이지로 돌려보낸다",
    "answerIndex": 2,
    "explanation": "Outlet은 레이아웃 라우트(부모) 안에서 자식 라우트의 화면이 끼워질 '구멍'입니다. 액자(공통 헤더·푸터)는 그대로 두고 그림(자식 페이지)만 갈아 끼우는 구조라서, 페이지를 오가도 헤더가 다시 그려지지 않고 그 안의 상태도 유지됩니다. 메뉴 강조는 NavLink, 404는 와일드카드(*), 튕겨내기는 ProtectedRoute의 역할입니다."
  },
  {
    "courseSlug": "lv2-react-routing",
    "question": "검색어처럼 어떤 상태를 useState가 아니라 URL 쿼리스트링(useSearchParams)에 두는 판단 기준으로 본문이 제시한 것은?",
    "option1": "값이 문자열이면 URL에, 숫자면 useState에 둔다",
    "option2": "그 상태가 담긴 링크를 남에게 보냈을 때 의미가 있으면 URL에 둔다",
    "option3": "서버 API가 사용하는 값이면 무조건 useState에 둔다",
    "option4": "자주 바뀌는 값은 URL에, 거의 안 바뀌는 값은 useState에 둔다",
    "answerIndex": 1,
    "explanation": "URL은 공유·북마크가 가능한 상태 저장소이므로, '이 링크를 남에게 보냈을 때 같은 화면이 재현되어야 의미 있는 상태'(검색어·페이지 번호·정렬 등)는 URL에 싣습니다. 반면 입력 중인 초안이나 모달 열림 여부처럼 남에게 보내도 의미 없는 상태는 useState로 충분합니다. 새로고침·링크 공유에도 결과가 유지되는 것이 URL에 두는 이점입니다."
  },
  {
    "courseSlug": "lv2-react-routing",
    "question": "ProtectedRoute가 미로그인 사용자를 로그인 페이지로 보낼 때 state에 원래 위치(from)를 담아 보내는 이유는 무엇인가요?",
    "option1": "로그인 페이지가 어떤 화면에서 왔는지 서버에 로그로 남기기 위해",
    "option2": "뒤로 가기 버튼을 완전히 비활성화하기 위해",
    "option3": "프론트 검사만으로 서버 인증을 대체할 수 있게 하기 위해",
    "option4": "로그인 성공 직후 사용자를 원래 가려던 페이지로 되돌려 보내기 위해",
    "answerIndex": 3,
    "explanation": "글쓰기(/write)에 가려다 막힌 사용자의 원래 목적지를 state.from에 담아 보내면, 로그인 페이지에서 로그인 성공 시 location.state?.from?.pathname을 읽어 navigate(from, { replace: true })로 원래 가려던 곳에 바로 데려다줄 수 있습니다. 홈으로 던져 놓는 것보다 훨씬 좋은 사용자 경험이며, 프론트 보호는 UX일 뿐 실제 보안은 서버의 토큰 검증이 담당합니다."
  },
  {
    "courseSlug": "lv2-form-validation",
    "question": "프론트엔드에서 이미 검증을 했는데도 서버가 같은 규칙을 반드시 다시 검사해야 하는 가장 근본적인 이유는 무엇인가요?",
    "option1": "서버 검증이 프론트 검증보다 실행 속도가 훨씬 빠르기 때문에",
    "option2": "브라우저마다 React 검증 코드가 다르게 동작하기 때문에",
    "option3": "개발자도구 콘솔의 fetch 등으로 폼과 프론트 검증을 아예 거치지 않고 서버에 직접 요청을 보낼 수 있기 때문에",
    "option4": "프론트 검증은 정규식을 사용할 수 없기 때문에",
    "answerIndex": 2,
    "explanation": "서버는 요청이 우리 폼에서 왔는지 알 수 없습니다. 개발자도구에서 fetch 한 줄이면 프론트 검증을 한 글자도 거치지 않고 서버에 직접 요청할 수 있으므로, 프론트 검증은 '건너뛸 수 있는 안내판'일 뿐이고 서버 검증이 '건너뛸 수 없는 검문소' 역할을 해야 합니다. 프론트 검증은 UX 담당, 서버 검증은 보안 담당입니다."
  },
  {
    "courseSlug": "lv2-form-validation",
    "question": "제어 컴포넌트로 여러 타입의 입력을 핸들러 하나로 처리할 때, 체크박스에서 값을 읽는 올바른 방법은 무엇인가요?",
    "option1": "e.target.value가 아니라 e.target.checked(true/false)를 읽어야 한다",
    "option2": "체크박스도 텍스트 입력과 똑같이 e.target.value를 읽으면 된다",
    "option3": "체크박스는 제어 컴포넌트로 만들 수 없어서 DOM에서 직접 읽어야 한다",
    "option4": "체크박스는 onChange 대신 onBlur에서만 값을 읽을 수 있다",
    "answerIndex": 0,
    "explanation": "입력 타입마다 값이 담기는 자리가 다릅니다. 텍스트·셀렉트는 value에 값이 담기지만, 체크박스는 checked(불리언)를 읽어야 합니다. 이를 놓치고 value를 읽으면 체크 여부와 무관한 이상한 문자열이 상태에 잡히므로, 핸들러에서 type === 'checkbox'일 때만 checked를 읽도록 분기합니다."
  },
  {
    "courseSlug": "lv2-form-validation",
    "question": "'비밀번호 확인' 필드는 필수·길이·정규식 같은 필드별 규칙 표(RULES)만으로는 검증할 수 없습니다. 그 이유는 무엇인가요?",
    "option1": "비밀번호는 보안상 프론트에서 검증하면 안 되기 때문에",
    "option2": "정규식이 비밀번호 문자를 처리하지 못하기 때문에",
    "option3": "비밀번호 확인 필드는 blur 이벤트가 발생하지 않기 때문에",
    "option4": "올바른 값이 고정된 규칙이 아니라 다른 필드(비밀번호)의 현재 값에 달려 있는 교차 검증이기 때문에",
    "answerIndex": 3,
    "explanation": "필드별 규칙 표는 '이 필드 하나의 값'만 보고 판정합니다. 그런데 비밀번호 확인의 정답은 비밀번호 필드에 지금 무엇이 입력됐는지에 따라 달라지므로, 필드 하나가 아니라 폼 전체를 받아서 필드 사이를 건너 비교하는 교차 검증(cross-field validation) 단계에서 따로 처리해야 합니다."
  },
  {
    "courseSlug": "lv2-form-validation",
    "question": "이메일 형식은 완벽한데 이미 가입된 이메일이라 회원가입이 거절되는 상황에 어울리는 HTTP 상태코드와 그 이유는 무엇인가요?",
    "option1": "400 — 입력값이 검증 규칙을 위반했기 때문에",
    "option2": "409 — 이미 존재하는 데이터와 부딪히는 충돌이며, DB를 가진 서버만 알 수 있는 에러이기 때문에",
    "option3": "500 — 서버 내부에서 처리 중 오류가 발생했기 때문에",
    "option4": "404 — 해당 이메일의 사용자를 찾을 수 없기 때문에",
    "answerIndex": 1,
    "explanation": "이메일 중복은 입력 형식(규칙)의 문제가 아니라 '이미 있는 것과 부딪히는' 상황이므로 400이 아닌 409 Conflict를 쓰는 관례가 있습니다. 형식이 아무리 완벽해도 중복 여부는 DB를 가진 서버만 알 수 있는, 프론트가 미리 잡을 수 없는 에러이며, 프론트는 이를 받아 '로그인해 보시겠어요?'처럼 다음 행동을 제안하는 문구로 안내합니다."
  },
  {
    "courseSlug": "lv2-form-validation",
    "question": "회원가입 제출이 서버 검증에서 실패(400)로 돌아왔을 때, 프론트가 지켜야 할 원칙으로 옳은 것은 무엇인가요?",
    "option1": "보안을 위해 즉시 폼을 초기화해서 모든 입력값을 비운다",
    "option2": "제출 버튼을 영구히 비활성화해서 재시도를 막는다",
    "option3": "입력값은 그대로 보존하고, 필드별 에러만 각 입력칸 아래에 표시한다",
    "option4": "네트워크 실패와 똑같이 '연결이 불안정해요' 문구를 폼 상단에 띄운다",
    "answerIndex": 2,
    "explanation": "실패했을 때 폼을 비우면 닉네임 하나 때문에 거절당한 사용자가 이메일·비밀번호까지 처음부터 다시 입력해야 합니다. 실패 시 입력값 보존이 원칙이고, 400 응답의 errors 객체를 에러 상태에 부어 각 입력칸 아래에 필드별로 표시합니다. 네트워크 실패는 사용자가 고칠 수 있는 문제가 아니므로 검증 실패와 구분해 다른 문구로 안내해야 합니다."
  },
  {
    "courseSlug": "lv2-error-handling-patterns",
    "question": "사용자가 존재하지 않는 글 번호로 조회를 요청해서 에러가 발생했습니다. 강좌의 분류에 따르면 이 에러는 어떻게 다루는 것이 옳을까요?",
    "option1": "버그이므로 즉시 서버를 재시작하고 코드를 수정한다",
    "option2": "예상된 실패이므로 404와 함께 무엇이 잘못됐는지 사용자에게 알려 준다",
    "option3": "500 상태코드와 범용 메시지로 감춘다",
    "option4": "catch에서 아무 처리도 하지 않고 넘어간다",
    "answerIndex": 1,
    "explanation": "없는 리소스 조회는 코드가 멀쩡해도 정상 운영 중 매일 일어나는 '예상된 실패(operational error)'입니다. 이런 에러는 사용자가 스스로 상황을 이해하고 고칠 수 있으므로 404 같은 적절한 상태코드와 친절한 설명이 답입니다. 500+범용 메시지로 감추는 처방은 예상 밖의 버그에만 해당하고, 에러를 삼키는 것(4번)은 어떤 경우에도 최악입니다."
  },
  {
    "courseSlug": "lv2-error-handling-patterns",
    "question": "async 함수 안에서 try/catch로 감쌌는데도 fetchPosts()의 실패가 catch에 잡히지 않고 콘솔에 'Unhandled promise rejection'이 뜹니다. 가장 유력한 원인은 무엇일까요?",
    "option1": "fetchPosts() 호출 앞에 await를 빼먹어서 에러가 try 블록이 끝난 뒤에 터졌기 때문",
    "option2": "try/catch는 async 함수 안에서는 원래 동작하지 않기 때문",
    "option3": "catch 블록에 인자를 선언하지 않았기 때문",
    "option4": "finally 블록이 없어서 catch가 무시되었기 때문",
    "answerIndex": 0,
    "explanation": "try/catch는 '지금 이 자리에서 터진' 에러만 잡습니다. await가 없으면 함수는 Promise의 결과를 기다리지 않고 지나가 버리고, 실패는 try 블록이 이미 끝난 뒤 catch의 손이 닿지 않는 곳에서 터집니다. await는 성공이든 에러든 결과를 '이 자리로 끌어오는' 역할이라, await를 붙이면 catch가 정상적으로 잡습니다. try/catch 자체는 async 함수 안에서도 잘 동작합니다."
  },
  {
    "courseSlug": "lv2-error-handling-patterns",
    "question": "Express에서 어떤 미들웨어가 '에러 처리 담당'으로 인식되는 기준은 무엇일까요?",
    "option1": "app.use가 아닌 app.error로 등록한 미들웨어",
    "option2": "파일 이름이 error.js인 미들웨어",
    "option3": "인자를 4개(err, req, res, next) 받는 미들웨어",
    "option4": "가장 먼저 등록된 미들웨어",
    "answerIndex": 2,
    "explanation": "Express는 미들웨어 함수의 인자 개수를 보고 역할을 구분합니다. 인자를 4개(err, req, res, next) 받으면 에러 처리 미들웨어로 인식하고, 어느 라우트에서든 throw되거나 next(err)로 넘긴 에러를 전부 그리로 배달합니다. 등록 위치는 모든 라우트 '뒤'여야 마지막 그물 역할을 할 수 있는데, 이는 동작 조건이지 인식 기준은 아닙니다."
  },
  {
    "courseSlug": "lv2-error-handling-patterns",
    "question": "예상 밖의 에러(버그)가 발생했을 때 스택 트레이스를 API 응답에 담아 보내면 안 되는 핵심 이유는 무엇일까요?",
    "option1": "스택 트레이스는 용량이 커서 네트워크 비용이 많이 들기 때문",
    "option2": "서버의 파일 경로·폴더 구조·라이브러리 버전 같은 내부 정보가 공격자에게 노출되기 때문",
    "option3": "JSON으로 직렬화할 수 없는 형식이기 때문",
    "option4": "브라우저가 스택 트레이스를 파싱하지 못해 화면이 깨지기 때문",
    "answerIndex": 1,
    "explanation": "스택 트레이스에는 서버 내부의 파일 경로, 폴더 구조, 사용 중인 라이브러리와 버전이 그대로 담깁니다. 이는 공격자에게 '어떤 취약점을 시도해 볼지' 고르게 해 주는 내부 지도를 무료로 배포하는 셈입니다. 그래서 예상 밖 에러는 사용자에겐 500 + 범용 메시지만 보내고, 스택 같은 진짜 내용은 서버 로그에만 남기는 것이 원칙입니다."
  },
  {
    "courseSlug": "lv2-error-handling-patterns",
    "question": "React 에러 바운더리(Error Boundary)에 대한 설명으로 옳은 것은 무엇일까요?",
    "option1": "fetch 요청 실패를 포함해 앱에서 발생하는 모든 에러를 잡아 준다",
    "option2": "반드시 앱 전체를 하나로 감싸야 효과가 있다",
    "option3": "자식 컴포넌트가 렌더링 중 던진 에러를 받아 폴백 화면을 대신 보여 주지만, 요청(fetch) 실패는 잡지 못한다",
    "option4": "에러를 잡으면 자동으로 해당 요청을 재시도한다",
    "answerIndex": 2,
    "explanation": "에러 바운더리는 자식 컴포넌트가 '렌더링 중' 던진 에러를 받아 화면 전체가 하얗게 비는 대신 폴백 UI를 세우는 안전망입니다. 이벤트 핸들러나 fetch 같은 비동기 코드의 에러는 범위 밖이라, 요청 실패에는 상태 기반의 '다시 시도' 폴백을 따로 만들어야 합니다. 또한 앱 전체가 아니라 글 목록·댓글 같은 영역 단위로 두르는 것이 '전부 죽거나 전부 살거나'를 피하는 설계입니다."
  },
  {
    "courseSlug": "lv2-practical-testing",
    "question": "코드를 고친 뒤 '고치지 않은 곳'이 몰래 깨지는 버그를 무엇이라고 부르나요?",
    "option1": "회귀(regression) 버그",
    "option2": "컴파일 에러",
    "option3": "메모리 누수",
    "option4": "경쟁 상태(race condition)",
    "answerIndex": 0,
    "explanation": "어제까지 멀쩡하던 기능이 다른 코드를 수정한 뒤 다시 깨지는 것을 회귀 버그라고 합니다. 사람은 고친 곳만 확인하기 쉽지만, 테스트는 고치지 않은 곳까지 기계가 매번 전부 다시 확인해 주기 때문에 회귀 버그를 잡는 안전망이 됩니다."
  },
  {
    "courseSlug": "lv2-practical-testing",
    "question": "단위 테스트의 뼈대인 AAA 패턴의 세 단계를 순서대로 올바르게 나열한 것은?",
    "option1": "실행(Act) → 준비(Arrange) → 검증(Assert)",
    "option2": "준비(Arrange) → 실행(Act) → 검증(Assert)",
    "option3": "검증(Assert) → 준비(Arrange) → 실행(Act)",
    "option4": "준비(Arrange) → 검증(Assert) → 실행(Act)",
    "answerIndex": 1,
    "explanation": "먼저 입력을 만들고(준비, Arrange), 대상 함수를 호출하고(실행, Act), 결과가 기대와 같은지 expect로 단언(검증, Assert)합니다. 이 순서가 뒤엉키면 테스트가 읽기 어려워지며, API 테스트든 컴포넌트 테스트든 모든 테스트가 이 뼈대를 공유합니다."
  },
  {
    "courseSlug": "lv2-practical-testing",
    "question": "supertest로 로그인 API를 테스트할 때, '비밀번호가 틀린 경우'에 확인해야 할 것으로 본문이 강조한 조합은?",
    "option1": "상태 코드 200과 토큰이 문자열인지",
    "option2": "상태 코드 404와 응답 헤더의 캐시 설정",
    "option3": "상태 코드 401과 토큰이 응답에 새어 나오지 않았는지",
    "option4": "상태 코드 409와 중복 가입 안내 문구",
    "answerIndex": 2,
    "explanation": "인증 실패는 401로 응답해야 하고, 실패했는데도 토큰이 응답 바디에 포함되면 보안 사고입니다. 그래서 '있어야 할 것'뿐 아니라 토큰이 undefined인지처럼 '없어야 할 것'을 확인하는 단언이 중요하다고 배웠습니다. 409는 중복 가입(Conflict)에 쓰는 코드입니다."
  },
  {
    "courseSlug": "lv2-practical-testing",
    "question": "테스트 하나만 골라 돌리면 실패하는데 전체를 순서대로 돌리면 통과한다면, 가장 가능성이 높은 원인은?",
    "option1": "Vitest의 감시 모드가 꺼져 있어서",
    "option2": "앞 테스트가 남긴 데이터에 의존하는 순서 의존이 생겨서",
    "option3": "커버리지가 100%에 도달하지 못해서",
    "option4": "테스트 이름을 '무엇이 어때야 한다' 형식으로 쓰지 않아서",
    "answerIndex": 1,
    "explanation": "테스트끼리 순서 의존이 생기면 앞 테스트가 만들어 둔 DB 상태에 기대게 되어, 혼자 돌리거나 순서를 바꾸면 결과가 달라집니다. 본문에서 이를 위험 신호로 규정했고, 해결책은 각 테스트 전에 초기화와 시드(beforeEach)로 항상 같은 출발점을 보장해 독립성을 확보하는 것입니다."
  },
  {
    "courseSlug": "lv2-practical-testing",
    "question": "Testing Library의 철학에 가장 잘 맞는 요소 찾기 방법은?",
    "option1": "CSS 클래스 이름으로 요소를 선택한다",
    "option2": "컴포넌트 내부 state 값을 직접 읽어 확인한다",
    "option3": "DOM 트리의 몇 번째 자식인지 인덱스로 접근한다",
    "option4": "getByRole로 역할과 보이는 이름, getByLabelText로 라벨을 통해 찾는다",
    "answerIndex": 3,
    "explanation": "Testing Library의 철학은 '사용자가 쓰는 방식대로 테스트하라'입니다. 사용자는 CSS 클래스나 내부 state를 모르고, '가입하기'라는 버튼과 '이메일'이라는 라벨을 보고 행동합니다. getByRole·getByLabelText로 찾으면 구현이 아닌 동작에 붙는 테스트가 되어 리팩터링에도 잘 깨지지 않습니다."
  },
  {
    "courseSlug": "lv2-mini-project-deploy",
    "question": "MVP(최소 기능 제품)를 자를 때 '이번엔 안 만든다' 목록이 중요한 이유로 이 강좌가 말한 것은?",
    "option1": "기능을 줄여야 코드 품질이 자동으로 좋아지기 때문에",
    "option2": "안 만드는 기능이 정해져야 '끝나는 날'을 지킬 수 있고, 미완성으로 끝나는 것을 막기 때문에",
    "option3": "안 만드는 기능 목록이 있어야 저작권 문제를 피할 수 있기 때문에",
    "option4": "포트폴리오에는 기능 개수가 적을수록 유리하기 때문에",
    "answerIndex": 1,
    "explanation": "MVP를 자르는 것은 기능을 포기하는 게 아니라 마감일을 지키는 행위입니다. [안 함]·[나중] 목록이 없으면 부가 기능에 시간을 쓰다 전부 미완성으로 끝나기 쉽고, 미완성 프로젝트는 포트폴리오가 되지 못한다고 강좌에서 강조했습니다."
  },
  {
    "courseSlug": "lv2-mini-project-deploy",
    "question": "프론트(localhost:5173)에서 백엔드(localhost:8080) API를 호출했더니 CORS 에러가 발생했다. 올바른 해결 방향은?",
    "option1": "프론트의 fetch 코드에 특별한 헤더를 추가해서 브라우저를 통과시킨다",
    "option2": "브라우저의 보안 설정을 꺼서 차단을 해제한다",
    "option3": "서버 쪽에서 프론트의 출처(origin)를 허용 목록에 등록한다",
    "option4": "프론트와 백엔드의 포트 번호를 같게 맞춘다",
    "answerIndex": 2,
    "explanation": "CORS는 버그가 아니라 브라우저의 보안 규칙으로, '다른 출처의 응답은 서버가 명시적으로 허용한 경우에만 JS에 전달한다'는 원칙입니다. 따라서 해결은 항상 서버 쪽에서 허용 출처를 등록하는 것이며(예: Spring의 @CrossOrigin), 프론트 코드를 고치는 것으로는 풀리지 않습니다."
  },
  {
    "courseSlug": "lv2-mini-project-deploy",
    "question": "DB 비밀번호 같은 시크릿을 실수로 git에 커밋했다가 다음 커밋에서 지웠다. 이 강좌가 말하는 올바른 수습은?",
    "option1": "지웠으므로 안전하다. 추가 조치는 필요 없다",
    "option2": ".gitignore에 .env를 추가하면 과거 커밋의 값도 함께 가려진다",
    "option3": "저장소를 비공개로 전환하면 유출이 없던 일이 된다",
    "option4": "유출로 간주하고 해당 비밀번호·키를 새 값으로 교체한다",
    "answerIndex": 3,
    "explanation": "git은 이력을 보존하는 도구라서 다음 커밋에서 지워도 과거 커밋에는 시크릿이 그대로 남습니다. 저장소를 본 적 있는 모든 사람이 그 값을 가졌다고 가정해야 하므로, 유일한 수습은 커밋 삭제가 아니라 비밀번호·키 자체를 새 값으로 교체하는 것입니다."
  },
  {
    "courseSlug": "lv2-mini-project-deploy",
    "question": "npm run dev(개발 모드)와 npm run build(프로덕션 빌드)의 차이에 대한 설명으로 옳은 것은?",
    "option1": "프로덕션 빌드는 파일을 압축·최적화하고 에러 메시지를 최소화한, 사용자에게 내보내기 위한 결과물이다",
    "option2": "개발 모드가 프로덕션 빌드보다 실행 성능이 더 좋다",
    "option3": "프로덕션 빌드는 에러 메시지를 더 길고 친절하게 보여 준다",
    "option4": "두 모드는 같은 결과물을 만들며 실행 명령어만 다르다",
    "answerIndex": 0,
    "explanation": "개발 모드는 '나를 위한 모드'로 소스를 그대로 쓰고 친절한 에러와 즉시 새로고침을 제공하는 대신 무겁습니다. 프로덕션 빌드는 '사용자를 위한 모드'로 압축·난독화와 파일 지문(hash)을 적용하고 내부 노출을 막기 위해 에러 메시지를 최소화합니다. 그래서 배포 전 npm run preview로 빌드 결과물을 확인해야 합니다."
  },
  {
    "courseSlug": "lv2-mini-project-deploy",
    "question": "\"내 컴퓨터에선 됐는데 배포하니 안 돼요\"의 원인 유형으로 이 강좌가 꼽은 것이 아닌 것은?",
    "option1": "환경 차이 — 내 PC에만 있는 설정이나 운영 .env(환경변수) 누락",
    "option2": "데이터 차이 — 실서버 DB는 비어 있어서 빈 배열에서 터지는 코드",
    "option3": "주소·경로 차이 — 하드코딩된 localhost, http/https 혼용, 새로고침 404",
    "option4": "언어 차이 — 프론트와 백엔드를 서로 다른 프로그래밍 언어로 만들어서 생기는 충돌",
    "answerIndex": 3,
    "explanation": "강좌는 원인 유형을 ① 환경 차이(파일·설정·환경변수), ② 데이터 차이(빈 DB·글 0개), ③ 주소·경로 차이(localhost 하드코딩, http/https 혼용, SPA 새로고침 404)로 정리했습니다. 프론트와 백엔드가 다른 언어인 것은 정상적인 구조(React+Spring 등)이지 문제의 원인이 아닙니다. 공통점은 코드가 아니라 코드를 둘러싼 환경이 문제라는 점입니다."
  },
  {
    "courseSlug": "lv3-layered-architecture",
    "question": "레이어드 아키텍처에서 계층을 나누는 가장 근본적인 이유는 무엇인가요?",
    "option1": "코드 실행 속도가 계층 수에 비례해 빨라지기 때문에",
    "option2": "서로 다른 이유로 바뀌는 코드를 분리해, 변경이 한 계층 안에 갇히도록 하기 위해",
    "option3": "파일 수를 늘려 여러 개발자가 커밋 충돌 없이 작업하게 하기 위해",
    "option4": "프레임워크(Spring)가 계층 구조 없이는 동작하지 않기 때문에",
    "answerIndex": 1,
    "explanation": "관심사 분리의 본질은 '서로 다른 이유로 바뀌는 코드는 서로 다른 자리에 둔다'입니다. URL 개편·정책 변경·DB 교체는 각각 바뀌는 이유가 다른데, 한 곳에 섞여 있으면 하나가 바뀔 때마다 전부를 다시 읽고 테스트해야 합니다. 계층은 그 변경을 한 칸 안에 가두는 담장이지, 성능이나 프레임워크 요구사항 때문이 아닙니다."
  },
  {
    "courseSlug": "lv3-layered-architecture",
    "question": "'얇은 컨트롤러' 원칙에 따를 때, 컨트롤러가 해도 되는 일은 무엇인가요?",
    "option1": "관심종목이 30개를 넘었는지 한도를 판정하는 것",
    "option2": "Repository를 직접 호출해 데이터를 조회하는 것",
    "option3": "HTTP 요청을 자바 객체로 번역하고 서비스에 위임하는 것",
    "option4": "JPA 엔티티를 그대로 JSON 응답으로 반환하는 것",
    "answerIndex": 2,
    "explanation": "얇은 컨트롤러는 '번역과 위임' 딱 두 가지만 합니다. 한도 판정 같은 비즈니스 규칙은 서비스의 일이고, Repository 직접 호출은 규칙 계층을 건너뛰는 지름길이며, 엔티티 직접 반환은 DB 내부 사정(숨겨야 할 필드, 지연로딩)을 API로 노출시키므로 모두 금지 목록에 해당합니다."
  },
  {
    "courseSlug": "lv3-layered-architecture",
    "question": "계층 간 의존성 방향이 '바깥(web) → 안쪽(domain)' 한 방향이어야 하는 이유로 옳은 것은?",
    "option1": "자주 바뀌는 HTTP 쪽이 천천히 바뀌는 규칙 쪽에 의존해야, 바깥이 바뀌어도 안쪽 규칙이 흔들리지 않기 때문",
    "option2": "안쪽 계층의 코드가 바깥 계층보다 먼저 컴파일되어야 하기 때문",
    "option3": "web 패키지의 클래스 수가 domain보다 항상 많기 때문",
    "option4": "양방향 의존은 스프링에서 컴파일 자체가 불가능하기 때문",
    "answerIndex": 0,
    "explanation": "핵심은 변경 속도의 차이입니다. URL·필드명 같은 HTTP 스펙은 자주 바뀌고 업무 규칙은 천천히 바뀝니다. 자주 바뀌는 쪽이 천천히 바뀌는 쪽에 의존해야 API가 아무리 출렁여도 규칙 코드는 무사합니다. 반대 방향 의존이 생기면 필드명 하나 바꾸는 데 규칙까지 수술해야 합니다. 컴파일 순서나 클래스 수와는 무관합니다."
  },
  {
    "courseSlug": "lv3-layered-architecture",
    "question": "빈약한 도메인 모델(Anemic Domain Model)의 문제점을 가장 잘 설명한 것은?",
    "option1": "엔티티에 메서드가 많아져 클래스가 비대해진다",
    "option2": "getter/setter만 있는 데이터 봉지가 되어 규칙이 서비스 여기저기에 흩어지고, setter로 규칙을 우회할 문이 열린다",
    "option3": "도메인 객체가 HTTP 타입에 직접 의존하게 된다",
    "option4": "record를 쓸 수 없어 DTO 작성이 불가능해진다",
    "answerIndex": 1,
    "explanation": "빈약한 모델은 엔티티가 getter/setter 덩어리가 되고 규칙이 전부 서비스로 흩어지는 상태입니다. setter가 열려 있으면 '보유 종목 100개 제한' 같은 규칙은 지켜 주길 바라는 부탁일 뿐이고, 같은 검증이 여러 서비스에 복사되는 병이 생깁니다. 행동 메서드 안에 규칙을 두면 우회할 문이 없어 규칙이 한 곳에서 강제됩니다."
  },
  {
    "courseSlug": "lv3-layered-architecture",
    "question": "traderbox가 관심사 분리를 마이크로서비스 수준으로 확장한 방식으로 옳지 않은 것은?",
    "option1": "서비스마다 자기 스키마만 사용하는 스키마-퍼-서비스로 데이터를 격리한다",
    "option2": "동기 호출은 /internal + 공유키, 비동기·감사는 Kafka, 실시간 팬아웃은 Valkey Pub/Sub로 채널을 나눈다",
    "option3": "게이트웨이가 URL 규칙으로 요청을 각 서비스에 라우팅해 서비스 자율성을 지킨다",
    "option4": "안정성을 위해 같은 데이터를 Kafka와 Valkey 두 채널에 모두 흘려 이중화한다",
    "answerIndex": 3,
    "explanation": "채널 규율의 핵심은 오히려 '같은 데이터를 두 채널로 흘리지 않는다'입니다. 용도별로 채널을 하나만 정해야(동기=/internal, 비동기·감사=Kafka, 실시간=Valkey) 데이터 흐름의 관심사가 분리됩니다. 두 채널에 이중으로 흘리면 어느 쪽이 진실인지 모호해지는 숨은 결합이 생깁니다. 나머지 셋은 본문에서 설명한 실제 규칙입니다."
  },
  {
    "courseSlug": "lv3-design-patterns",
    "question": "market 서비스의 시세 프로바이더 분기를 전략(Strategy) 패턴으로 재설계했을 때, 새 프로바이더(예: 일본 주식)를 추가하는 비용으로 가장 정확한 것은?",
    "option1": "모든 분기(if/switch)가 있는 메서드를 찾아 else-if를 하나씩 추가한다",
    "option2": "@Component가 붙은 구현 클래스 하나만 새로 작성하면 되고, 기존 파일 수정은 0개다",
    "option3": "QuoteService의 생성자를 수정해 새 프로바이더를 수동으로 등록해야 한다",
    "option4": "인터페이스에 새 시장용 메서드를 추가하고 모든 구현체를 수정해야 한다",
    "answerIndex": 1,
    "explanation": "전략 패턴 도입 후에는 스프링이 같은 인터페이스(QuoteProvider)의 빈을 List로 자동 주입해 주므로, @Component 클래스 하나만 추가하면 자동으로 합류합니다. 기존 코드를 수정하지 않고 확장할 수 있다는 것은 개방 폐쇄 원칙(OCP)의 실현이며, 기존 코드를 안 건드리므로 기존 기능이 깨질 위험도 사라집니다. Before(if 분기) 방식에서는 분기가 복붙된 곳을 전부 수색해 고쳐야 하고 하나라도 빠뜨리면 런타임 버그가 됩니다."
  },
  {
    "courseSlug": "lv3-design-patterns",
    "question": "같은 클래스 안에서 this.method()로 내부 호출을 하면 @Transactional이 적용되지 않는 이유는 무엇인가?",
    "option1": "내부 호출은 프록시(대리인) 객체를 거치지 않고 진짜 객체로 직행하기 때문",
    "option2": "@Transactional은 컨트롤러 계층에서만 동작하도록 설계되었기 때문",
    "option3": "내부 호출 시 스프링 컨테이너가 새 인스턴스를 생성해 트랜잭션 컨텍스트가 초기화되기 때문",
    "option4": "this 키워드가 자바에서 트랜잭션 전파를 차단하는 예약어이기 때문",
    "answerIndex": 0,
    "explanation": "스프링의 @Transactional은 프록시 패턴으로 구현됩니다. 스프링은 해당 클래스를 감싼 대리인(프록시) 객체를 빈으로 등록하고, 외부 호출이 프록시를 거칠 때 트랜잭션 시작·커밋·롤백을 앞뒤에 끼워 넣습니다. 그런데 같은 클래스 안에서 this로 부르는 내부 호출은 프록시를 우회해 진짜 객체의 메서드로 바로 가므로, 트랜잭션 처리가 끼어들 기회가 없습니다. 프록시 구조를 이해하면 이 함정이 '마법의 버그'가 아니라 당연한 결과임을 알 수 있습니다."
  },
  {
    "courseSlug": "lv3-design-patterns",
    "question": "어댑터·데코레이터·퍼사드는 셋 다 '감싸는' 패턴이라 헷갈리기 쉽다. 각 패턴의 목적을 올바르게 짝지은 것은?",
    "option1": "어댑터=기능 추가, 데코레이터=인터페이스 번역, 퍼사드=접근 제어",
    "option2": "어댑터=여러 개를 하나로 묶기, 데코레이터=모양 바꾸기, 퍼사드=기능 겹겹이 입히기",
    "option3": "어댑터=모양(인터페이스) 번역, 데코레이터=같은 인터페이스로 기능 추가, 퍼사드=복잡한 하위 시스템에 단순한 창구 제공",
    "option4": "셋 모두 목적이 동일하며 이름만 다르다",
    "answerIndex": 2,
    "explanation": "구분 기준은 '왜 감싸는가'입니다. 어댑터는 외부 API처럼 모양이 안 맞는 것을 우리 표준 인터페이스로 번역하기 위해(예: 키움 브리지의 부호 접두어 가격을 Quote로 변환), 데코레이터는 같은 인터페이스를 유지한 채 캐시·로깅 같은 기능을 겹겹이 더하기 위해, 퍼사드는 여러 하위 시스템 호출 절차를 창구 하나 뒤로 숨겨 단순하게 보이기 위해 감쌉니다. 목적이 다르므로 적용할 상황도 다릅니다."
  },
  {
    "courseSlug": "lv3-design-patterns",
    "question": "스프링 환경에서 싱글턴 패턴에 대한 설명으로 옳은 것은?",
    "option1": "스프링에서는 getInstance()를 가진 교과서식 싱글턴 클래스를 직접 작성하는 것이 권장된다",
    "option2": "@Service·@Component 빈은 기본 스코프가 싱글턴이므로, 직접 싱글턴 코드를 짜지 않아도 인스턴스 공유의 이득을 이미 누리고 있다",
    "option3": "스프링 빈은 요청마다 새 인스턴스가 생성되므로 싱글턴이 필요하면 별도 구현이 필수다",
    "option4": "직접 만든 정적(static) 싱글턴이 스프링 빈보다 테스트에서 교체하기 쉽다",
    "answerIndex": 1,
    "explanation": "스프링 빈의 기본 스코프는 싱글턴이라, 컨테이너가 인스턴스 하나를 만들어 필요한 곳에 주입해 줍니다. 즉 JwtService나 MarketBus 같은 @Service/@Component 클래스는 이미 '컨테이너가 관리하는 싱글턴'입니다. 직접 만든 정적 싱글턴은 정적 결합 때문에 테스트에서 가짜(Mock)로 갈아 끼우기 어렵지만, 스프링 빈은 주입 구조 덕에 교체가 쉽습니다. 교훈은 '프레임워크가 이미 준 패턴을 손으로 다시 만들지 말 것'입니다."
  },
  {
    "courseSlug": "lv3-design-patterns",
    "question": "패턴 도입과 리팩터링의 타이밍에 대한 설명으로 이 강좌의 내용과 일치하는 것은?",
    "option1": "패턴은 많이 쓸수록 좋은 코드이므로, 케이스가 2개뿐이어도 미리 전략 패턴을 깔아 두는 것이 바람직하다",
    "option2": "리팩터링은 구조를 바꾸는 작업이므로 테스트는 구조 변경이 끝난 뒤에 작성하는 것이 효율적이다",
    "option3": "구현체가 하나뿐인 인터페이스라도 선배가 만든 구조라면 걷어내지 않고 유지해야 한다",
    "option4": "\"언젠가 필요할지도 몰라서\" 미리 추상화를 쌓는 것(YAGNI 위반)이 가장 위험한 신호이며, 패턴 도입 전에 현재 동작을 고정하는 테스트를 먼저 만들어야 한다",
    "answerIndex": 3,
    "explanation": "패턴은 목적이 아니라 도구이므로, 실제 코드 냄새(같은 분기의 복붙, 수정 공포 등)가 나타났을 때 도입해야 합니다. 아직 오지 않은 요구사항을 위한 추상화는 YAGNI(You Aren't Gonna Need It) 위반으로, 읽기 비용과 헛된 탐색만 늘립니다. 또한 패턴 도입은 '동작은 그대로, 구조만 바꾸기'이므로, 도입 전 테스트로 현재 동작을 고정하고 변경 후 같은 테스트가 통과함을 확인하는 전/후 초록불이 유일한 안전망입니다. 과잉 추상화를 발견하면 걷어내는 것도 리팩터링입니다."
  },
  {
    "courseSlug": "lv3-db-indexing",
    "question": "EXPLAIN과 EXPLAIN ANALYZE의 차이로 옳은 것은?",
    "option1": "EXPLAIN ANALYZE는 계획만 보여 주고, EXPLAIN은 실제로 실행한다",
    "option2": "EXPLAIN은 플래너의 계획과 추정치만 보여 주고, EXPLAIN ANALYZE는 쿼리를 실제로 실행해 실측 시간과 실제 행 수까지 보여 준다",
    "option3": "둘은 완전히 같고 출력 형식만 다르다",
    "option4": "EXPLAIN ANALYZE는 SELECT에만 사용할 수 있어 UPDATE에는 붙일 수 없다",
    "answerIndex": 1,
    "explanation": "EXPLAIN은 실행하지 않고 플래너의 계획(cost·추정 rows)만 보여 주는 반면, ANALYZE를 붙이면 쿼리를 실제로 실행해 actual time·actual rows 같은 실측치를 함께 보여 줍니다. 그래서 성능 진단은 추정과 실측을 나란히 비교하는 것이 기본기이고, 같은 이유로 UPDATE·DELETE에 EXPLAIN ANALYZE를 붙이면 데이터가 정말 바뀌므로 BEGIN → ROLLBACK으로 감싸야 합니다."
  },
  {
    "courseSlug": "lv3-db-indexing",
    "question": "복합 인덱스 (user_id, created_at)이 걸려 있을 때, 이 인덱스를 활용하지 못하는 조건은?",
    "option1": "WHERE user_id = 7 AND created_at > '2026-07-01'",
    "option2": "WHERE user_id = 7",
    "option3": "WHERE created_at > '2026-07-01' (user_id 조건 없이)",
    "option4": "WHERE user_id = 7 ORDER BY created_at",
    "answerIndex": 2,
    "explanation": "복합 인덱스는 전화번호부가 (성, 이름) 순으로 정렬된 것과 같아서, 앞 컬럼(user_id) 없이 뒤 컬럼(created_at)만으로는 정렬 트리를 탈 수 없습니다. 앞 컬럼만 쓰거나 앞+뒤를 함께 쓰는 조건은 인덱스를 활용할 수 있지만, 뒤 컬럼 단독 조건은 이 인덱스를 쓰지 못합니다."
  },
  {
    "courseSlug": "lv3-db-indexing",
    "question": "인덱스가 걸린 컬럼인데도 인덱스를 타지 못하는 쿼리 패턴은?",
    "option1": "WHERE email = 'kim@awesomedev.dev'",
    "option2": "WHERE title LIKE '보안%' (전방 일치)",
    "option3": "WHERE lower(email) = 'kim@awesomedev.dev' (일반 email 인덱스만 있는 경우)",
    "option4": "WHERE created_at >= '2026-07-01' (범위 조건)",
    "answerIndex": 2,
    "explanation": "인덱스는 원본 컬럼값 그대로 정렬돼 있으므로, lower()처럼 컬럼을 함수로 감싸는 순간 그 정렬표를 쓸 수 없게 됩니다. 꼭 필요하면 lower(email)에 대한 함수 기반 인덱스를 따로 만들어야 합니다. 반면 등호 비교와 범위 조건은 B-Tree 인덱스가 그대로 처리합니다. (전방 일치 LIKE('보안%')도 원리는 범위 검색이지만, Postgres 기본 로케일에선 text_pattern_ops 인덱스를 따로 만들어야 탑니다.)"
  },
  {
    "courseSlug": "lv3-db-indexing",
    "question": "EXPLAIN ANALYZE 결과에서 플래너의 추정 rows와 actual rows가 수십 배 어긋날 때, 가장 먼저 시도할 처방은?",
    "option1": "인덱스를 하나 더 추가한다",
    "option2": "ANALYZE 테이블명; 으로 통계 정보를 갱신한다",
    "option3": "테이블을 삭제하고 다시 만든다",
    "option4": "쿼리에 LIMIT을 붙여 행 수를 줄인다",
    "answerIndex": 1,
    "explanation": "플래너는 테이블의 통계 정보(값 분포 표본)로 행 수를 추정해 계획을 고르는데, 대량 INSERT·DELETE 직후엔 이 통계가 현실과 어긋나 오판이 생깁니다. 추정과 실측이 크게 어긋나는 것은 인덱스 부족이 아니라 통계가 낡았다는 신호이므로, ANALYZE 한 줄로 통계를 갱신하는 것이 가장 싸고 효과적인 첫 처방입니다."
  },
  {
    "courseSlug": "lv3-db-indexing",
    "question": "인덱스 추가가 오히려 서비스를 느리게 만들 수 있는 이유로 옳은 것은?",
    "option1": "인덱스가 많을수록 SELECT가 항상 느려지기 때문",
    "option2": "인덱스는 읽기 전용이라 쓰기와 무관하기 때문",
    "option3": "INSERT/UPDATE/DELETE 때마다 인덱스도 함께 갱신해야 해서 쓰기가 많은 테이블에선 쓰기 처리량이 떨어질 수 있기 때문",
    "option4": "인덱스를 만들면 기존 데이터가 모두 삭제되기 때문",
    "answerIndex": 2,
    "explanation": "인덱스는 공짜가 아닙니다. 인덱스가 5개 걸린 테이블에 INSERT 한 번은 사실상 쓰기 6번(본문 1 + 인덱스 5)이 됩니다. 쓰기가 압도적으로 많은 테이블에 조회용 인덱스를 여럿 얹으면 쓰기 처리량이 주저앉을 수 있어요. 그래서 개선 전후 비교표에 읽기 시간뿐 아니라 쓰기 영향과 인덱스 크기도 함께 기록해야 온전한 측정입니다."
  },
  {
    "courseSlug": "lv3-transaction-isolation",
    "question": "잔고 100만원인 계좌에 80만원짜리 매수 요청 두 개가 동시에 들어와 둘 다 '잔고 충분'으로 판단하고 차감해 잔고가 -60만원이 되었다. 이 사고의 근본 원인은 무엇인가요?",
    "option1": "매수 금액 계산 로직에 오버플로 버그가 있어서",
    "option2": "'읽고 → 판단하고 → 쓰는' 구간 사이의 빈틈에 다른 트랜잭션이 끼어들었기 때문",
    "option3": "커밋된 데이터가 디스크에 저장되지 않아 지속성이 깨졌기 때문",
    "option4": "@Transactional이 붙어 있으면 항상 발생하는 정상 동작이라서",
    "answerIndex": 1,
    "explanation": "코드 자체에는 버그가 없어도, 두 트랜잭션이 각각 '잔고 확인(읽기)'과 '차감(쓰기)' 사이의 시간 틈에 서로 끼어들면 둘 다 옛 잔고를 근거로 판단하게 됩니다. 이런 동시성 사고는 요청을 하나씩 보내는 테스트로는 재현되지 않고, 격리 수준이나 잠금(FOR UPDATE, 낙관적 락) 같은 동시성 제어로 막아야 합니다."
  },
  {
    "courseSlug": "lv3-transaction-isolation",
    "question": "PostgreSQL의 기본 격리 수준과, 그 수준에서 여전히 발생할 수 있는 이상 현상의 조합으로 올바른 것은?",
    "option1": "READ UNCOMMITTED — 더티 리드가 발생할 수 있다",
    "option2": "READ COMMITTED — 논-리피터블 리드가 발생할 수 있다",
    "option3": "REPEATABLE READ — 더티 리드가 발생할 수 있다",
    "option4": "SERIALIZABLE — 팬텀 리드가 발생할 수 있다",
    "answerIndex": 1,
    "explanation": "Postgres의 기본값은 READ COMMITTED입니다. 이 수준은 커밋되지 않은 값을 읽는 더티 리드는 차단하지만, 같은 트랜잭션 안에서 같은 행을 두 번 읽었을 때 값이 달라지는 논-리피터블 리드(그리고 팬텀 리드)는 허용합니다. 더티 리드만 막아도 대부분의 조회에 충분히 안전하면서 동시성 비용이 가장 낮기 때문에 기본값으로 채택된 것입니다."
  },
  {
    "courseSlug": "lv3-transaction-isolation",
    "question": "JPA 엔티티에 @Version 컬럼을 두는 낙관적 락에 대한 설명으로 옳은 것은?",
    "option1": "SELECT 시점에 해당 행에 배타락을 걸어 다른 트랜잭션의 접근을 즉시 차단한다",
    "option2": "UPDATE 시 WHERE 조건에 읽었을 때의 버전을 포함시켜, 그 사이 누가 먼저 고쳤으면 갱신이 실패하고 예외가 발생한다",
    "option3": "충돌이 매우 잦은 데이터(예: 재고 1개짜리 상품)에 가장 적합한 방식이다",
    "option4": "테이블 전체를 잠그기 때문에 행 잠금보다 동시성이 높다",
    "answerIndex": 1,
    "explanation": "낙관적 락은 DB 잠금을 걸지 않고, UPDATE ... WHERE id = ? AND version = ? 형태로 저장 시점에 버전을 검사합니다. 그 사이 다른 트랜잭션이 먼저 수정했다면 버전이 달라져 0건 갱신이 되고 OptimisticLockException이 발생해 재시도하거나 사용자에게 알리게 됩니다. 잠그지 않는 방식이므로 충돌이 드문 데이터에 유리하고, 충돌이 잦다면 미리 잠그는 비관적 락이 더 쌉니다."
  },
  {
    "courseSlug": "lv3-transaction-isolation",
    "question": "트랜잭션 A는 1번 계좌를 잠근 뒤 2번을 기다리고, B는 2번을 잠근 뒤 1번을 기다리는 데드락이 반복된다. 이를 '원천적으로' 예방하는 가장 좋은 방법은?",
    "option1": "격리 수준을 READ UNCOMMITTED로 낮춰 잠금을 없앤다",
    "option2": "여러 자원을 잠글 때는 항상 같은 순서(예: id 오름차순)로 잠그는 규칙을 팀 전체에 적용한다",
    "option3": "데드락이 나면 서버를 재시작하도록 모니터링을 건다",
    "option4": "재시도 시 대기 시간 없이 즉시 같은 트랜잭션을 다시 실행한다",
    "answerIndex": 1,
    "explanation": "데드락은 두 트랜잭션이 서로 엇갈린 순서로 자원을 잡을 때 생기므로, '항상 같은 순서로 잠근다'는 규칙 하나면 서로가 서로를 기다리는 구조 자체가 만들어질 수 없습니다. 타임아웃과 재시도는 어디까지나 보조 안전망이고, 재시도할 때는 같은 충돌이 반복되지 않도록 짧은 무작위 대기를 섞는 것이 좋습니다. 데드락은 한 코드의 버그가 아니라 두 코드 경로의 조합에서 나오므로 규칙으로 막는 것이 맞습니다."
  },
  {
    "courseSlug": "lv3-transaction-isolation",
    "question": "같은 @Service 클래스 안에서 buyAll()이 this.buy()를 호출하는데, buy()에 붙은 @Transactional이 동작하지 않는다. 이유는 무엇인가요?",
    "option1": "스프링 트랜잭션은 프록시가 호출을 가로채 적용되는데, this로 부르는 내부 호출은 프록시를 거치지 않기 때문",
    "option2": "@Transactional은 반복문 안에서 호출된 메서드에는 적용되지 않도록 설계되었기 때문",
    "option3": "buy() 메서드가 public이어서 트랜잭션 프록시가 접근할 수 없기 때문",
    "option4": "readOnly 속성을 지정하지 않으면 @Transactional이 무시되기 때문",
    "answerIndex": 0,
    "explanation": "스프링의 @Transactional은 마법이 아니라 프록시(대리인) 객체가 메서드 호출의 앞뒤에서 BEGIN/COMMIT을 감싸 주는 방식입니다. 외부에서 빈을 통해 들어온 호출만 프록시를 통과하고, this.buy() 같은 self-invocation은 대리인을 건너뛰고 본체를 직접 부르므로 트랜잭션이 적용되지 않습니다. 해법은 트랜잭션이 필요한 메서드를 별도의 빈으로 분리해 주입받아 호출하는 것입니다."
  },
  {
    "courseSlug": "lv3-query-optimization",
    "question": "게시글 20건짜리 목록을 열었더니 SQL 로그에 select가 21번 찍혔습니다. 이 'N+1 문제'가 발생하는 직접적인 원인은 무엇인가요?",
    "option1": "DB 인덱스가 없어서 매 행마다 풀스캔이 일어나기 때문",
    "option2": "지연 로딩(LAZY)된 연관을 반복문에서 행마다 접근할 때, 그때마다 추가 쿼리가 나가기 때문",
    "option3": "JPA가 버그로 같은 쿼리를 중복 실행하기 때문",
    "option4": "트랜잭션이 readOnly로 선언되지 않았기 때문",
    "answerIndex": 1,
    "explanation": "N+1은 버그가 아니라 지연 로딩이라는 정상 동작의 부작용입니다. LAZY 연관은 '실제로 쓸 때' 가져오는데, 목록을 반복문으로 돌며 행마다 연관(예: 작성자)에 접근하면 그 순간마다 select가 1번씩 나가서 목록 1번 + 행 수 N번의 쿼리가 됩니다. 인덱스·readOnly는 별개의 문제입니다."
  },
  {
    "courseSlug": "lv3-query-optimization",
    "question": "컬렉션(OneToMany) 연관을 fetch join하면서 동시에 페이징(Pageable)을 걸면 어떤 일이 벌어지나요?",
    "option1": "하이버네이트가 자동으로 batch_size를 적용해 문제없이 동작한다",
    "option2": "컴파일 에러가 발생해 애초에 실행되지 않는다",
    "option3": "DB에서 limit을 걸지 못해 전체 데이터를 메모리로 가져온 뒤 애플리케이션에서 잘라, 데이터가 크면 메모리 장애로 이어진다",
    "option4": "count 쿼리만 두 번 실행될 뿐 성능에는 영향이 없다",
    "answerIndex": 2,
    "explanation": "컬렉션을 조인하면 부모 행이 자식 수만큼 불어나 DB에서 limit을 정확히 걸 수 없습니다. 그래서 하이버네이트는 'applying in memory!' 경고를 찍고 전체 결과를 메모리로 퍼 올린 뒤 애플리케이션에서 자릅니다. 실행은 되지만(에러 아님) 데이터가 크면 OOM으로 직행하므로, 컬렉션의 페이징은 batch_size로 푸는 것이 정석입니다."
  },
  {
    "courseSlug": "lv3-query-optimization",
    "question": "목록 화면에는 제목·작성자·작성일 3개 컬럼만 표시하는데 API가 느립니다. '필요한 만큼만 가져오기' 관점에서 가장 적절한 처방은?",
    "option1": "연관을 전부 FetchType.EAGER로 바꿔 미리 로딩해 둔다",
    "option2": "select 절에 화면이 쓰는 컬럼만 명시해 DTO로 바로 받는 프로젝션 조회로 바꾼다",
    "option3": "엔티티를 그대로 조회하되 트랜잭션을 없앤다",
    "option4": "본문(TEXT) 컬럼에 인덱스를 추가한다",
    "answerIndex": 1,
    "explanation": "프로젝션(DTO 조회)은 select 절에 필요한 컬럼만 명시하므로 무거운 본문(TEXT)이나 안 쓰는 연관이 아예 실려 오지 않고, 연관을 '로딩'하는 게 아니라 값만 '조회'하므로 N+1도 구조적으로 생기지 않습니다. EAGER는 그 연관을 안 쓰는 화면에서까지 항상 가져오게 만들어 해법이 아니라 함정입니다."
  },
  {
    "courseSlug": "lv3-query-optimization",
    "question": "\"이 게시글에 댓글이 하나라도 있는가?\"만 확인하면 되는 상황입니다. count(*) 대신 EXISTS(또는 existsBy 메서드)를 써야 하는 이유는?",
    "option1": "count(*)는 조건에 맞는 행을 끝까지 다 세지만, EXISTS는 첫 행을 찾는 순간 멈추기 때문",
    "option2": "count(*)는 트랜잭션 안에서만 실행할 수 있기 때문",
    "option3": "EXISTS는 결과를 자동으로 캐시에 저장해 주기 때문",
    "option4": "count(*)는 N+1 문제를 반드시 일으키기 때문",
    "answerIndex": 0,
    "explanation": "존재 여부만 궁금할 때 count(*)를 쓰면 DB는 댓글이 10만 개든 조건에 맞는 행을 전부 세고 나서야 답합니다. 반면 EXISTS는 조건을 만족하는 첫 행을 발견하는 즉시 탐색을 멈추므로 훨씬 저렴합니다. 캐시 저장이나 트랜잭션 제약과는 무관하며, count 자체가 N+1을 일으키는 것도 아닙니다."
  },
  {
    "courseSlug": "lv3-query-optimization",
    "question": "N+1을 fetch join으로 고친 뒤 '쿼리 카운트 회귀 테스트'(assertSelectCount 등)를 남겨 두는 가장 중요한 이유는 무엇인가요?",
    "option1": "테스트가 있으면 쿼리 실행 속도 자체가 빨라지기 때문",
    "option2": "회귀 테스트가 없으면 fetch join 문법이 컴파일되지 않기 때문",
    "option3": "운영 DB의 느린 쿼리 로그를 대체할 수 있기 때문",
    "option4": "나중에 누군가 코드를 수정하며 지연 로딩을 건드려 N+1이 재발했을 때, 배포 후 장애가 아니라 CI 단계에서 먼저 잡아내기 위해",
    "answerIndex": 3,
    "explanation": "N+1은 고쳐도 다시 생깁니다 — 몇 달 뒤 DTO에 필드를 추가하며 무심코 연관 접근을 넣는 식으로요. '이 API는 쿼리 2번을 넘지 않는다'를 테스트로 박아 두면 재발이 운영 장애가 아니라 CI의 빨간불로 먼저 드러납니다. 테스트가 쿼리를 빠르게 해 주는 것은 아니며, DB 측 느린 쿼리 로그와는 역할이 다른 보완 관계입니다."
  },
  {
    "courseSlug": "lv3-caching-strategy",
    "question": "히트율이 낮은 캐시가 '오히려 손해'인 이유로 가장 정확한 것은?",
    "option1": "캐시 서버는 히트율이 낮으면 자동으로 꺼지기 때문에",
    "option2": "미스가 많으면 요청마다 캐시 확인 + DB 조회 + 캐시 적재라는 추가 단계를 밟아, 캐시 없이 DB만 보던 때보다 일이 늘어나기 때문에",
    "option3": "히트율이 낮으면 DB의 데이터가 자동으로 삭제되기 때문에",
    "option4": "히트율이 낮으면 TTL이 무한대로 늘어나기 때문에",
    "answerIndex": 1,
    "explanation": "캐시는 히트일 때만 이득을 봅니다. 미스가 나면 캐시 확인이라는 헛걸음에 더해 DB 조회와 캐시 적재까지 세 단계를 밟으므로, 히트율이 낮으면 요청당 평균 작업량이 캐시 없던 시절보다 늘어납니다. 여기에 무효화 코드의 복잡도와 불일치 위험이라는 유지비까지 더해지므로, 히트율 낮은 캐시는 떼는 것이 나을 수 있습니다."
  },
  {
    "courseSlug": "lv3-caching-strategy",
    "question": "우리 플랫폼의 시세 캐시 market:quote가 로컬(인메모리) 캐시가 아니라 분산 캐시(Valkey)에 사는 핵심 이유는?",
    "option1": "로컬 캐시보다 Valkey의 응답 속도가 더 빠르기 때문에",
    "option2": "Valkey는 TTL 기능이 없어서 데이터가 영원히 보존되기 때문에",
    "option3": "시세는 수집기가 쓰고 여러 API 서버가 읽는 데이터라, 모든 인스턴스가 같은 최신 값을 봐야 하기 때문에",
    "option4": "Valkey에 두면 DB 조회가 완전히 사라져 DB를 없앨 수 있기 때문에",
    "answerIndex": 2,
    "explanation": "속도만 보면 네트워크 왕복이 없는 로컬 캐시(나노초)가 Valkey(약 1ms)보다 빠릅니다. 그런데도 분산 캐시를 쓰는 이유는 공유 때문입니다. 시세는 수집기가 갱신하고 여러 API 서버가 읽는데, 로컬 캐시로는 서버마다 다른 값을 들게 되고 수집기가 각 서버의 메모리에 값을 넣어 줄 방법도 없습니다. 쓰는 쪽과 읽는 쪽이 다르고 읽는 쪽이 여럿이면 분산 캐시가 답입니다."
  },
  {
    "courseSlug": "lv3-caching-strategy",
    "question": "쓰기 시점의 캐시 처리에서 '갱신(SET 새 값)'보다 '삭제(DEL)'가 기본 선택으로 권장되는 이유는?",
    "option1": "삭제가 갱신보다 다음 읽기 요청을 항상 더 빠르게 만들기 때문에",
    "option2": "거의 동시에 쓰는 두 요청이 엇갈리면 갱신은 낡은 값이 캐시에 남을 수 있지만, 삭제는 그 경합 자체가 없고 값을 만드는 책임이 읽기 경로 하나로 통일되기 때문에",
    "option3": "Redis에는 SET 명령이 없고 DEL 명령만 있기 때문에",
    "option4": "삭제하면 DB의 원본 데이터도 함께 정리되기 때문에",
    "answerIndex": 1,
    "explanation": "갱신은 요청 A와 B가 거의 동시에 쓸 때 DB에는 나중 값이 남았는데 캐시에는 늦게 도착한 이전 값이 눌러앉는 경합 위험이 있습니다. 삭제는 캐시를 비우기만 하므로 이 경합이 원천적으로 없고, 값을 다시 계산하는 주체가 읽기 경로 하나로 모여 코드도 단순해집니다. 다음 읽기 한 번이 미스가 되는 건 삭제의 대가이지 장점이 아니므로, 갱신은 계산이 아주 비싸고 쓰기 경합이 없다고 확신할 때만 선택합니다."
  },
  {
    "courseSlug": "lv3-caching-strategy",
    "question": "Cache-Aside(지연 로딩)가 캐싱 패턴의 '기본형'으로 꼽히는 이유가 아닌 것은?",
    "option1": "캐시 서버가 통째로 재시작돼도 한동안 전부 미스가 될 뿐, 서비스는 틀리지 않고 계속 돈다",
    "option2": "실제로 읽힌 데이터만 캐시에 올라가 메모리가 수요를 따라간다",
    "option3": "캐시에만 쓰고 DB에는 나중에 몰아서 반영하므로 쓰기 속도가 매우 빠르다",
    "option4": "DB 쓰기 코드가 캐시의 존재를 몰라도 되고, 삭제 한 줄만 얹으면 된다",
    "answerIndex": 2,
    "explanation": "3번은 Cache-Aside가 아니라 Write-Behind(Write-Back)의 설명입니다. Write-Behind는 쓰기가 빠른 대신 캐시가 죽으면 아직 DB로 옮기지 못한 쓰기가 유실되는 무거운 위험을 집니다. Cache-Aside는 읽기 경로에서 캐시를 확인하고 미스면 DB에서 읽어 채우는 패턴으로, 실패에 강하고(전부 미스여도 동작), 읽힌 것만 캐시하며, 쓰기 코드와의 결합이 약하다는 점이 기본형으로 꼽히는 이유입니다."
  },
  {
    "courseSlug": "lv3-caching-strategy",
    "question": "캐시 스탬피드(인기 키 만료 순간 동시 미스 폭주)에 대한 방어로 본문에서 소개한 방법의 공통 원리는?",
    "option1": "캐시 메모리를 늘려서 키가 절대 만료되지 않게 한다",
    "option2": "모든 요청을 DB로 직접 보내 캐시 의존을 없앤다",
    "option3": "TTL을 전부 0으로 설정해 캐시를 항상 비워 둔다",
    "option4": "락으로 한 요청만 DB에 가게 하거나 확률적 조기 만료로 갱신 시점을 흩어, '동시에' 일어나는 일을 없앤다",
    "answerIndex": 3,
    "explanation": "스탬피드의 본질은 미스와 갱신이 '동시에' 몰리는 것입니다. 락(setnx)은 미스가 나도 한 요청만 DB에 가고 나머지는 잠깐 기다렸다가 채워진 캐시를 읽게 하고, 확률적 조기 만료는 남은 TTL이 짧을수록 높은 확률로 미리 갱신을 자원하게 해 갱신 시점을 자연스럽게 분산시킵니다. TTL 지터도 같은 원리입니다. 세 방어 모두 동시성을 시간 축으로 펼쳐 놓는 설계입니다."
  },
  {
    "courseSlug": "lv3-performance-profiling",
    "question": "응답 시간 지표로 평균 대신 p95·p99 같은 백분위를 쓰는 가장 큰 이유는 무엇인가요?",
    "option1": "평균은 계산 비용이 커서 서버에 부담을 주기 때문에",
    "option2": "평균은 소수의 아주 느린 요청을 다수의 빠른 요청 속에 숨겨 버리기 때문에",
    "option3": "백분위는 에러율까지 한 번에 보여 주기 때문에",
    "option4": "평균은 부하 테스트 도구에서 측정할 수 없기 때문에",
    "answerIndex": 1,
    "explanation": "평균은 대부분 50ms인데 일부만 2,000ms인 경우에도 147ms처럼 '괜찮아 보이는' 값이 나와 느린 꼬리를 가립니다. 사용자가 체감하는 '느리다'는 평균이 아니라 꼬리(p95·p99)에서 오기 때문에, 95%·99%의 요청이 끝나는 시간을 직접 보는 백분위가 실태를 정직하게 보여 줍니다."
  },
  {
    "courseSlug": "lv3-performance-profiling",
    "question": "k6 부하 테스트 시나리오에서 본 측정 전에 웜업(warm-up) 단계를 두는 이유는 무엇인가요?",
    "option1": "JIT 컴파일과 캐시가 덥혀지기 전에 재면 실제보다 느린 왜곡된 숫자가 나오기 때문에",
    "option2": "웜업 없이 시작하면 k6가 스크립트 오류를 감지하지 못하기 때문에",
    "option3": "웜업 구간의 p95가 곧 최종 측정 결과로 쓰이기 때문에",
    "option4": "가상 사용자(VU)를 만들려면 최소 30초의 준비 시간이 필수이기 때문에",
    "answerIndex": 0,
    "explanation": "JVM은 JIT 컴파일러가 자주 쓰는 코드를 기계어로 최적화한 뒤에야 제 속도가 나고, 캐시도 처음엔 비어 있습니다. 웜업 없이 곧바로 재면 이 '덥혀지기 전' 상태가 섞여 실제 운영 성능보다 나쁜 숫자가 나오므로, 가벼운 웜업 후 안정 상태(지속 구간)에서 측정해야 합니다."
  },
  {
    "courseSlug": "lv3-performance-profiling",
    "question": "커넥션 풀이나 스레드 풀의 포화가 CPU 프로파일(불꽃 그래프)에 잘 잡히지 않는 이유는 무엇인가요?",
    "option1": "풀 대기는 커널 영역에서 일어나 JVM 프로파일러가 접근할 수 없기 때문에",
    "option2": "async-profiler가 스레드 정보를 수집하지 않기 때문에",
    "option3": "기다리는 스레드는 CPU를 쓰지 않아 샘플링에 조용하게 나타나기 때문에",
    "option4": "풀 포화는 항상 에러를 발생시켜 프로파일링 전에 서버가 먼저 죽기 때문에",
    "answerIndex": 2,
    "explanation": "커넥션이 나올 때까지 줄 서 있는 스레드는 CPU 연산을 하지 않으므로 CPU 샘플링 기반의 불꽃 그래프에서는 넓게 나타나지 않습니다. 그래서 코드는 멀쩡한데 p95만 치솟는 이 유형은 풀의 활성 커넥션 수·대기 시간 같은 지표를 따로 봐야 발견할 수 있습니다."
  },
  {
    "courseSlug": "lv3-performance-profiling",
    "question": "GC 로그에서 'Pause Full ... 1843.7ms' 같은 긴 Full GC가 자주 보일 때, 이것이 서비스 성능에 미치는 영향으로 옳은 것은?",
    "option1": "백그라운드에서만 돌기 때문에 요청 처리에는 영향이 없다",
    "option2": "stop-the-world로 모든 애플리케이션 스레드가 멈춰, 그 순간의 요청들이 통째로 지연된다",
    "option3": "메모리가 정리되므로 오히려 그 직후 p95가 항상 좋아진다",
    "option4": "CPU 사용률만 올라가고 응답 시간에는 변화가 없다",
    "answerIndex": 1,
    "explanation": "Full GC의 stop-the-world 구간에서는 수거가 끝날 때까지 모든 애플리케이션 스레드가 정지하므로, 그 시간 동안 들어온 요청 전부가 그만큼 지연됩니다. 'p99만 이상하게 튀는' 현상의 단골 원인이며, 잦고 길다면 힙 크기·과도한 객체 할당·메모리 누수를 순서대로 의심해야 합니다."
  },
  {
    "courseSlug": "lv3-performance-profiling",
    "question": "Micrometer의 세 계측기 중 '현재 버퍼에 쌓인 항목 수'처럼 오르내리는 순간값을 보고 싶을 때 알맞은 것은?",
    "option1": "게이지(Gauge)",
    "option2": "카운터(Counter)",
    "option3": "타이머(Timer)",
    "option4": "히스토그램 전용 카운터",
    "answerIndex": 0,
    "explanation": "게이지는 '지금 이 순간'의 값을 들여다보는 창으로, 버퍼 크기나 활성 연결 수처럼 오르내리는 값에 씁니다. 카운터는 총 에러 수처럼 늘어나기만 하는 누적값, 타이머는 횟수에 더해 소요 시간 분포(p95·p99)까지 기록하는 용도입니다 — '몇 번?'은 카운터, '얼마나 걸렸나?'는 타이머, '지금 얼마나?'는 게이지."
  },
  {
    "courseSlug": "lv3-web-security",
    "question": "PreparedStatement(파라미터 바인딩)가 SQL 인젝션을 막는 근본 원리는 무엇인가요?",
    "option1": "사용자 입력에서 따옴표 같은 위험한 문자를 자동으로 삭제하기 때문",
    "option2": "쿼리의 구조를 먼저 확정하고 입력은 값 자리에만 끼워 넣어, 입력이 문법(코드)을 바꿀 수 없기 때문",
    "option3": "쿼리를 암호화해서 전송하므로 공격자가 내용을 볼 수 없기 때문",
    "option4": "DB 계정의 권한을 자동으로 최소화해 주기 때문",
    "answerIndex": 1,
    "explanation": "인젝션의 본질은 입력이 데이터가 아니라 코드로 해석되는 것입니다. PreparedStatement는 쿼리의 구조(명령)를 먼저 확정한 뒤 입력을 값 자리에만 바인딩하므로, 어떤 교묘한 입력도 문법을 바꿀 수 없습니다. 위험 문자를 지우는 블랙리스트 필터링은 우회 기법이 많아 방어가 되지 못합니다."
  },
  {
    "courseSlug": "lv3-web-security",
    "question": "CSRF 공격이 성립하는 핵심 조건은 무엇인가요?",
    "option1": "서버가 HTTPS를 사용하지 않아 요청이 도청될 수 있다",
    "option2": "프론트엔드가 사용자 입력을 이스케이프하지 않고 화면에 그린다",
    "option3": "브라우저가 대상 사이트로 가는 요청에 쿠키를 자동으로 붙여 보내므로, 다른 사이트발 위조 요청도 로그인 사용자의 요청처럼 보인다",
    "option4": "비밀번호가 평문으로 데이터베이스에 저장되어 있다",
    "answerIndex": 2,
    "explanation": "CSRF의 범인은 '쿠키 자동 전송'입니다. 로그인한 사용자가 공격자의 페이지를 열면, 그 페이지가 우리 서버로 보내는 요청에도 세션 쿠키가 자동으로 실려 가서 서버는 정상 요청과 구분하지 못합니다. 그래서 SameSite 쿠키, CSRF 토큰, Origin 검증을 겹쳐 방어합니다. 도청(HTTPS)이나 이스케이프(XSS), 평문 저장(암호화 실패)은 다른 유형의 문제입니다."
  },
  {
    "courseSlug": "lv3-web-security",
    "question": "저장형(Stored) XSS가 세 종류 중 가장 파괴적이라고 평가되는 이유는 무엇인가요?",
    "option1": "공격 스크립트가 서버(DB)에 저장되어, 해당 콘텐츠를 보는 모든 사용자의 브라우저에서 실행되기 때문",
    "option2": "서버를 거치지 않고 프론트엔드 자바스크립트 안에서만 실행되기 때문",
    "option3": "피해자가 조작된 링크를 클릭해야만 성립하기 때문",
    "option4": "SQL 데이터베이스의 관리자 권한을 즉시 탈취하기 때문",
    "answerIndex": 0,
    "explanation": "저장형 XSS는 게시글·채팅·닉네임 같은 곳에 공격 스크립트가 DB에 저장되어, 그 콘텐츠를 열람하는 모든 사용자에게 반복 실행됩니다. 링크 클릭이 필요한 것은 반사형, 서버와 무관하게 프론트 JS가 스스로 실행시키는 것은 DOM 기반 XSS의 특징입니다. XSS는 브라우저에서 실행되는 공격이지 DB 관리자 권한 탈취가 아닙니다."
  },
  {
    "courseSlug": "lv3-web-security",
    "question": "IDOR(Insecure Direct Object Reference) 취약점의 올바른 방어 방법은 무엇인가요?",
    "option1": "프론트엔드에서 남의 자원으로 가는 버튼과 링크를 화면에서 숨긴다",
    "option2": "자원 ID를 추측하기 어려운 긴 난수로만 바꾼다",
    "option3": "로그인(인증)만 통과하면 모든 자원 조회를 허용한다",
    "option4": "서버가 모든 요청마다 '이 요청자가 이 자원의 소유자(권한자)인가'를 조회 조건에 포함해 검증한다",
    "answerIndex": 3,
    "explanation": "IDOR는 인증은 통과했지만 인가(소유권 검증)를 빠뜨린 접근 통제 결함입니다. 공격자는 화면이 아니라 API를 직접 두드리므로 프론트에서 숨기는 것은 UX일 뿐 보안이 아니며, 인증만으로는 남의 자원 접근(수평 권한 상승)을 막지 못합니다. findByIdAndUserId처럼 서버측에서 매 요청 소유권을 함께 확인하는 것이 정답입니다."
  },
  {
    "courseSlug": "lv3-web-security",
    "question": "심층 방어(defense in depth) 관점에서 CSP(Content-Security-Policy) 헤더의 역할로 가장 알맞은 것은?",
    "option1": "출력 인코딩을 대체하므로 CSP만 설정하면 이스케이프는 필요 없다",
    "option2": "출력 인코딩을 실수로 빠뜨린 곳이 있어도, 허용되지 않은 출처의 스크립트 실행을 막아 주는 마지막 방어선 역할을 한다",
    "option3": "SQL 인젝션을 데이터베이스 계층에서 차단한다",
    "option4": "쿠키가 다른 사이트로 자동 전송되는 것을 막아 CSRF를 방어한다",
    "answerIndex": 1,
    "explanation": "심층 방어는 한 층의 실수가 전체 붕괴로 이어지지 않게 방어층을 겹치는 것입니다. XSS 방어의 1층은 프레임워크 자동 이스케이프 같은 출력 인코딩이고, CSP는 인코딩이 뚫렸을 때도 허용 안 된 출처의 스크립트 실행을 차단하는 마지막 방어선입니다. 대체재가 아니라 보완층이며, SQL 인젝션은 파라미터 바인딩, 쿠키 자동 전송 차단은 SameSite 속성의 역할입니다."
  },
  {
    "courseSlug": "lv3-auth-architecture",
    "question": "HTTP 상태 코드 401과 403의 차이를 가장 정확히 설명한 것은?",
    "option1": "401은 서버 오류, 403은 클라이언트 오류다",
    "option2": "401은 인증 실패(누구인지 확인 불가), 403은 인가 실패(신원은 알지만 권한 없음)다",
    "option3": "401은 토큰 만료, 403은 토큰 위조를 뜻한다",
    "option4": "둘은 같은 의미이며 서버 구현에 따라 골라 쓴다",
    "answerIndex": 1,
    "explanation": "인증(Authentication)은 '너 누구야?'라는 신원 확인이고, 인가(Authorization)는 '너 이거 해도 돼?'라는 권한 판단입니다. 401 Unauthorized는 신원 확인에 실패했을 때, 403 Forbidden은 신원은 확인됐지만 권한이 없을 때 반환됩니다. 누구인지 모르면 권한을 따질 수 없으므로 항상 인증이 인가보다 먼저입니다."
  },
  {
    "courseSlug": "lv3-auth-architecture",
    "question": "JWT가 세션 방식과 비교해 갖는 대표적인 '대가'는 무엇인가?",
    "option1": "서버를 여러 대로 늘리면 검증이 불가능해진다",
    "option2": "매 요청마다 세션 저장소를 조회해야 해서 느리다",
    "option3": "한번 발급한 토큰을 만료 전에 서버가 무효화할 방법이 없다",
    "option4": "페이로드가 암호화되어 있어 서버도 내용을 읽을 수 없다",
    "answerIndex": 2,
    "explanation": "JWT는 서버가 아무것도 기억하지 않는 무상태 방식이라, 저장소 조회 없이 서명 검증만으로 처리되고 서버 확장이 쉽습니다. 하지만 바로 그 '기억하지 않음' 때문에 발급된 토큰을 만료 전에 취소할 수 없습니다(유출돼도 만료까지 유효). 그래서 수명을 짧게 잡고 리프레시 토큰으로 보완합니다. 참고로 페이로드는 암호화가 아니라 Base64 인코딩이라 누구나 읽을 수 있습니다."
  },
  {
    "courseSlug": "lv3-auth-architecture",
    "question": "리프레시 토큰 회전(rotation)에서, 이미 폐기된 옛 리프레시 토큰이 다시 들어오는 상황이 의미하는 것은?",
    "option1": "정상적인 재로그인 절차이므로 새 토큰을 발급하면 된다",
    "option2": "액세스 토큰이 만료되었다는 신호다",
    "option3": "토큰이 도난당했을 가능성이 높다는 신호이므로 해당 사용자의 토큰을 끊어야 한다",
    "option4": "Valkey 캐시가 만료되어 재저장이 필요하다는 뜻이다",
    "answerIndex": 2,
    "explanation": "회전 방식에서는 리프레시할 때마다 새 리프레시 토큰을 발급하고 옛것을 즉시 폐기합니다. 따라서 폐기된 토큰이 다시 사용된다는 것은 정상 사용자와 공격자 중 한쪽이 이미 새 토큰을 받아갔는데 다른 쪽이 옛 토큰을 들고 온 상황, 즉 토큰 도난의 강력한 신호입니다. 이때 해당 사용자의 토큰을 전부 무효화해 피해를 차단할 수 있습니다."
  },
  {
    "courseSlug": "lv3-auth-architecture",
    "question": "토큰 저장 위치로서 localStorage와 httpOnly 쿠키의 트레이드오프를 옳게 설명한 것은?",
    "option1": "localStorage는 XSS에 뚫리면 토큰이 통째로 유출되고, httpOnly 쿠키는 스크립트가 토큰을 읽을 수 없는 대신 CSRF 방어(SameSite 등)가 필요하다",
    "option2": "httpOnly 쿠키는 XSS와 CSRF를 모두 완벽하게 막아 주므로 추가 방어가 필요 없다",
    "option3": "localStorage는 브라우저가 요청마다 자동으로 전송해 주므로 CSRF에 취약하다",
    "option4": "httpOnly 쿠키에 저장하면 XSS 공격 자체가 불가능해진다",
    "answerIndex": 0,
    "explanation": "localStorage는 JS로 읽을 수 있어 XSS 스크립트 한 줄에 토큰이 통째로 유출되지만, 자동 전송이 없어 CSRF에는 해당되지 않습니다. httpOnly 쿠키는 스크립트에서 읽을 수 없어 XSS로도 토큰 자체는 훔칠 수 없지만, 브라우저가 자동 첨부하는 성질 때문에 CSRF 위험이 있어 SameSite 속성과 CSRF 토큰으로 막아야 합니다. 또한 httpOnly로 옮겨도 XSS 자체가 무해해지는 것은 아니므로(토큰을 못 읽을 뿐 요청 위조는 가능) 방어는 겹겹이 필요합니다."
  },
  {
    "courseSlug": "lv3-auth-architecture",
    "question": "OAuth2 Authorization Code 흐름에서 브라우저 리다이렉트로는 1회용 '인가 코드'만 전달하고, 실제 토큰은 서버 간 통신으로 교환하는 이유는?",
    "option1": "브라우저는 토큰처럼 긴 문자열을 URL에 담을 수 없기 때문이다",
    "option2": "브라우저 리다이렉트는 주소창·히스토리 등에 노출될 위험이 있는 경로라 가치가 낮은 1회용 코드만 태우고, 진짜 토큰은 client_secret으로 신원을 증명하는 서버 간 통신으로만 주고받기 위해서다",
    "option3": "인가 코드가 곧 액세스 토큰이므로 교환 절차는 형식적인 것이다",
    "option4": "사용자 비밀번호를 우리 서버에 전달하기 위한 중간 단계가 필요하기 때문이다",
    "answerIndex": 1,
    "explanation": "브라우저를 거치는 리다이렉트(앞문)는 주소창과 히스토리에 노출될 수 있는 경로입니다. 그래서 노출돼도 피해가 작은 1회용 단명 코드만 태우고, 진짜 액세스 토큰은 우리 서버와 인가 서버가 client_secret으로 서로를 확인하는 뒷문(서버 간 통신)으로만 교환합니다. 이 흐름에서 사용자 비밀번호는 구글(인가 서버)에만 입력되며 우리 앱은 전혀 볼 수 없습니다."
  },
  {
    "courseSlug": "lv3-observability",
    "question": "장애 대응에서 관측 가능성 3기둥의 역할 분담으로 가장 올바른 설명은?",
    "option1": "로그가 '무엇이 느린가'를, 메트릭이 '왜 느린가'를 답한다",
    "option2": "메트릭이 '무엇이 이상한가'를 알리고, 트레이스로 구간을 좁힌 뒤, 로그로 원인을 확정한다",
    "option3": "트레이스만 있으면 로그와 메트릭은 필요 없다",
    "option4": "세 기둥은 같은 데이터를 형식만 바꿔 저장한 것이므로 하나만 쌓으면 된다",
    "answerIndex": 1,
    "explanation": "세 기둥은 성격이 다른 데이터로 역할을 분담합니다. 메트릭은 숫자의 시계열로 '무엇이, 얼마나 이상한가'를 싸고 빠르게 알려 주고, 트레이스는 요청 하나의 여정에서 느린 구간을 짚어 주며, 로그는 그 순간의 맥락으로 '왜'를 확정합니다. 알림(메트릭) → 구간 좁히기(트레이스) → 원인 확정(로그)이 장애 대응의 기본 동선입니다."
  },
  {
    "courseSlug": "lv3-observability",
    "question": "메트릭 라벨(tag)에 사용자 ID처럼 무한히 늘어나는 값을 쓰면 안 되는 이유는?",
    "option1": "사용자 ID는 개인정보라서 메트릭 시스템이 법적으로 저장을 거부하기 때문",
    "option2": "라벨 값의 종류마다 별도의 시계열이 생겨, 시계열 수가 폭발하며 Prometheus 메모리를 고갈시키기 때문",
    "option3": "Micrometer가 문자열 라벨을 아예 지원하지 않기 때문",
    "option4": "라벨이 많으면 Grafana 대시보드의 색상이 부족해지기 때문",
    "answerIndex": 1,
    "explanation": "메트릭의 라벨은 값의 종류(카디널리티)마다 별도의 시계열을 만듭니다. 라우트나 상태코드처럼 유한한 '분류'는 괜찮지만, 사용자 ID처럼 무한히 늘어나는 '개체'를 라벨로 쓰면 사용자 수만큼 시계열이 생겨 저장소가 터집니다(카디널리티 폭발). 개체 단위 추적은 메트릭이 아니라 로그와 트레이스의 몫입니다."
  },
  {
    "courseSlug": "lv3-observability",
    "question": "지연 시간 지표로 평균 대신 p99를 보라고 하는 이유로 가장 적절한 것은?",
    "option1": "p99는 평균보다 계산이 빨라 서버 부하가 적기 때문",
    "option2": "평균은 항상 p99보다 큰 값이 나와 과잉 경보를 일으키기 때문",
    "option3": "대부분 요청이 빠르면 소수의 매우 느린 요청이 평균에 묻히는데, p99는 가장 불행한 사용자층의 체감을 드러내기 때문",
    "option4": "p99는 에러율까지 함께 포함해서 보여 주는 지표이기 때문",
    "answerIndex": 2,
    "explanation": "요청 100개 중 99개가 10ms이고 1개가 5초여도 평균은 약 60ms로 멀쩡해 보입니다. 그러나 그 1%의 사용자는 실제로 5초를 기다렸죠. p99는 분포의 꼬리, 즉 '가장 불행한 1%가 겪는 경험'을 숫자로 드러내기 때문에 사용자 체감을 대변합니다. 사용자 체감은 평균이 아니라 꼬리에 삽니다."
  },
  {
    "courseSlug": "lv3-observability",
    "question": "새벽에 온콜을 깨우는 알림 설계 원칙으로 본문의 설명과 일치하는 것은?",
    "option1": "CPU 사용률 같은 내부 원인 지표가 높으면 사용자 영향과 무관하게 즉시 깨운다",
    "option2": "임계치를 넘는 순간마다 바로 알림을 보내야 대응이 빨라지므로 지속시간 조건은 두지 않는다",
    "option3": "에러율·p99 초과 같은 증상(사용자 영향) 기반으로 깨우고, 임계치에 지속시간 조건을 더해 순간 스파이크 노이즈를 거른다",
    "option4": "장애를 놓치지 않도록 관련 서비스 전부가 각자 알림을 울리게 한다",
    "answerIndex": 2,
    "explanation": "사람을 깨울 알림은 사용자가 실제로 겪는 증상(에러율 상승, p99 초과) 기반이어야 하고, CPU 같은 원인 지표는 참고용으로 돌립니다. 또 'p99가 1.5초를 10분간 계속 넘을 때'처럼 지속시간(for) 조건을 곱하면 스스로 지나가는 순간 스파이크가 걸러집니다. 서비스 여러 개가 중복으로 우는 것은 원인 알림을 묻어 버리는 안티패턴입니다."
  },
  {
    "courseSlug": "lv3-observability",
    "question": "SLO가 '30일 창에서 99.9%'일 때, 에러 버짓에 대한 설명으로 올바른 것은?",
    "option1": "에러 버짓은 100% - SLO로, 30일 중 약 43분의 장애 허용량이며 버짓이 바닥나면 신규 배포를 늦추고 안정화에 투자하는 의사결정 도구다",
    "option2": "에러 버짓은 서버 증설에 쓸 수 있는 예산 금액을 뜻한다",
    "option3": "에러 버짓이 남아 있으면 일부러 장애를 내서 소진해야 한다",
    "option4": "가용성 목표는 항상 100%로 잡는 것이 이상적이며 에러 버짓은 임시방편이다",
    "answerIndex": 0,
    "explanation": "에러 버짓은 100% - SLO, 즉 '써도 되는 실패량'입니다. 99.9%면 30일 중 약 43분이죠. 진짜 쓸모는 의사결정에 있습니다 — 버짓이 넉넉하면 배포·실험에, 바닥이면 안정화에 투자합니다. 100%를 목표로 하지 않는 이유는 9 하나를 더할 때마다 비용이 약 10배로 뛰는데 사용자는 그 차이를 대부분 느끼지 못하기 때문입니다."
  },
  {
    "courseSlug": "lv3-zero-downtime-deploy",
    "question": "배포 직후 JVM 프로세스는 떴지만 DB 커넥션 풀이 아직 채워지지 않은 서비스가 있다. 헬스체크 관점에서 올바른 상태와 처방은?",
    "option1": "liveness DOWN — 컨테이너를 즉시 재시작해야 한다",
    "option2": "liveness UP, readiness DOWN — 트래픽을 보내지 말고 준비될 때까지 기다린다",
    "option3": "liveness UP, readiness UP — HTTP 서버가 떴으므로 트래픽을 보내도 된다",
    "option4": "readiness DOWN — 즉시 이전 버전으로 롤백해야 한다",
    "answerIndex": 1,
    "explanation": "liveness는 '살아 있나', readiness는 '손님 받을 준비가 됐나'라는 서로 다른 질문입니다. 프로세스는 살아 있으므로 재시작할 일이 아니고(재시작하면 영원히 준비를 못 마칠 수 있음), 준비가 안 됐으므로 트래픽만 주지 않고 기다리는 것이 맞습니다. 진단을 둘로 나누는 이유는 처방이 다르기 때문입니다."
  },
  {
    "courseSlug": "lv3-zero-downtime-deploy",
    "question": "users.name 컬럼을 users.display_name으로 바꾸려 한다. 무중단 배포 원칙에 맞는 진행 방식은?",
    "option1": "배포와 동시에 RENAME COLUMN을 실행해 한 번에 교체한다",
    "option2": "신버전 배포 직후 곧바로 옛 컬럼을 DROP해 스키마를 깔끔히 정리한다",
    "option3": "새 컬럼을 추가만 하고(확장), 새 코드가 양쪽에 쓰며 데이터를 옮긴 뒤(이행), 모두 전환된 다음 릴리스에서 옛 컬럼을 지운다(수축)",
    "option4": "ddl-auto: update가 컬럼 이름 변경을 자동으로 처리하므로 별도 작업이 필요 없다",
    "answerIndex": 2,
    "explanation": "어떤 배포 전략이든 구버전과 신버전 코드가 동시에 살아 있는 구간이 생기므로, DB는 그동안 양쪽 모두와 호환돼야 합니다. 확장→이행→수축 3단계를 여러 릴리스로 나누면 각 단계 사이에서 언제든 멈추거나 되돌릴 수 있습니다. 한 방에 RENAME하면 구버전 코드가 즉사하고, ddl-auto: update는 컬럼 이름 변경을 아예 처리하지 못합니다."
  },
  {
    "courseSlug": "lv3-zero-downtime-deploy",
    "question": "블루-그린 배포가 롤링 배포보다 '되돌림(롤백)'에서 유리한 핵심 이유는?",
    "option1": "구버전(blue)이 그대로 살아 있어 앞단 업스트림 설정만 되돌리면 재빌드·재기동 없이 즉시 복귀되기 때문",
    "option2": "환경을 2벌 유지해 서버 비용이 절반으로 줄어들기 때문",
    "option3": "구버전과 신버전이 동시에 살아 있는 구간이 아예 생기지 않기 때문",
    "option4": "트래픽을 5%씩 점진적으로 늘리며 지표를 관찰할 수 있기 때문",
    "answerIndex": 0,
    "explanation": "블루-그린은 운영과 똑같은 환경을 한 벌 더 두고 트래픽 스위치만 넘기는 방식이라, 문제가 생기면 스위치를 되돌리는 것만으로 롤백이 끝납니다. 비용은 오히려 2배로 늘고(2번 오답), 전환 검증 구간 동안 두 버전이 공존하며(3번 오답), 트래픽을 점진적으로 늘리는 것은 카나리아 전략의 특징입니다(4번 오답)."
  },
  {
    "courseSlug": "lv3-zero-downtime-deploy",
    "question": "JPA의 ddl-auto: update를 운영에서 계속 쓰는 것이 무중단·롤백 관점에서 위험한 이유로 옳은 것은?",
    "option1": "앱 시작 속도가 느려져 readiness 통과가 늦어지기 때문",
    "option2": "스키마 변경 내용을 배포 전에 리뷰할 수 없고, 되돌릴 스크립트도 존재하지 않기 때문",
    "option3": "Flyway와 함께 쓰면 마이그레이션이 두 번 실행되기 때문",
    "option4": "컬럼 추가가 불가능해 확장(expand) 단계를 수행할 수 없기 때문",
    "answerIndex": 1,
    "explanation": "ddl-auto: update는 앱이 뜰 때 스키마를 알아서 바꾸므로 무엇이 바뀔지 배포 전에 알 수 없어 리뷰가 불가능하고, 롤백 스크립트라는 개념 자체가 없습니다. 또한 컬럼 삭제·이름 변경은 못 하고 추가만 흉내냅니다(즉 컬럼 추가는 가능하므로 4번은 오답). Flyway 도입 시에는 ddl-auto를 validate로 바꿔 앱의 스키마 변경 권한을 회수하는 것이 정석입니다."
  },
  {
    "courseSlug": "lv3-zero-downtime-deploy",
    "question": "롤백 트리거 조건을 문서에 적을 때 가장 바람직한 형태는?",
    "option1": "\"문제가 심각하다고 판단되면 담당자 협의 후 롤백한다\"",
    "option2": "\"사용자 신고가 여러 건 들어오면 롤백을 검토한다\"",
    "option3": "\"팀장이 승인하면 롤백한다\"",
    "option4": "\"5xx 비율이 1%를 10분 넘게 유지하면 롤백한다\"처럼 숫자와 시간이 있는 조건으로 적는다",
    "answerIndex": 3,
    "explanation": "\"심각하면 롤백\" 같은 문장은 새벽 3시의 배포 담당자가 심각함을 판정하느라 시간을 태우게 만듭니다. 숫자(임계값)와 시간(지속 조건)이 명시된 조건이어야 현장에서는 판단이 아니라 실행만 남습니다. 조건은 판단력이 흐린 배포 순간이 아니라 '대낮에' 미리 문서로 정해 두는 것이 핵심이며, 원인 분석은 되돌린 다음에 합니다."
  },
  {
    "courseSlug": "lv3-code-review-refactoring",
    "question": "리팩터링의 올바른 정의는 무엇인가요?",
    "option1": "버그를 고치면서 동시에 코드도 정리하는 작업",
    "option2": "겉으로 보이는 동작을 바꾸지 않으면서 코드의 내부 구조를 개선하는 것",
    "option3": "낡은 코드를 삭제하고 처음부터 새로 작성하는 것",
    "option4": "성능을 높이기 위해 알고리즘을 더 빠른 것으로 교체하는 것",
    "answerIndex": 1,
    "explanation": "리팩터링은 입력이 같으면 출력도 같도록, 즉 사용자가 아무 변화도 느끼지 못하게 유지하면서 내부 구조만 개선하는 작업입니다. 버그 수정은 동작이 바뀌므로 리팩터링이 아니고(기능 개발 모자와 리팩터링 모자를 한 커밋에서 섞지 않는 규율), 전면 재작성은 리팩터링과 정반대의 접근입니다."
  },
  {
    "courseSlug": "lv3-code-review-refactoring",
    "question": "테스트가 없는 코드를 리팩터링해야 할 때 가장 먼저 해야 할 일은 무엇인가요?",
    "option1": "현재 동작을 그대로 고정하는 테스트부터 작성한다",
    "option2": "IDE의 자동 리팩터링 기능을 믿고 바로 구조를 바꾼다",
    "option3": "코드가 검증되지 않았으므로 전면 재작성으로 전환한다",
    "option4": "리팩터링과 함께 테스트를 나중에 한 커밋으로 묶어 올린다",
    "answerIndex": 0,
    "explanation": "\"동작이 바뀌지 않았다\"는 사람 눈이 아니라 테스트가 보장합니다. 그래서 안전망(현재 동작을 고정하는 테스트)을 먼저 만들고, 초록불 → 작은 변경 → 초록불 확인의 짧은 보폭을 반복하는 것이 리팩터링의 순서입니다. IDE 자동 리팩터링이 참조를 잘 따라가 주긴 하지만 테스트라는 안전망을 대체하지는 못합니다."
  },
  {
    "courseSlug": "lv3-code-review-refactoring",
    "question": "리팩터링 우선순위를 정할 때 가장 먼저 정리해야 할 코드는 어디인가요?",
    "option1": "거의 수정되지 않지만 가장 오래된 레거시 코드",
    "option2": "코드 냄새가 가장 많이 발견된 파일 전부",
    "option3": "자주 변경되면서 코드 상태가 지저분한 파일",
    "option4": "신입이 처음 읽게 될 진입점 코드",
    "answerIndex": 2,
    "explanation": "기술 부채의 이자는 그 코드를 '만질 때마다' 나갑니다. 아무도 열지 않는 지저분한 코드는 이자가 0원이므로 급하지 않고, 변경 빈도가 높은데 지저분한 곳(뜨거운 길목)부터 갚아야 투자 대비 효과가 가장 큽니다. 변경 빈도는 감이 아니라 git log 같은 기록으로 확인합니다."
  },
  {
    "courseSlug": "lv3-code-review-refactoring",
    "question": "코드 리뷰의 목적에 대한 설명으로 본문과 일치하는 것은 무엇인가요?",
    "option1": "리뷰의 목적은 결함 발견 하나이며, 나머지는 부수 효과다",
    "option2": "리뷰는 작성자의 실력을 평가해 팀 내 등급을 정하는 절차다",
    "option3": "리뷰는 시니어가 주니어의 코드를 승인해 주는 일방향 관문이다",
    "option4": "결함 발견 외에 지식 공유와 팀 표준 정착도 리뷰의 핵심 효용이다",
    "answerIndex": 3,
    "explanation": "리뷰의 효용은 결함 발견, 지식 공유, 표준 정착 세 가지입니다. 리뷰를 거친 코드는 최소 두 사람이 이해하는 코드가 되어 담당자 부재에도 팀이 멈추지 않고, 네이밍·계층 규칙 같은 팀 합의가 코멘트를 통해 전파됩니다. 또한 '사람이 아니라 코드를 리뷰한다'가 제1원칙이므로 실력 평가라는 해석은 리뷰 문화를 방어전으로 만드는 잘못된 관점입니다."
  },
  {
    "courseSlug": "lv3-code-review-refactoring",
    "question": "리뷰 코멘트에서 '블로킹'과 '제안'을 구분하는 기준으로 옳은 것은 무엇인가요?",
    "option1": "블로킹은 시니어의 코멘트, 제안은 주니어의 코멘트를 말한다",
    "option2": "블로킹은 머지 전 반드시 해결해야 할 문제이고, 제안은 작성자 재량에 맡기는 개선 의견이다",
    "option3": "블로킹은 성능 문제에만, 제안은 보안 문제에만 붙이는 표시다",
    "option4": "코멘트 수가 5개를 넘으면 자동으로 블로킹이 된다",
    "answerIndex": 1,
    "explanation": "블로킹은 보안 구멍, N+1, 롤백 불가처럼 머지 전에 반드시 해결돼야 하는 문제이고, 제안(non-blocking)은 네이밍 취향처럼 '나는 이렇게 하겠지만 당신 판단에 맡긴다'는 개선 의견입니다. 코멘트마다 어느 쪽인지 명시하면 \"다 고쳐야 하나요?\" 같은 소모전이 사라져 리뷰 속도와 분위기가 좋아집니다. 누가 썼는지나 개수와는 무관한 구분입니다."
  },
  {
    "courseSlug": "lv4-distributed-systems-basics",
    "question": "CAP 정리에 대한 설명으로 가장 정확한 것은?",
    "option1": "일관성(C)·가용성(A)·분할내성(P) 중 마음에 드는 두 개를 자유롭게 고르는 문제다",
    "option2": "네트워크 분할이 일어났을 때, 일관성(C)과 가용성(A) 중 하나를 포기해야 한다는 정리다",
    "option3": "분할내성(P)을 포기하면 일관성과 가용성을 모두 얻을 수 있으므로 P를 포기하는 것이 실무의 정석이다",
    "option4": "CAP은 분할이 없는 평상시의 지연 시간과 일관성의 트레이드오프를 설명한다",
    "answerIndex": 1,
    "explanation": "네트워크 분할은 반드시 언젠가 일어나므로 P는 선택지가 아니라 전제입니다. 따라서 '셋 중 둘'이 아니라, 분할이 일어난 순간 C(일부 요청 거절)와 A(어긋남 허용) 중 무엇을 택할지가 실제 선택입니다. P를 포기한다는 것은 네트워크가 절대 끊기지 않는다고 가정하는 것이라 설계가 될 수 없고, 평상시의 지연 vs 일관성 트레이드오프는 CAP이 아니라 확장판인 PACELC의 Else 부분이 다루는 내용입니다."
  },
  {
    "courseSlug": "lv4-distributed-systems-basics",
    "question": "타임아웃·재시도 설계에서 멱등성(idempotency)이 반드시 세트로 필요한 이유는?",
    "option1": "멱등성이 있으면 타임아웃 값을 계산할 필요가 없어지기 때문이다",
    "option2": "타임아웃이 터졌을 때 요청이 이미 서버에서 처리됐을 가능성을 배제할 수 없어서, 같은 요청을 다시 보내도 결과가 한 번 처리한 것과 같도록 보장해야 하기 때문이다",
    "option3": "멱등성이 재시도 간격을 자동으로 지수적으로 늘려 주기 때문이다",
    "option4": "멱등성은 네트워크 분할 자체를 예방해 재시도가 필요 없게 만들기 때문이다",
    "answerIndex": 1,
    "explanation": "타임아웃 시 호출자는 '요청 미도착 / 처리 중 / 처리 완료 후 응답만 유실'의 세 경우를 구분할 수 없습니다. 즉 재시도하는 요청은 이미 처리됐을 수 있으므로, Idempotency-Key 같은 장치로 '같은 요청 두 번 = 한 번'을 서버가 보장해야 중복 결제 같은 사고 없이 안전하게 재시도할 수 있습니다. 재시도 간격 조절은 지수 백오프의 역할이고, 멱등성은 분할을 예방하지 못합니다."
  },
  {
    "courseSlug": "lv4-distributed-systems-basics",
    "question": "쿼럼에서 복제본 N개, 쓰기 확인 W개, 읽기 확인 R개일 때 W + R > N 조건이 보장하는 것은?",
    "option1": "모든 노드의 물리 시계가 동기화된다",
    "option2": "네트워크 분할이 발생하지 않는다",
    "option3": "읽기에 참여하는 노드와 쓰기에 성공한 노드가 반드시 한 개 이상 겹쳐서, 읽기가 최소 하나의 최신 값을 만난다",
    "option4": "쓰기 성능과 읽기 성능이 항상 동일해진다",
    "answerIndex": 2,
    "explanation": "W + R > N이면 쓰기가 성공한 노드 집합과 읽기가 확인하는 노드 집합이 수학적으로 반드시 교집합을 가집니다. 그래서 읽기가 최소 한 노드에서는 최신 값을 만나게 되어 낡은 값만 읽는 상황을 막습니다. 같은 원리(과반은 하나뿐이라 반드시 겹친다)가 리더 선출에서 스플릿 브레인을 막는 다수결의 근거이기도 합니다. 시계 동기화나 분할 예방과는 무관하며, N=3/W=3/R=1처럼 쓰기와 읽기 성능은 구성에 따라 비대칭이 됩니다."
  },
  {
    "courseSlug": "lv4-distributed-systems-basics",
    "question": "LWW(Last-Write-Wins) 충돌 해소가 위험한 근본 이유는?",
    "option1": "타임스탬프 비교 연산 자체가 너무 느려서 시스템 전체의 처리량이 떨어지기 때문이다",
    "option2": "서버들의 물리 시계가 어긋나 있으면 실제로는 나중에 일어난 정당한 쓰기가 에러나 로그도 없이 조용히 사라질 수 있기 때문이다",
    "option3": "LWW는 반드시 리더-팔로워 복제에서만 동작하기 때문이다",
    "option4": "LWW를 쓰면 읽기 요청이 항상 모든 노드를 거쳐야 하기 때문이다",
    "answerIndex": 1,
    "explanation": "LWW는 '타임스탬프가 늦은 쓰기가 이긴다'는 규칙인데, 그 타임스탬프가 서로 어긋난 물리 시계에서 나온다는 것이 함정입니다. 시계가 빠른 서버의 이전 쓰기가 시계가 정상인 서버의 나중 쓰기를 이겨 버리면, 정당한 데이터가 에러도 로그도 없이 증발합니다. NTP로 맞춰도 오차는 남으므로, 잔고처럼 모든 쓰기가 의미 있는 데이터에는 버전 번호·벡터 시계·단일 리더 직렬화 같은 대안을 써야 합니다. 성능 문제가 아니라 정합성(무손실) 문제입니다."
  },
  {
    "courseSlug": "lv4-distributed-systems-basics",
    "question": "traderbox에서 시세 틱은 Valkey Pub/Sub(AP 성향), 인증·잔고성 데이터는 단일 PostgreSQL(강한 일관성)로 설계된 이유로 옳은 것은?",
    "option1": "시세는 유실돼도 다음 틱이 곧 최신 상태로 덮어 주지만, 잔고성 데이터는 한 건의 어긋남이 그대로 사고이고 스스로 복구되지 않기 때문이다",
    "option2": "Valkey가 PostgreSQL보다 데이터를 더 안전하게 영구 보존하기 때문이다",
    "option3": "시세 데이터가 잔고 데이터보다 중요해서 더 빠른 저장소를 배정했기 때문이다",
    "option4": "하나의 제품은 반드시 하나의 일관성 수준으로 통일해야 한다는 원칙 때문이다",
    "answerIndex": 0,
    "explanation": "시세의 가치는 신선도가 전부라 1초 전 가격은 이미 낡은 데이터이고, 몇 틱을 놓쳐도 다음 틱이 화면을 최신으로 되돌리는 자기 치유가 있습니다. 그래서 전달 보장을 일부러 포기하고 Pub/Sub으로 흘려보냅니다. 반면 인증·잔고성 데이터는 한 건의 유실·어긋남이 복구 불가능한 사고이므로 트랜잭션이 보장되는 단일 Postgres에 두고 지연을 감수합니다. 핵심 관점은 'CP냐 AP냐는 시스템이 아니라 데이터마다 다르게 정한다'는 것으로, 4번 선택지는 이 관점과 정반대입니다."
  },
  {
    "courseSlug": "lv4-message-queue-event-driven",
    "question": "동기 REST 체인으로 회원가입 후 이메일·포인트·통계 서비스를 차례로 호출할 때 생기는 문제로, 강좌에서 설명한 것이 아닌 것은?",
    "option1": "가장 느린 서비스가 전체 응답 시간의 하한을 정한다",
    "option2": "부가 기능인 통계 서비스의 장애가 회원가입 자체를 실패시킨다",
    "option3": "알림 채널을 추가하려면 가입 API 코드를 수정해야 한다",
    "option4": "브로커에 메시지가 쌓여 컨슈머 랙이 계속 증가한다",
    "answerIndex": 3,
    "explanation": "컨슈머 랙은 비동기 메시징(브로커) 환경에서 소비가 생산을 못 따라갈 때 생기는 지표입니다. 동기 REST 체인에는 브로커가 없으므로 랙이라는 개념 자체가 없고, 동기 체인의 문제는 지연 전파·연쇄 장애·강한 결합 세 가지입니다."
  },
  {
    "courseSlug": "lv4-message-queue-event-driven",
    "question": "Kafka에서 파티션 키를 userId로 지정했을 때 얻는 효과로 옳은 것은?",
    "option1": "토픽 전체에 걸쳐 모든 메시지의 순서가 보장된다",
    "option2": "같은 사용자의 이벤트는 같은 파티션에 들어가 순서가 유지되고, 다른 사용자끼리는 병렬 처리된다",
    "option3": "파티션 수와 무관하게 컨슈머를 무한히 늘려 병렬성을 높일 수 있다",
    "option4": "메시지가 소비되는 즉시 파티션에서 삭제된다",
    "answerIndex": 1,
    "explanation": "Kafka는 같은 파티션 안에서만 순서를 보장하고, 같은 키의 메시지는 항상 같은 파티션으로 갑니다. 따라서 키를 userId로 주면 사용자별 순서 보장과 사용자 간 부하 분산을 동시에 얻습니다. 전체 순서 보장은 불가능하고, 병렬성의 상한은 파티션 수이며, Kafka는 소비돼도 메시지를 지우지 않습니다."
  },
  {
    "courseSlug": "lv4-message-queue-event-driven",
    "question": "at-least-once 전달에서 컨슈머가 같은 메시지를 중복으로 받게 되는 대표적 원인은?",
    "option1": "브로커가 성능을 위해 모든 메시지를 기본적으로 두 번씩 보내기 때문",
    "option2": "컨슈머가 작업 처리를 마친 직후, 오프셋을 커밋하기 전에 죽으면 브로커가 같은 메시지를 다시 전달하기 때문",
    "option3": "파티션 키를 지정하지 않으면 메시지가 자동으로 복제되기 때문",
    "option4": "DLQ로 보낸 메시지가 자동으로 원본 토픽에 재발행되기 때문",
    "answerIndex": 1,
    "explanation": "컨슈머가 실제 작업(예: 알림 발송)을 끝낸 뒤 오프셋 커밋 전에 죽으면, 브로커 입장에서는 '아직 안 읽은' 메시지이므로 재전달합니다. 유실을 막으려면 재전송이 불가피하고 그 대가가 중복이므로, 이는 버그가 아니라 at-least-once의 정상 동작입니다. 그래서 eventId로 중복을 제거하는 멱등 컨슈머가 필요합니다."
  },
  {
    "courseSlug": "lv4-message-queue-event-driven",
    "question": "트랜잭셔널 아웃박스 패턴이 해결하려는 핵심 문제는 무엇인가?",
    "option1": "컨슈머가 같은 메시지를 두 번 처리해 알림이 중복 발송되는 문제",
    "option2": "DB에 데이터를 저장한 뒤 Kafka에 발행하기 전에 서버가 죽으면 이벤트가 유실되는(또는 그 반대로 유령 이벤트가 생기는) 문제",
    "option3": "컨슈머 랙이 증가해 알림이 늦게 도착하는 문제",
    "option4": "이벤트 스키마에서 필드를 삭제하면 옛 컨슈머가 깨지는 문제",
    "answerIndex": 1,
    "explanation": "아웃박스는 이벤트를 브로커가 아니라 비즈니스 데이터와 같은 DB 트랜잭션으로 outbox 테이블에 먼저 기록해, 'DB 저장과 이벤트 발행이 어긋나는 틈'을 없애는 패턴입니다. 별도 릴레이가 outbox를 읽어 발행하므로 유실이 없습니다. 중복 소비 문제는 아웃박스가 아니라 멱등 컨슈머가 담당하는 별개의 방어입니다."
  },
  {
    "courseSlug": "lv4-message-queue-event-driven",
    "question": "이벤트와 커맨드의 구분에 대한 설명으로 옳은 것은?",
    "option1": "UserSignedUp은 이미 일어난 과거형 사실(이벤트)이고, SendEmail은 특정 수신자에게 행동을 요구하는 요청(커맨드)이다",
    "option2": "이벤트는 반드시 동기로, 커맨드는 반드시 비동기로 전달해야 한다",
    "option3": "커맨드로 설계하면 발행자가 수신자를 몰라도 되므로 결합이 가장 느슨해진다",
    "option4": "이벤트는 미래에 해야 할 일을 명령형으로 서술한 메시지다",
    "answerIndex": 0,
    "explanation": "이벤트는 '가입했다'처럼 이미 일어난 과거형 사실이며 발행자는 누가 듣는지 모릅니다. 커맨드는 '보내라'처럼 특정 수신자에게 특정 행동을 요구하는 요청입니다. 결합을 느슨하게 하는 쪽은 이벤트이고(커맨드는 수신자와 의도를 명시해 결합이 생김), 동기/비동기 여부와는 별개의 구분입니다."
  },
  {
    "courseSlug": "lv4-monolith-vs-microservices",
    "question": "콘웨이의 법칙이 아키텍처 선택에 시사하는 바로 가장 적절한 것은?",
    "option1": "시스템은 항상 최신 기술 트렌드를 따라 설계해야 한다",
    "option2": "마이크로서비스 도입 여부는 조직이 감당할 수 있는 독립 배포 주기의 수라는 조직 질문으로 판단해야 한다",
    "option3": "팀 규모와 무관하게 서비스 수는 많을수록 좋다",
    "option4": "시스템 구조는 사용하는 프로그래밍 언어에 의해 결정된다",
    "answerIndex": 1,
    "explanation": "콘웨이의 법칙은 '시스템 설계는 그 조직의 커뮤니케이션 구조를 닮는다'는 관찰입니다. 3명이 30개 서비스를 운영하면 온콜·배포 부담이 조직을 초과하고, 30명이 모놀리스 하나에 매달리면 배포 열차와 머지 충돌이 병목이 됩니다. 따라서 쪼갤지 말지는 기술 질문이 아니라 '우리 조직이 몇 개의 독립 배포 주기를 감당할 수 있나'라는 조직 질문으로 판단해야 합니다."
  },
  {
    "courseSlug": "lv4-monolith-vs-microservices",
    "question": "'분산 모놀리스'의 대표적인 증상으로 본문에서 든 것은?",
    "option1": "서비스 하나를 배포하려면 다른 서비스들도 같이 배포해야 한다",
    "option2": "모든 서비스가 하나의 코드베이스에 들어 있다",
    "option3": "서비스마다 자기 스키마의 데이터를 소유하고 있다",
    "option4": "동기 호출과 비동기 이벤트를 용도에 맞게 나눠 쓴다",
    "answerIndex": 0,
    "explanation": "분산 모놀리스는 쪼개서 네트워크 비용(지연·부분 실패·버전 관리)은 전부 내는데, 마이크로서비스의 핵심 보상인 '독립 배포'는 못 받는 최악의 형태입니다. A를 배포하려면 B·C도 같이 배포해야 하거나, 여러 서비스가 한 테이블을 같이 읽고 쓰는 것이 전형적 증상입니다. 나머지 보기들은 정상적인 모놀리스이거나 오히려 건강한 마이크로서비스의 특징입니다."
  },
  {
    "courseSlug": "lv4-monolith-vs-microservices",
    "question": "도메인 주도 설계에서 바운디드 컨텍스트(서비스 경계 후보)를 찾는 실마리로 본문이 제시한 것은?",
    "option1": "화면·비즈니스 로직·DB 같은 기술 계층이 나뉘는 지점",
    "option2": "코드 라인 수가 일정 기준을 넘는 지점",
    "option3": "같은 단어가 문맥에 따라 다른 뜻이 되는 지점",
    "option4": "개발자들이 가장 자주 수정하는 파일이 모인 지점",
    "answerIndex": 2,
    "explanation": "쇼핑몰의 '상품'이 카탈로그에선 사진·검색 키워드, 재고에선 SKU·창고 위치, 주문에선 가격 스냅샷, 배송에선 무게·부피를 뜻하듯 — 같은 단어의 의미가 갈라지는 지점이 곧 바운디드 컨텍스트의 경계입니다. 반대로 기술 계층(화면/로직/DB)으로 자르면 기능 하나를 고칠 때 여러 서비스를 전부 건드리게 되는 나쁜 경계가 됩니다."
  },
  {
    "courseSlug": "lv4-monolith-vs-microservices",
    "question": "사가 패턴에서 이메일 발송이나 외부 결제 승인처럼 보상이 불가능하거나 비싼 단계를 어디에 배치해야 하는가?",
    "option1": "사가의 첫 단계 — 가장 먼저 처리해 실패를 빨리 발견한다",
    "option2": "사가의 중간 — 앞뒤 단계가 감싸도록 한다",
    "option3": "사가의 마지막 단계 — 뒤에 실패할 단계가 없으면 보상할 일도 없다",
    "option4": "순서는 무관하다 — 2단계 커밋이 자동으로 되돌려 준다",
    "answerIndex": 2,
    "explanation": "사가는 중간 단계가 실패하면 이미 커밋된 앞 단계들을 보상 트랜잭션으로 역순으로 되돌리는 패턴입니다. 발송된 이메일은 거둘 수 없고 외부 PG 승인 취소는 별개의 복잡한 절차이므로, 이런 단계를 마지막에 두면 그 뒤에 실패할 단계가 없어 보상이 필요한 상황 자체가 생기지 않습니다. 2PC는 참여자 전원이 락을 잡고 대기해 하나만 느려져도 전체가 멈추기 때문에 실무에서 기피됩니다."
  },
  {
    "courseSlug": "lv4-monolith-vs-microservices",
    "question": "traderbox가 채택한 '스키마-퍼-서비스'(단일 PostgreSQL + 서비스별 스키마 격리) 접근의 장점으로 옳은 것은?",
    "option1": "여러 서비스가 서로의 테이블에 자유롭게 JOIN할 수 있다",
    "option2": "DB 인스턴스는 한 대라 운영이 가볍고, 경계는 강제되며, 나중에 커진 스키마만 별도 DB로 이사시킬 수 있다",
    "option3": "처음부터 서비스 수만큼 DB 인스턴스를 운영해 완전한 물리적 격리를 얻는다",
    "option4": "트랜잭션이 서비스 경계를 넘어도 하나의 BEGIN~COMMIT으로 묶인다",
    "answerIndex": 1,
    "explanation": "스키마-퍼-서비스는 DB 분리의 단계적 접근입니다. 인프라는 한 대라 운영 부담이 적으면서도, 서비스별 DB 계정에 자기 스키마 권한만 부여하면 '남의 테이블에 JOIN 못 함'이 DB 수준에서 강제됩니다. 코드가 이미 경계를 지키고 있으므로 나중에 트래픽이 커진 스키마만 별도 인스턴스로 옮기면 됩니다. 서로의 테이블에 직접 JOIN하는 것은 공유 DB 안티패턴으로, 스키마 변경의 자유와 독립 배포를 무너뜨립니다."
  },
  {
    "courseSlug": "lv4-db-replication-sharding",
    "question": "글을 쓴 직후 새로고침했더니 방금 쓴 글이 목록에 보이지 않는다. 리더-팔로워 복제 환경에서 이 현상의 가장 유력한 원인은?",
    "option1": "리더 DB가 INSERT를 거부하고도 성공 응답을 보냈기 때문",
    "option2": "복제 지연으로 아직 글이 도착하지 않은 팔로워에서 읽었기 때문",
    "option3": "팔로워가 읽기 전용이라 SELECT 자체가 실패했기 때문",
    "option4": "WAL이 손상되어 리더의 데이터가 유실됐기 때문",
    "answerIndex": 1,
    "explanation": "쓰기는 리더에 커밋됐지만 WAL이 팔로워에 재생되기 전에 읽기 요청이 팔로워로 라우팅되면, 아직 글이 없는 상태를 읽게 됩니다(read-your-writes 위반). DB가 고장 난 게 아니라 '복제는 시간이 걸린다'는 성질을 설계가 반영하지 못한 것으로, 쓰기 직후 리더에서 읽기·세션 고정·타임스탬프 라우팅 같은 패턴으로 해결합니다."
  },
  {
    "courseSlug": "lv4-db-replication-sharding",
    "question": "비동기 복제 + 자동 failover 환경에서 '커밋 성공 응답을 받은 쓰기'가 사라질 수 있는 이유는?",
    "option1": "비동기 복제에서 커밋 성공은 리더에만 적혔다는 뜻이라, WAL 전달 전에 리더가 죽고 팔로워가 승격되면 그 쓰기가 새 리더에 없기 때문",
    "option2": "팔로워가 승격될 때 자신의 데이터를 전부 초기화하고 새로 시작하기 때문",
    "option3": "자동 failover 도구가 승격 과정에서 최근 트랜잭션을 의도적으로 롤백하기 때문",
    "option4": "동기 복제보다 비동기 복제의 디스크 기록이 느려서 커밋이 유실되기 때문",
    "answerIndex": 0,
    "explanation": "비동기 복제는 리더가 자기 디스크에 쓰자마자 성공을 응답하고 WAL은 뒤따라 전송됩니다. 그 WAL이 팔로워에 도착하기 전에 리더가 죽으면, 승격된 새 리더에는 해당 쓰기가 존재하지 않아 클라이언트만 성공했다고 믿는 상태가 됩니다. 그래서 잃으면 안 되는 쓰기는 동기 복제나 별도 원장으로 보호해야 합니다."
  },
  {
    "courseSlug": "lv4-db-replication-sharding",
    "question": "해시 샤딩과 레인지 샤딩의 트레이드오프 설명으로 옳은 것은?",
    "option1": "해시 샤딩은 범위 조회에 유리하지만 데이터가 특정 샤드에 쏠리기 쉽다",
    "option2": "레인지 샤딩은 항상 모든 샤드에 부하를 균등하게 분배한다",
    "option3": "해시 샤딩은 분포가 균등하지만 범위 조회가 모든 샤드를 두드리고, 레인지 샤딩은 범위 조회에 유리하지만 최신 구간에 쓰기가 몰릴 수 있다",
    "option4": "두 방식 모두 샤드 수를 늘려도 데이터 이동이 전혀 필요 없다",
    "answerIndex": 2,
    "explanation": "해시 샤딩은 키를 해시해 균등하게 뿌리므로 분포는 고르지만, 연속된 범위의 키가 여러 샤드에 흩어져 범위 조회가 스캐터-개더가 됩니다. 레인지 샤딩은 범위째 담아 범위 조회가 한 샤드로 끝나지만, 시간순 키라면 최신 구간 샤드에만 쓰기가 집중되는 핫 샤드가 생깁니다. 균등 분포와 조회 지역성은 서로 당기는 방향이 반대인 트레이드오프입니다."
  },
  {
    "courseSlug": "lv4-db-replication-sharding",
    "question": "시계열 데이터(시세·로그)에 시간 기반 파티셔닝을 적용할 때, 오래된 데이터 정리에 DELETE 대신 파티션 DROP을 쓰는 이유는?",
    "option1": "DELETE는 트랜잭션을 지원하지 않아 데이터 정합성이 깨지기 때문",
    "option2": "DROP은 파티션 파일째 제거해 밀리초에 끝나지만, DELETE는 수억 행을 개별 삭제하며 죽은 튜플과 VACUUM 부담을 남기기 때문",
    "option3": "DROP TABLE은 실행 후 되돌릴 수 있어 DELETE보다 안전하기 때문",
    "option4": "파티션된 테이블에서는 DELETE 문 실행이 문법적으로 불가능하기 때문",
    "answerIndex": 1,
    "explanation": "DELETE는 행을 하나씩 지우면서 죽은 튜플(dead tuple)을 남겨 VACUUM 부담을 키우고 오래 걸립니다. 반면 시간 기반 파티션은 기간이 지난 파티션을 DROP(또는 DETACH 후 DROP)하면 파일 단위로 즉시 제거되어 부스러기가 없습니다. 그래서 보존 정책이 있는 시계열 데이터는 '지울 수 있게'가 아니라 '떨어뜨릴 수 있게' 설계하는 것이 정석입니다."
  },
  {
    "courseSlug": "lv4-db-replication-sharding",
    "question": "'유저 1천만 명, 일 쓰기 5천만 건' 서비스의 확장 전략으로 이 강좌의 판단 순서에 가장 부합하는 것은?",
    "option1": "유저가 1천만 명이므로 지체 없이 샤딩부터 도입한다",
    "option2": "복제는 쓰기도 늘려 주므로 레플리카만 충분히 늘리면 샤딩은 영원히 불필요하다",
    "option3": "일 5천만 건은 평균 초당 약 580건 수준이므로, 캐시와 읽기 레플리카로 읽기를 분산하고 쓰기는 튜닝으로 버티다 쓰기 한계 전망이 보일 때 샤딩을 준비한다",
    "option4": "RDS 같은 관리형 서비스로 옮기면 샤드 키 설계와 복제 지연 대응까지 자동으로 해결된다",
    "answerIndex": 2,
    "explanation": "일 5천만 건은 평균 초당 약 580건으로, 피크를 감안해도 잘 튜닝된 단일 Postgres가 감당할 수 있는 범위입니다. 따라서 인덱스·쿼리 튜닝·캐시·읽기 레플리카(복제)를 먼저 쓰고, 쓰기 성장이 한 대의 천장에 닿을 전망이 보일 때 샤딩을 준비하는 것이 순서입니다. 복제는 쓰기를 모든 복제본에 반복시키므로 쓰기 확장 수단이 아니며, 관리형 서비스는 운영 노동을 덜어 줄 뿐 복제 지연 대응이나 샤드 키 설계는 여전히 설계자의 몫입니다."
  },
  {
    "courseSlug": "lv4-high-traffic-architecture",
    "question": "평균 응답시간이 45ms로 좋아 보이는데도 실무에서는 p99 지연을 더 중요하게 보는 이유로 가장 적절한 것은?",
    "option1": "p99가 평균보다 항상 계산하기 쉽기 때문이다",
    "option2": "평균은 소수의 느린 요청을 다수의 빠른 요청이 희석해 버리고, 하루에 여러 번 요청하는 사용자는 '1%의 느린 요청'을 높은 확률로 매일 겪기 때문이다",
    "option3": "p99는 서버 비용을 직접 결정하는 유일한 지표이기 때문이다",
    "option4": "평균은 읽기 요청에만 적용되고 쓰기 요청에는 적용할 수 없기 때문이다",
    "answerIndex": 1,
    "explanation": "평균은 소수의 아주 느린 요청을 다수의 빠른 요청이 희석합니다. 하루 50회 요청하는 사용자가 p99 지연을 한 번이라도 만날 확률은 1-(0.99^50)≈39%로, '1%의 느린 요청'은 사실상 세 명 중 한 명이 매일 겪는 경험입니다. 게다가 한 화면이 API 여러 개를 부르면 가장 느린 하나가 화면 전체의 체감 속도를 정하므로, 사용자 경험을 결정하는 것은 평균이 아니라 p99입니다."
  },
  {
    "courseSlug": "lv4-high-traffic-architecture",
    "question": "로드밸런싱 알고리즘 중 컨시스턴트 해싱(consistent hashing)을 선택하는 가장 적절한 상황은?",
    "option1": "모든 요청의 처리 비용이 비슷하고 서버 사양이 동일할 때",
    "option2": "요청마다 처리 시간이 들쭉날쭉해서 연결 수가 적은 서버로 보내고 싶을 때",
    "option3": "캐시 지역성이 중요해서 같은 키는 늘 같은 서버로 보내되, 서버 증감 시 재배치되는 키를 최소화하고 싶을 때",
    "option4": "죽은 서버를 자동으로 감지해서 트래픽 풀에서 제외하고 싶을 때",
    "answerIndex": 2,
    "explanation": "컨시스턴트 해싱은 키(유저ID 등)를 해시해 같은 키를 항상 같은 서버로 보내므로 캐시 지역성이 좋고, 서버를 추가·제거해도 이동하는 키가 전체의 1/N 정도에 그칩니다. 요청 비용이 균일하면 라운드로빈, 처리 시간이 들쭉날쭉하면 최소 연결이 어울리고, 죽은 서버 제외는 알고리즘이 아니라 헬스체크의 역할입니다."
  },
  {
    "courseSlug": "lv4-high-traffic-architecture",
    "question": "캐시 스탬피드(cache stampede)에 대한 설명으로 옳은 것은?",
    "option1": "인기 키의 TTL이 만료되는 순간 그동안 캐시가 막아 주던 대량의 요청이 한꺼번에 원본(DB)으로 몰리는 현상으로, 잠금·확률적 조기 갱신·TTL 지터로 막는다",
    "option2": "캐시 서버의 메모리가 가득 차서 오래된 키가 자동으로 삭제되는 현상으로, 메모리를 늘리면 해결된다",
    "option3": "쓰기 요청이 캐시와 DB에 동시에 기록되면서 응답이 느려지는 현상으로, write-behind 패턴으로 막는다",
    "option4": "CDN 엣지 서버가 원본과 통신이 끊겨 낡은 콘텐츠를 계속 제공하는 현상으로, purge API로 막는다",
    "answerIndex": 0,
    "explanation": "캐시 스탬피드는 인기 키가 만료되는 그 순간, 초당 수천 건의 요청이 전부 캐시 miss가 되어 동시에 원본으로 돌진하는 현상입니다. 대책은 본문에서 배운 세 가지 — 한 요청만 락을 잡고 재계산하는 잠금, TTL 임박 시 미리 갱신하는 확률적 조기 갱신, 만료 시각을 흩는 TTL 랜덤 지터입니다. 나머지 보기는 캐시 축출·쓰기 패턴·CDN 무효화라는 별개의 주제입니다."
  },
  {
    "courseSlug": "lv4-high-traffic-architecture",
    "question": "레이트리밋 알고리즘에서 토큰 버킷(token bucket)과 슬라이딩 윈도우(sliding window)의 차이로 옳은 것은?",
    "option1": "토큰 버킷은 유저 기준 제한에만, 슬라이딩 윈도우는 IP 기준 제한에만 쓸 수 있다",
    "option2": "슬라이딩 윈도우는 순간 폭발(burst)을 허용하는 방식이라 로그인 시도 제한에 적합하다",
    "option3": "토큰 버킷은 모아 둔 토큰만큼 순간 폭발(burst)을 허용해 API 트래픽에 어울리고, 슬라이딩 윈도우는 시간 창 안의 횟수를 엄격히 세서 로그인 시도처럼 버스트도 막고 싶을 때 어울린다",
    "option4": "두 알고리즘은 동작이 완전히 같고 구현 언어만 다르다",
    "answerIndex": 2,
    "explanation": "토큰 버킷은 초당 r개씩 토큰이 채워지고(최대 b개) 요청이 토큰을 소모하는 구조라, 평시 속도는 제한하면서도 모아 둔 토큰만큼의 짧은 몰림(burst)은 자연스럽게 허용합니다. 슬라이딩 윈도우는 '최근 60초 동안 N회까지'처럼 시간 창을 미끄러뜨리며 정확히 세므로 버스트까지 엄격히 막아야 하는 로그인 시도·인증 코드 발송에 어울립니다. 무엇을 기준으로 셀지(IP·유저·API 키)는 알고리즘과 별개의 선택입니다."
  },
  {
    "courseSlug": "lv4-high-traffic-architecture",
    "question": "외부 시세 API 호출용 스레드풀과 결제용 스레드풀을 분리해 두었더니, 시세 API가 멈춰 해당 스레드가 모두 잠겨도 결제 기능은 정상 동작했다. 이때 적용된 장애 격리 패턴은?",
    "option1": "서킷 브레이커 — 실패율이 임계값을 넘으면 호출을 차단한다",
    "option2": "벌크헤드 — 배의 격벽처럼 자원 풀을 기능별로 나눠 한 기능의 장애가 다른 기능으로 번지지 않게 가둔다",
    "option3": "백프레셔 — 소비 속도를 생산자에게 알려 유입을 늦춘다",
    "option4": "stale-while-revalidate — 낡은 응답을 먼저 주고 백그라운드에서 갱신한다",
    "answerIndex": 1,
    "explanation": "벌크헤드는 배의 격벽에서 온 이름으로, 스레드풀·커넥션풀 같은 자원을 기능별로 분리해 한 구획의 장애가 전체로 번지지 않게 가두는 패턴입니다. 공용 풀 하나였다면 시세 API 대기가 풀 전체를 잠식해 무관한 결제까지 함께 죽었을 것입니다. 서킷 브레이커는 실패율 기반 '호출 차단', 백프레셔는 '유입 속도 조절', stale-while-revalidate는 캐시 응답 전략으로 각각 다른 문제를 풉니다."
  },
  {
    "courseSlug": "lv4-kubernetes-cloud-native",
    "question": "쿠버네티스의 '선언형(declarative)' 운영 방식에 대한 설명으로 가장 정확한 것은?",
    "option1": "관리자가 컨테이너를 띄우는 명령을 순서대로 실행하면 시스템이 그 순서를 기록해 재실행한다",
    "option2": "원하는 상태를 YAML로 선언해 두면 컨트롤 루프가 현재 상태와의 차이를 계속 좁혀 그 상태로 수렴시킨다",
    "option3": "모든 설정을 이미지 안에 하드코딩해 어떤 환경에서도 같은 상태가 보장되게 한다",
    "option4": "파드가 죽으면 관리자에게 알림을 보내 수동 복구를 빠르게 하도록 돕는다",
    "answerIndex": 1,
    "explanation": "선언형의 핵심은 '어떻게(절차)'가 아니라 '어떤 상태여야 하는지(결과)'를 적는 것입니다. 컨트롤 루프(reconciliation)가 원하는 상태와 현재 상태를 끊임없이 비교해 차이를 좁히기 때문에, 파드가 죽어 replicas가 모자라면 사람 개입 없이 새 파드를 만들어 선언된 수로 복원합니다. 1번은 명령형 사고이고, 4번처럼 수동 복구에 의존하는 것이 바로 compose 시절의 한계입니다."
  },
  {
    "courseSlug": "lv4-kubernetes-cloud-native",
    "question": "파드 IP가 재생성 때마다 바뀌는 문제를 해결하기 위해 쿠버네티스가 제공하는 것은?",
    "option1": "Deployment — 파드에 영구 고정 IP를 할당해 준다",
    "option2": "ConfigMap — 바뀐 IP를 환경변수로 자동 갱신해 준다",
    "option3": "Service — 라벨로 파드 무리를 묶어 고정된 이름(DNS)과 가상 IP를 부여하고 트래픽을 분배한다",
    "option4": "StatefulSet — 모든 파드의 IP가 절대 바뀌지 않게 막는다",
    "answerIndex": 2,
    "explanation": "파드는 일회용이라 죽고 새로 뜰 때마다 IP가 바뀝니다. Service는 라벨 셀렉터로 파드들을 묶어 안정적인 이름과 가상 IP라는 '고정된 문패'를 제공하고, 들어온 요청을 뒤의 파드들에 분배하는 내장 로드밸런싱까지 해 줍니다. 클러스터 안에서는 서비스 이름이 곧 DNS 호스트명이 되므로 호출하는 쪽은 파드 IP 변화를 전혀 신경 쓰지 않아도 됩니다."
  },
  {
    "courseSlug": "lv4-kubernetes-cloud-native",
    "question": "liveness 프로브와 readiness 프로브의 차이를 올바르게 설명한 것은?",
    "option1": "liveness 실패 시 컨테이너를 재시작하고, readiness 실패 시 재시작 없이 Service의 트래픽 분배 대상에서만 제외한다",
    "option2": "liveness는 배포 때만 검사하고, readiness는 운영 중에만 검사한다",
    "option3": "readiness 실패 시 컨테이너를 재시작하고, liveness 실패 시 트래픽만 차단한다",
    "option4": "둘은 이름만 다르고 실패 시 동작은 동일하다",
    "answerIndex": 0,
    "explanation": "liveness는 '살았나?'를 묻는 질문이라 실패하면 회복 불능(데드락 등)으로 보고 컨테이너를 재시작합니다. readiness는 '지금 트래픽을 받을 준비가 됐나?'를 묻는 질문이라 실패해도 재시작하지 않고 Service의 분배 대상에서만 잠시 빼 둡니다. JVM 워밍업처럼 '죽은 게 아니라 아직 준비 안 된' 상태를 재시작으로 다루면 안 되기 때문에 두 질문을 분리하는 것이며, readiness를 성급하게 OK로 만들면 배포 때마다 준비 안 된 파드로 트래픽이 흘러 502가 납니다."
  },
  {
    "courseSlug": "lv4-kubernetes-cloud-native",
    "question": "리소스 limits를 초과했을 때 CPU와 메모리에 가해지는 제재로 옳은 것은?",
    "option1": "CPU 초과와 메모리 초과 모두 컨테이너가 즉시 강제 종료된다",
    "option2": "CPU 초과는 스로틀링(속도 제한)되고, 메모리 초과는 OOMKill로 컨테이너가 강제 종료된다",
    "option3": "CPU 초과는 OOMKill로 강제 종료되고, 메모리 초과는 스로틀링된다",
    "option4": "둘 다 경고 로그만 남기고 동작에는 영향이 없다",
    "answerIndex": 1,
    "explanation": "CPU는 시간을 쪼개 나눠 쓸 수 있는 자원이라 limit을 넘으려 하면 스로틀링으로 속도만 늦춰지지만, 메모리는 이미 점유한 것을 뺏을 수 없는 자원이라 limit을 넘으면 OOMKill로 컨테이너가 강제 종료됩니다. '앱이 이유 없이 재시작된다'는 문제의 단골 원인이 JVM 힙 설정과 어긋난 빡빡한 메모리 limit인 것도 이 때문입니다."
  },
  {
    "courseSlug": "lv4-kubernetes-cloud-native",
    "question": "DB·Kafka 같은 상태를 가진(stateful) 워크로드에 대해 많은 실무 팀이 내리는 판단과 그 이유로 가장 적절한 것은?",
    "option1": "쿠버네티스에 올리면 StatefulSet이 복제·장애 승격까지 전부 자동으로 해 주므로 반드시 클러스터에 넣는다",
    "option2": "상태 워크로드는 컨테이너화가 불가능하므로 물리 서버에만 설치한다",
    "option3": "무상태 앱은 쿠버네티스에 두고, DB 등은 백업·복제·장애 조치를 대신 맡아 주는 관리형 서비스에 두는 팀이 많다",
    "option4": "DB를 무상태 앱과 같은 Deployment로 배포해 자가 치유를 받게 한다",
    "answerIndex": 2,
    "explanation": "StatefulSet과 PersistentVolume이라는 오브젝트가 있긴 하지만, 디스크 연결·복제 순서·장애 시 승격 같은 DB 고유의 운영 난제를 쿠버네티스가 대신 풀어 주지는 않습니다. 그래서 복제·치유가 공짜인 무상태 앱만 클러스터에 올리고, 상태 워크로드는 관리형 서비스(RDS, MSK 등)에 맡겨 운영 부담을 비용과 맞바꾸는 선택을 하는 팀이 많습니다. '전부 클러스터에 넣어야 진짜'라는 규칙은 없고, 워크로드 성격에 따라 두는 곳을 고르는 것이 설계입니다."
  },
  {
    "courseSlug": "lv4-observability-metrics-tracing",
    "question": "모니터링과 관측성(observability)의 차이를 가장 정확하게 설명한 것은?",
    "option1": "모니터링은 미리 아는 질문에 답을 준비해 두는 것이고, 관측성은 사고 당일 처음 떠올린 질문에도 데이터로 답할 수 있는 상태다",
    "option2": "모니터링은 무료 도구를 쓰고, 관측성은 유료 상용 도구를 쓰는 것이다",
    "option3": "모니터링은 백엔드용이고, 관측성은 프론트엔드용이다",
    "option4": "관측성은 모니터링에서 로그만 제거한 개념이다",
    "answerIndex": 0,
    "explanation": "모니터링은 'CPU가 80% 넘으면 알려 줘'처럼 미리 아는 질문에 대한 답을 준비하는 일입니다. 하지만 장애는 늘 예상 못 한 모양으로 오기 때문에, 시스템이 내보내는 메트릭·로그·트레이스가 충분히 풍부해서 새로운 질문에도 즉석에서 답을 캘 수 있는 상태 — 그것이 관측성입니다. 도구의 가격이나 적용 계층의 문제가 아닙니다."
  },
  {
    "courseSlug": "lv4-observability-metrics-tracing",
    "question": "메트릭 레이블에 user_id를 넣으면 안 되는 이유는?",
    "option1": "개인정보 보호법 위반이라 법적으로 금지되어 있기 때문",
    "option2": "메트릭 저장소는 레이블 값 조합마다 별도의 시계열을 만들기 때문에, 무한히 늘어나는 값을 레이블로 쓰면 시계열 수가 곱셈으로 폭발해 저장소가 감당하지 못한다",
    "option3": "user_id는 문자열이라 숫자만 저장하는 메트릭에 넣을 수 없기 때문",
    "option4": "user_id가 들어가면 그래프의 색이 너무 많아져 보기 어렵기 때문",
    "answerIndex": 1,
    "explanation": "이것이 카디널리티 폭발입니다. 메트릭 저장소는 레이블 값의 조합마다 시계열을 하나씩 만드는데, method(5종)×path(50종)×status(10종)=2,500 시계열 정도는 괜찮지만 여기에 유저 10만 명의 user_id가 곱해지면 2억 5천만 시계열이 되어 메트릭 서버가 먼저 죽습니다. 레이블에는 종류가 유한하고 적은 값만 쓰고, 유저별 분석 같은 질문은 트레이스와 로그의 몫입니다."
  },
  {
    "courseSlug": "lv4-observability-metrics-tracing",
    "question": "응답 시간을 볼 때 평균(mean) 대신 퍼센타일(p50/p95/p99)을 봐야 하는 이유는?",
    "option1": "평균은 계산 비용이 너무 커서 실시간으로 구할 수 없기 때문",
    "option2": "퍼센타일이 평균보다 항상 작은 값이 나와서 보고서가 좋아 보이기 때문",
    "option3": "응답 시간 분포는 꼬리가 긴 모양이라 소수의 매우 느린 요청이 평균을 부풀리거나, 반대로 다수의 빠른 요청이 일부 사용자의 고통을 가려 버리기 때문",
    "option4": "평균은 히스토그램이 아니라 카운터로만 계산할 수 있기 때문",
    "answerIndex": 2,
    "explanation": "응답 시간 분포는 한쪽 꼬리가 긴 비대칭 모양입니다. 본문 예시처럼 10건 중 단 한 건의 2.4초가 평균을 285ms로 5배 부풀릴 수 있고, 반대로 p99가 나쁜데 평균이 멀쩡하면 '일부 사용자만 계속 고통받는' 상태가 숨겨집니다. 그래서 p50은 절반의 경험, p99는 가장 아픈 1%의 경험으로 나눠 보고, SLO도 평균이 아니라 퍼센타일로 겁니다."
  },
  {
    "courseSlug": "lv4-observability-metrics-tracing",
    "question": "분산 트레이싱에서 trace_id가 gateway에서 auth로, 즉 서로 다른 서비스(프로세스)를 건너가는 방법은?",
    "option1": "두 서비스가 같은 데이터베이스 테이블을 조회해서 trace_id를 공유한다",
    "option2": "호출하는 쪽이 W3C 표준 traceparent HTTP 헤더에 trace_id를 실어 보내면, 받는 쪽이 새 트레이스를 만들지 않고 같은 trace_id 아래 자기 스팬을 단다(컨텍스트 전파)",
    "option3": "각 서비스가 무작위로 trace_id를 생성한 뒤 나중에 시간순으로 짜맞춘다",
    "option4": "trace_id는 서비스를 건너갈 수 없어서 서비스마다 트레이스를 따로 봐야 한다",
    "answerIndex": 1,
    "explanation": "비밀은 단순한 HTTP 헤더입니다. W3C 표준 traceparent 헤더에 trace_id와 부모 span_id를 실어 보내면 받는 서비스가 같은 trace_id 아래에 자기 스팬을 이어 붙입니다. 이를 컨텍스트 전파(context propagation)라고 하며, Kafka로 건널 때는 메시지 헤더에 같은 값을 싣습니다. 전파가 한 군데라도 끊기면 트레이스가 두 동강 난 고아 스팬이 되므로, 자체 스레드풀 같은 절단 지점은 자동 계측에 맡기는 것이 안전합니다."
  },
  {
    "courseSlug": "lv4-observability-metrics-tracing",
    "question": "새벽에 담당자를 깨우는 페이징 알람의 우선순위 원칙으로 옳은 것은?",
    "option1": "CPU 사용률 같은 원인 기반 알람이 근본 원인을 가리키므로 항상 페이징 1순위다",
    "option2": "가능한 모든 지표에 알람을 걸어 두는 것이 놓치는 장애를 줄이는 최선이다",
    "option3": "알람은 한 번 설정하면 수정하지 않는 것이 일관성 유지에 좋다",
    "option4": "에러율·지연처럼 사용자가 지금 아프다는 증상 기반 알람을 페이징으로 하고, CPU·디스크 같은 원인 기반 알람은 사용자 영향이 없다면 티켓·채널 알림으로 강등한다",
    "answerIndex": 3,
    "explanation": "우선순위는 언제나 증상이 먼저입니다. CPU가 99%여도 사용자 지연이 없으면 새벽에 깨울 일이 아니고, CPU가 30%여도 에러율이 5%면 즉시 일어나야 합니다. 또 '혹시 몰라서' 걸어 둔 알람이 헛울음을 반복하면 알람 피로가 쌓여 진짜 장애 알람까지 무시되므로, 조치 없이 닫힌 알람은 버그처럼 취급해 임계값을 고치거나 페이징에서 강등해야 합니다."
  },
  {
    "courseSlug": "lv4-sre-incident-response",
    "question": "SRE가 100% 가용성을 목표로 삼지 않는 가장 근본적인 이유는 무엇인가요?",
    "option1": "100%를 측정할 수 있는 모니터링 도구가 없기 때문에",
    "option2": "어느 선을 넘으면 사용자가 차이를 느끼지 못하는데 비용은 9 하나마다 약 10배씩 급증하기 때문에",
    "option3": "SLA 계약서에 100%를 명시하는 것이 법적으로 금지되어 있기 때문에",
    "option4": "클라우드 사업자가 100% 가용성을 기술적으로 지원하지 않기 때문에",
    "answerIndex": 1,
    "explanation": "사용자의 인터넷·기기·통신사부터가 100%가 아니므로 일정 수준을 넘으면 사용자는 개선을 체감하지 못합니다. 반면 신뢰성에는 비용 곡선이 있어 9가 하나 늘 때마다 비용이 대략 10배로 뜁니다. 그래서 SRE의 첫 질문은 '우리에게 몇 개의 9가 정말 필요한가'입니다."
  },
  {
    "courseSlug": "lv4-sre-incident-response",
    "question": "SLO가 99.9%일 때 '에러 버짓'에 대한 설명으로 옳은 것은 무엇인가요?",
    "option1": "한 달에 약 43분의 허용된 실패이며, 소진되면 신규 기능 배포를 멈추고 안정화에 투자한다",
    "option2": "장애를 일으킨 팀에게 부과되는 벌점으로, 누적되면 온콜 순번이 늘어난다",
    "option3": "외부 고객과 맺는 계약이므로 SLO보다 항상 엄격하게 잡아야 한다",
    "option4": "CPU·메모리 사용률이 넘지 말아야 할 상한선을 뜻한다",
    "answerIndex": 0,
    "explanation": "에러 버짓은 100% - SLO로, 99.9%면 한 달(30일)에 약 43.2분의 '써도 되는 실패' 예산입니다. 벌점이 아니라 배포·실험에 쓰는 예산이며, 소진 시 배포를 동결하고 안정화 작업으로 전환하는 정책을 평시에 합의해 두는 것이 핵심입니다. 참고로 외부 계약인 SLA는 SLO보다 느슨하게 잡습니다."
  },
  {
    "courseSlug": "lv4-sre-incident-response",
    "question": "장애 대응 중 인시던트 커맨더(IC)의 올바른 행동은 무엇인가요?",
    "option1": "가장 실력 있는 사람이므로 직접 터미널을 열고 디버깅을 주도한다",
    "option2": "원인이 완전히 규명될 때까지 롤백을 보류하도록 지시한다",
    "option3": "직접 디버깅하지 않고 우선순위 결정과 역할 배정 등 전체 지휘에 집중한다",
    "option4": "혼란을 줄이기 위해 원인이 확정될 때까지 외부 공지를 중단시킨다",
    "answerIndex": 2,
    "explanation": "IC가 디버깅에 뛰어드는 것이 가장 흔한 실수입니다. 전원이 디버깅하면 전체 그림을 보는 사람이 0명이 되므로, IC는 우선순위 결정·역할 배정·상황 파악에 전념해야 합니다. 또한 복구가 우선이라 의심되는 배포는 일단 롤백하고, 커뮤니케이션은 침묵이 최악이므로 주기적으로(다음 업데이트 시각 명시) 이어가야 합니다."
  },
  {
    "courseSlug": "lv4-sre-incident-response",
    "question": "장애 진단에서 '최근에 뭐가 바뀌었나?'가 첫 질문인 이유는 무엇인가요?",
    "option1": "변경 이력 확인이 로그 분석보다 법적 책임 소재를 가리기에 유리하기 때문에",
    "option2": "멀쩡히 돌던 시스템이 스스로 무너지는 경우는 드물고, 장애 대다수가 배포·설정·트래픽 같은 변화 직후에 오기 때문에",
    "option3": "최근 변경을 찾으면 변경한 사람에게 대응을 맡길 수 있기 때문에",
    "option4": "이분 탐색은 변경 이력이 있어야만 수행할 수 있기 때문에",
    "answerIndex": 1,
    "explanation": "장애의 대다수는 배포, 설정 변경, 인프라 작업, 트래픽 급증 같은 '변화' 직후에 발생합니다. 지표가 꺾인 시각과 배포 시각이 겹치면 조사의 80%가 끝난 셈입니다. 변경자에게 책임을 묻기 위함이 아니며(비난 없는 문화), 이분 탐색적 격리는 변화가 안 보일 때도 쓸 수 있는 별개의 기법입니다."
  },
  {
    "courseSlug": "lv4-sre-incident-response",
    "question": "비난 없는(blameless) 포스트모템의 원칙에 부합하는 것은 무엇인가요?",
    "option1": "재발 방지를 위해 실수한 사람의 이름을 근본 원인 항목에 명시한다",
    "option2": "액션 아이템은 부담을 줄이기 위해 담당자와 기한 없이 방향성만 적는다",
    "option3": "같은 장애가 두 번 나면 담당자의 부주의로 기록하고 교육을 강화한다",
    "option4": "'누가 그랬나'가 아니라 '무엇이 그 행동을 합리적으로 보이게 했나'를 묻고, 시스템의 빈틈을 근본 원인으로 판다",
    "answerIndex": 3,
    "explanation": "사람을 벌하면 실수를 숨기게 되어 학습이 사라지고 같은 장애가 반복됩니다. 그래서 개인이 아니라 '잘못이 검증 없이 운영까지 흘러갈 수 있었던 시스템의 빈틈'을 근본 원인으로 다룹니다. 액션 아이템은 담당자와 기한이 없으면 쓰지 않은 것과 같고, 같은 장애의 재발은 사람 잘못이 아니라 시스템(포스트모템 프로세스) 잘못으로 회고해야 합니다."
  },
  {
    "courseSlug": "lv4-system-design-url-shortener",
    "question": "시스템 설계 문제를 받았을 때 가장 먼저 해야 할 일로 이 강좌가 강조하는 것은 무엇인가요?",
    "option1": "컴포넌트 다이어그램을 그려 전체 그림부터 잡는다",
    "option2": "기능 요구와 비기능 요구를 질문으로 명확히 한다",
    "option3": "익숙한 기술 스택(캐시·샤딩)을 먼저 확정한다",
    "option4": "테이블 스키마를 설계해 데이터부터 고정한다",
    "answerIndex": 1,
    "explanation": "요구사항이 다르면 정답도 다르기 때문입니다. 하루 1만 건짜리 사내 도구와 하루 1억 건짜리 글로벌 서비스는 이름만 같은 다른 문제라서, 그림·스키마·기술 선택은 모두 요구사항(기능·규모·지연·가용성)이 확정된 뒤에야 의미가 있습니다. '일단 그림부터'가 아니라 '일단 질문부터'가 루틴의 출발점입니다."
  },
  {
    "courseSlug": "lv4-system-design-url-shortener",
    "question": "쓰기 하루 100만 건, 읽기:쓰기 = 100:1이라는 가정에서 용량 산정이 내려 준 핵심 결론은 무엇인가요?",
    "option1": "저장 용량이 병목이므로 처음부터 DB를 샤딩해야 한다",
    "option2": "쓰기가 초당 수천 건이라 쓰기 경로 최적화가 급하다",
    "option3": "읽기 편중 시스템이므로 설계 에너지를 읽기 경로(캐시)에 쏟아야 한다",
    "option4": "읽기와 쓰기가 비슷한 수준이라 균형 잡힌 설계가 필요하다",
    "answerIndex": 2,
    "explanation": "계산하면 쓰기는 약 12건/초로 한가한 반면 읽기는 평균 1,200건/초, 피크 수천 건에 이릅니다. 저장 용량도 5년치 약 1TB로 단일 DB가 감당할 수준이라 문제가 아닙니다. 결국 '읽기가 지배한다'는 특성이 캐시 우선 조회, 최단 읽기 경로 등 이후 모든 설계 결정을 지배하게 됩니다."
  },
  {
    "courseSlug": "lv4-system-design-url-shortener",
    "question": "키 생성 서비스(KGS) 패턴에서 키 '범위'를 서버마다 미리 떼어 주는 방식의 핵심 이점은 무엇인가요?",
    "option1": "각 서버가 조율 없이 로컬 카운터만으로 충돌 없는 키를 발급할 수 있다",
    "option2": "키가 순차적으로 발급되어 다음 키를 쉽게 예측할 수 있다",
    "option3": "해시 충돌이 발생해도 재시도 없이 자동으로 해결된다",
    "option4": "서버가 죽어도 배정된 범위의 키를 하나도 잃지 않는다",
    "answerIndex": 0,
    "explanation": "범위를 미리 떼어 주면 각 서버는 자기 범위 안에서 로컬 카운터만 올리면 되므로, 전역 카운터라는 단일 장애점·병목 없이도 충돌이 원천적으로 없습니다. 대신 서버가 죽으면 그 범위는 버려지는데, 62^7 ≈ 3.5조 개라는 넉넉한 키 공간에서 100만 개쯤의 낭비는 조율을 없애는 대가로 기꺼이 지불할 수 있는 비용입니다."
  },
  {
    "courseSlug": "lv4-system-design-url-shortener",
    "question": "클릭 분석이 핵심 상품인 URL 단축 서비스가 리다이렉트에 302를 선택하는 이유는 무엇인가요?",
    "option1": "302가 301보다 브라우저 캐시를 더 오래 유지해 서버 부하가 줄기 때문",
    "option2": "301은 브라우저가 결과를 캐시해 이후 클릭이 서버에 도달하지 않아 집계가 불가능하기 때문",
    "option3": "302만이 HTTPS 환경에서 동작하는 유일한 리다이렉트 코드이기 때문",
    "option4": "301은 리다이렉트 속도가 302보다 항상 느리기 때문",
    "answerIndex": 1,
    "explanation": "301(영구 이동)을 주면 브라우저가 결과를 캐시해서 같은 링크를 다시 눌러도 서버에 오지 않고 목적지로 바로 갑니다. 서버 부하는 줄지만 클릭이 잡히지 않아 통계가 불가능하고, 만료·차단·목적지 변경도 반영되지 않습니다. 302는 매 클릭이 서버를 통과하므로 부하는 그대로지만 분석과 즉시 반영이 가능합니다. 요구사항(분석이 상품)이 상태 코드 하나까지 결정하는 대표적 트레이드오프입니다."
  },
  {
    "courseSlug": "lv4-system-design-url-shortener",
    "question": "존재하지 않는 키를 대량 조회하는 '캐시 관통' 공격에서 블룸 필터가 유효한 방어가 되는 이유는 무엇인가요?",
    "option1": "모든 키의 원본 URL을 압축 저장해 DB 조회를 완전히 대체하기 때문",
    "option2": "'있다'는 판정이 100% 정확해 캐시 적중률을 끌어올리기 때문",
    "option3": "'없다'는 판정은 확실하므로 없는 키 요청을 DB 앞에서 걸러 낼 수 있기 때문",
    "option4": "공격자의 IP를 식별해 요청 자체를 차단해 주기 때문",
    "answerIndex": 2,
    "explanation": "존재하지 않는 키는 캐시에 없으므로 요청이 전부 DB로 관통해 DB가 먼저 쓰러집니다. 블룸 필터는 확률적 자료구조라 '있다'는 답에는 오차(실제로는 없을 수 있음)가 있지만, '없다'는 답만큼은 확실합니다. 이 성질 덕분에 없는 키 폭격을 DB에 닿기 전 메모리 단계에서 걸러 낼 수 있고, 짧은 TTL의 네거티브 캐시와 함께 겹으로 쓰입니다."
  },
  {
    "courseSlug": "lv4-system-design-chat",
    "question": "실시간 시세 스트림에는 SSE가 적합하지만 채팅에는 WebSocket이 더 적합한 근본적인 이유는 무엇인가요?",
    "option1": "SSE는 HTTPS에서 동작하지 않기 때문에",
    "option2": "SSE는 서버→클라이언트 단방향 스트림이라 방송에는 충분하지만, 채팅은 양방향 저지연 대화가 필요하기 때문에",
    "option3": "WebSocket이 SSE보다 항상 대역폭을 적게 쓰기 때문에",
    "option4": "SSE는 자동 재접속을 지원하지 않기 때문에",
    "answerIndex": 1,
    "explanation": "SSE는 서버가 클라이언트로 일방적으로 밀어주는 단방향 스트림이라 시세처럼 '방송'이면 충분합니다. 반면 채팅은 클라이언트도 수시로 메시지를 밀어넣어야 하는 양방향 대화이므로, 하나의 연결 위에서 양쪽 모두 push할 수 있는 WebSocket이 자연스러운 선택입니다. 참고로 SSE는 자동 재접속을 오히려 내장하고 있고, HTTPS에서도 잘 동작합니다."
  },
  {
    "courseSlug": "lv4-system-design-chat",
    "question": "유저 A는 chat-server-1에, 유저 B는 chat-server-7에 WebSocket으로 접속해 있습니다. A의 메시지를 B에게 전달하는 가장 일반적인 확장 가능한 구조는?",
    "option1": "로드밸런서가 두 유저를 항상 같은 서버로 몰아 붙인다",
    "option2": "chat-server-1이 나머지 모든 서버에 HTTP로 순차 질의해 B를 찾는다",
    "option3": "Pub/Sub 백본(예: Valkey)에 채널 메시지를 발행하고, 각 서버가 구독해 자기에게 붙은 참여자에게만 소켓으로 전달한다",
    "option4": "B의 브라우저가 1초마다 모든 서버에 폴링해서 새 메시지를 찾는다",
    "answerIndex": 2,
    "explanation": "Pub/Sub 백본에 메시지를 한 번 발행하면 구독 중인 모든 채팅 서버가 받아, 각자 자기에게 연결된 참여자에게만 fan-out합니다. 서버가 늘어나도 구조가 그대로 확장되죠. traderbox의 MarketBus가 Valkey 채널(market.ticks)로 시세를 각 인스턴스에 뿌리는 것과 같은 구조입니다. 같은 서버로 몰아 붙이는 방식은 방 참여자가 여러 서버에 흩어지는 걸 막을 수 없어 근본 해법이 못 됩니다."
  },
  {
    "courseSlug": "lv4-system-design-chat",
    "question": "채팅 메시지의 순서 보장에 대한 설명으로 옳은 것은?",
    "option1": "전 세계 모든 메시지에 전역 순서를 부여해야 사용자 경험이 성립한다",
    "option2": "클라이언트 기기가 찍은 타임스탬프를 순서 기준으로 쓰는 것이 가장 정확하다",
    "option3": "순서는 채널(방) 단위로만 보장하면 충분하며, 채널 내 단조 증가 ID로 순서·페이지네이션·동기화 커서를 함께 해결할 수 있다",
    "option4": "순서 보장은 저장소가 자동으로 해 주므로 설계에서 고려할 필요가 없다",
    "answerIndex": 2,
    "explanation": "필요한 것은 '같은 방을 보는 사람들이 같은 순서를 보는 것'뿐이므로, 보장 범위를 채널 단위로 좁히면 전역 순서라는 분산 시스템의 난제를 피할 수 있습니다. 채널 내에서만 단조 증가하는 ID 하나로 정렬 순서, 커서 기반 페이지네이션, 오프라인 동기화 커서까지 함께 풀립니다. 클라이언트 시계는 기기마다 어긋나므로(클럭 스큐) 순서 기준으로 쓰면 안 되고 표시용으로만 씁니다."
  },
  {
    "courseSlug": "lv4-system-design-chat",
    "question": "오프라인이었던 유저가 재접속했을 때 밀린 메시지를 받아 가는 방식과, 이때 발생할 수 있는 문제의 대비책으로 옳은 조합은?",
    "option1": "마지막 수신 메시지 ID(커서)를 서버에 알려 그 이후 메시지만 받고, 중복 수신에 대비해 클라이언트가 메시지 ID 기준으로 dedupe한다",
    "option2": "서버가 모든 이력을 처음부터 다시 전송하고, 중복은 서버가 완벽히 막아 주므로 클라이언트는 신경 쓰지 않는다",
    "option3": "오프라인 동안의 메시지는 유실을 허용하고 재접속 이후 것만 받는다",
    "option4": "클라이언트가 접속 시각 타임스탬프를 보내면 서버가 그 시각 이후 메시지를 추정해서 보낸다",
    "answerIndex": 0,
    "explanation": "재접속 시 '내 커서는 여기까지'라고 알리면 서버가 그 이후 메시지만 몰아 전달하는 커서 기반 inbox 동기화가 정석입니다. 다만 실시간 push와 동기화가 겹치거나 재전송이 일어나면 같은 메시지가 두 번 도착할 수 있는데, 네트워크에서 '정확히 한 번 전송'은 불가능하므로 '최소 한 번 전송 + 수신 측이 msgId로 멱등 처리(dedupe)'가 실제 구현 가능한 전달 보장의 모양입니다."
  },
  {
    "courseSlug": "lv4-system-design-chat",
    "question": "읽음 표시와 온라인 상태(presence) 설계에 대한 설명으로 옳은 것은?",
    "option1": "읽음은 메시지×참여자마다 read 플래그를 남기는 것이 쓰기 비용이 가장 적다",
    "option2": "presence는 메시지 본문과 똑같이 절대 유실·지연되면 안 되는 강한 일관성이 필요하다",
    "option3": "읽음은 참여자별 '어디까지 읽음' 커서 하나로 관리하고, presence는 하트비트+TTL 기반의 최종 일관성으로 충분하다",
    "option4": "300명 그룹에서 읽음·presence 이벤트를 전원에게 그대로 실시간 중계해도 부하는 무시할 수준이다",
    "answerIndex": 2,
    "explanation": "읽음을 메시지별 플래그로 만들면 300명 방에서 쓰기가 폭발하지만, 참여자별 커서(포인터) 하나면 몇백 개를 읽어도 UPDATE 한 번이고 안 읽은 수도 '최신 ID − 커서'로 즉시 계산됩니다. presence는 초록 불이 몇 초 늦게 꺼져도 아무 피해가 없으므로 하트비트+TTL의 최종 일관성이면 충분합니다. 반대로 300명 방에서 이벤트를 전원에게 그대로 중계하면 틱당 최대 9만 건(300×300)까지 불어나므로 batch·throttle 같은 절감책이 필요합니다."
  },
  {
    "courseSlug": "lv4-system-design-newsfeed",
    "question": "뉴스피드가 '읽기 폭발형' 시스템이라 불리는 근본 이유로 가장 정확한 것은?",
    "option1": "피드 데이터가 이미지·영상 때문에 용량이 크기 때문",
    "option2": "쓰기 1건이 팔로워 수만큼의 읽기(노출) 요구로 증폭되는 구조이기 때문",
    "option3": "글 작성 트랜잭션이 팔로워 수에 비례해 느려지기 때문",
    "option4": "데이터베이스가 시간순 정렬을 지원하지 않기 때문",
    "answerIndex": 1,
    "explanation": "게시물 1건은 그 작성자의 팔로워 전원의 피드에 나타나야 합니다. 평균 팔로워 200명이면 쓰기 1건이 200건의 피드 노출 요구로 부풀어, 읽기:쓰기 비율이 수백 대 일을 넘는 증폭 구조가 됩니다. 그래서 설계의 중심 질문이 'N개 소스의 병합 정렬 비용을 읽을 때 낼 것인가, 쓸 때 낼 것인가'로 좁혀집니다."
  },
  {
    "courseSlug": "lv4-system-design-newsfeed",
    "question": "풀(pull) 모델과 푸시(push) 모델의 비용 특성을 옳게 짝지은 것은?",
    "option1": "풀: 읽기 지연이 팔로잉 수에 비례 / 푸시: 쓰기 비용이 팔로워 수에 비례",
    "option2": "풀: 저장 중복이 심하다 / 푸시: 글 원본 한 벌만 저장한다",
    "option3": "풀: 게시가 느리다 / 푸시: 조회가 팔로잉 수에 비례해 느려진다",
    "option4": "풀: 캐시를 쓸 수 없다 / 푸시: Kafka를 쓸 수 없다",
    "answerIndex": 0,
    "explanation": "풀 모델은 요청 시 팔로잉 N명의 최신 글을 모아 병합하므로 읽기 지연이 N에 비례하고, 대신 쓰기는 INSERT 1건으로 저렴하며 저장 중복이 없습니다. 푸시 모델은 게시 시점에 팔로워 각자의 피드함에 zadd 하므로 쓰기 비용이 팔로워 수에 비례하고, 대신 조회는 이미 정렬된 피드함을 읽기만 하면 되어 사실상 상수 시간입니다. 병합 비용이 사라지는 게 아니라 읽기↔쓰기 사이로 옮겨지는 것입니다."
  },
  {
    "courseSlug": "lv4-system-design-newsfeed",
    "question": "푸시 모델에서 팬아웃을 게시 API가 직접 수행하지 않고 'Kafka 이벤트 → 팬아웃 워커'의 비동기 파이프라인으로 처리하는 이유는?",
    "option1": "Valkey sorted set은 동기 호출로는 쓸 수 없기 때문",
    "option2": "Kafka를 거치면 팬아웃 쓰기 총량 자체가 줄어들기 때문",
    "option3": "게시 API가 팔로워 수만 명의 피드함 쓰기를 다 마치고 응답하면 게시가 수십 초씩 걸리므로, '수 초 내 반영'이라는 느슨한 요구사항을 근거로 적재를 뒤로 미루는 것",
    "option4": "동기로 처리하면 피드 정렬 순서가 뒤섞이기 때문",
    "answerIndex": 2,
    "explanation": "팬아웃 쓰기 총량은 비동기로 해도 그대로입니다. 핵심은 사용자 경험과 요구사항입니다. 게시 API는 글 저장과 이벤트 발행만 하고 즉시 응답하며, 피드함 적재는 워커가 뒤에서 처리합니다. 이것이 허용되는 근거는 게시 반영 목표가 '즉시'가 아니라 '수 초 내'로 잡혀 있기 때문으로, 요구사항의 한 줄이 아키텍처의 갈래를 결정하는 예입니다."
  },
  {
    "courseSlug": "lv4-system-design-newsfeed",
    "question": "팔로워 1,000만 명의 셀럽 계정 문제에 대한 하이브리드 전략의 설명으로 옳은 것은?",
    "option1": "셀럽의 글은 더 많은 워커를 투입해 푸시 팬아웃을 더 빠르게 돌린다",
    "option2": "일반 유저는 푸시로 피드함에 적재하고, 셀럽 글은 팬아웃하지 않은 채 조회 시점에 풀로 가져와 피드함과 병합한다",
    "option3": "셀럽의 글은 피드에서 제외하고 별도 알림으로만 전달한다",
    "option4": "일반 유저는 풀, 셀럽은 푸시로 처리해 쓰기 부하를 분산한다",
    "answerIndex": 1,
    "explanation": "셀럽 글 1건을 푸시하면 zadd 1,000만 번이 필요해(초당 1만 건 처리 워커 기준 약 17분) '수 초 내 반영' 요구가 무너지고 다른 유저의 팬아웃까지 밀립니다. 그래서 셀럽은 팬아웃하지 않고, 피드를 읽는 시점에 내 피드함(푸시분)과 팔로우한 셀럽들의 최신 글(풀분)을 병합합니다. 셀럽은 극소수라 풀 쪽 병합 소스가 적고, 수백만 명이 같은 글을 읽으니 캐시 적중률도 매우 높습니다. 방향을 뒤집은 4번은 오답입니다."
  },
  {
    "courseSlug": "lv4-system-design-newsfeed",
    "question": "인기 게시물의 좋아요 카운터에서 발생하는 핫키 문제와 그 대응 패턴으로 옳은 것은?",
    "option1": "같은 행을 갱신하려는 트랜잭션이 잠금 경합을 일으키므로, 캐시(Valkey)에서 INCR로 집계하고 주기적으로 델타를 DB에 플러시한다",
    "option2": "카운터 컬럼에 인덱스를 추가해 UPDATE 속도를 높인다",
    "option3": "좋아요 요청을 CDN에서 처리해 DB 접근을 없앤다",
    "option4": "정확성이 중요하므로 결제 잔액에도 동일한 캐시 집계 패턴을 적용한다",
    "answerIndex": 0,
    "explanation": "인기 글 하나에 좋아요가 초당 수만 건 몰리면 같은 행(row)에 대한 UPDATE 잠금 경합으로 DB가 먼저 무너집니다. 그래서 쓰기 폭주는 메모리 연산인 캐시 INCR로 흡수하고, 플러셔가 주기적으로(예: 5초마다) 누적 델타를 DB에 반영합니다. 이 패턴은 플러시 주기만큼의 표시 오차를 허용하는 거래이므로 좋아요 수에는 적합하지만, 정확성이 필수인 결제 잔액에는 쓰면 안 됩니다."
  },
  {
    "courseSlug": "lv4-tech-leadership-adr",
    "question": "ADR(아키텍처 결정 기록)의 기본 구조로 올바른 것은?",
    "option1": "요구사항 → 구현 → 테스트",
    "option2": "맥락(Context) → 결정(Decision) → 결과(Consequences)",
    "option3": "문제 → 원인 → 재발 방지 대책",
    "option4": "목표 → 일정 → 담당자",
    "answerIndex": 1,
    "explanation": "ADR은 '어떤 제약과 상황이었나(맥락) → 무엇을 하기로 했나(결정) → 그 선택으로 무엇을 얻고 무엇을 포기했나(결과)'의 순서로 결정 하나를 기록합니다. 여기에 기각한 대안과 그 이유를 더하면, 3년 뒤에도 사고 과정을 재생할 수 있는 문서가 됩니다. 3번은 장애 포스트모템의 구조에 가깝습니다."
  },
  {
    "courseSlug": "lv4-tech-leadership-adr",
    "question": "'일방향 문(one-way door)'과 '양방향 문(two-way door)' 결정에 대한 설명으로 옳은 것은?",
    "option1": "모든 기술 결정은 일방향 문이므로 항상 신중하게 내려야 한다",
    "option2": "양방향 문은 되돌리기 어려운 결정이므로 문서와 리뷰가 필수다",
    "option3": "되돌리기 쉬운 결정(양방향 문)은 빠르게, 되돌리기 어려운 결정(일방향 문)은 신중하게 통과한다",
    "option4": "일방향 문 결정은 팀장만 내릴 수 있다",
    "answerIndex": 2,
    "explanation": "결정의 무게에 맞는 절차를 고르는 것이 핵심입니다. 라이브러리 선택처럼 틀리면 되돌리면 되는 양방향 문은 빠르게 시도하고, DB 교체·공개 API 계약·서비스 경계처럼 되돌리기 비싼 일방향 문은 문서·대안 비교·리뷰를 거쳐 천천히 통과합니다. 모든 결정을 일방향 문처럼 다루면 팀이 느려지고, 반대면 장기 부채가 생깁니다."
  },
  {
    "courseSlug": "lv4-tech-leadership-adr",
    "question": "이미 승인된 ADR의 결정을 나중에 뒤집게 됐을 때 올바른 처리 방법은?",
    "option1": "기존 ADR 파일을 열어 새 결정 내용으로 수정한다",
    "option2": "기존 ADR 파일을 삭제하고 새로 작성한다",
    "option3": "새 ADR을 작성하고, 기존 ADR은 상태만 'superseded(대체됨)'로 바꿔 남겨 둔다",
    "option4": "ADR은 한번 승인되면 어떤 경우에도 뒤집을 수 없다",
    "answerIndex": 2,
    "explanation": "ADR은 불변 기록입니다. 옛 문서를 고치거나 지우면 '그때 왜 그렇게 판단했는지'라는 역사가 사라집니다. 대신 무엇이 달라져서 재검토하는지를 담은 새 ADR을 쓰고, 기존 ADR의 상태만 'Superseded by ADR-NNN'으로 바꿉니다. 이렇게 하면 저장소에 판단의 역사(결정의 지층)가 쌓입니다."
  },
  {
    "courseSlug": "lv4-tech-leadership-adr",
    "question": "설계 리뷰(디자인 독 리뷰)의 본질적인 목적으로 이 강좌가 강조한 것은?",
    "option1": "상급자의 승인 도장을 받는 공식 절차",
    "option2": "결함을 코드가 되기 전, 가장 싼 시점에 찾아내는 장치",
    "option3": "작성자의 역량을 평가해 등급을 매기는 자리",
    "option4": "일정 지연의 책임 소재를 미리 정해 두는 자리",
    "answerIndex": 1,
    "explanation": "문서 단계의 결함은 문장 몇 줄 고치면 끝나지만, 배포 후의 결함은 장애 대응과 마이그레이션 비용으로 갚아야 합니다. 그래서 리뷰는 승인 절차가 아니라 '결함을 싸게 찾는 장치'이고, 리뷰어가 찾아 준 구멍은 공격이 아니라 가장 싼 시점에 도착한 버그 리포트입니다. 좋은 리뷰어는 '왜 안 했나'보다 '어떤 제약이 있었나'를 묻습니다."
  },
  {
    "courseSlug": "lv4-tech-leadership-adr",
    "question": "기술 부채에 대한 이 강좌의 관점으로 옳은 것은?",
    "option1": "부채는 악이므로 리더는 부채를 0으로 만드는 것을 목표로 해야 한다",
    "option2": "의도적 부채는 무엇을 미뤘는지 알고 기록과 상환 계획이 있는 부채이며, 리더의 일은 부채의 이자율을 관리하는 것이다",
    "option3": "부채 상환은 기술적 문제이므로 경영진에게는 기술 용어 그대로 설명하는 것이 가장 효과적이다",
    "option4": "문서와 현실의 불일치(문서 드리프트)는 코드가 아니므로 부채에 해당하지 않는다",
    "answerIndex": 1,
    "explanation": "부채는 출시를 앞당기려 미래 시간을 당겨 쓰는 대출이며, 문제는 빚 자체가 아니라 이자율을 아무도 관리하지 않는 상태입니다. 의도적 부채(인지·기록·상환 계획 있음)와 무지성 부채(인식조차 없어 장애로 청구서가 오는 부채)를 구분하고, 상환을 로드맵에 올릴 땐 '분기당 3주치 개발력이 이자로 나간다'처럼 사업 언어로 비용을 증명해야 합니다. 문서 드리프트도 잘못된 판단을 유발하는 이자가 붙는 부채입니다."
  }
]
