JavaScript 조건문과 반복문: 변수 값의 흐름부터 추적하기

2026.09.05·수정 2026.09.12·약 19분·작성: 해비·블로그 소개

이 글에서 정리하는 내용

JavaScript 조건문과 반복문을 처음 배우는 분을 위해 변수에 값을 담고, 조건을 판별하고, 반복할 때 값이 어떻게 바뀌는지 한 줄씩 추적합니다. 배열 지식 없이 실행할 수 있습니다.

준비물은 텍스트 편집기와 Node.js입니다. Node.js 공식 다운로드에서 LTS를 설치한 뒤 새 터미널에서 node --version으로 설치를 확인하세요. 새 실습 폴더에 control-flow.mjs를 만들고 아래 완성 코드를 저장합니다. 해당 폴더에서 명령을 실행하며, 별도 라이브러리 설치는 필요하지 않습니다.

예제에서 28은 숫자(number), 따옴표로 감싼 안내 문구는 문자열(string), truefalse는 참·거짓(boolean)입니다. =는 오른쪽 값을 왼쪽 이름에 저장하는 대입이고 ===는 비교입니다. 이름은 대소문자를 구분합니다. console.log는 현재 값을 터미널에 출력해 실행 결과를 살펴보는 함수입니다.

변수와 연산으로 조건 만들기

JavaScript 조건문과 반복문을 쓰기 전에 값의 이름부터 정합니다. 다시 대입하지 않을 값은 const, 실행 중 바뀌는 값은 let으로 선언합니다. const는 선언 뒤 같은 이름에 다른 값을 대입할 수 없고, let은 누적값이나 횟수처럼 변화를 표현할 때 알맞습니다.

비교 연산은 두 값을 비교해 true 또는 false를 만듭니다. >=는 이상, ===는 값과 타입이 같은지 확인합니다. 이 글처럼 참·거짓 조건을 연결할 때 논리 AND인 &&는 두 조건이 모두 참일 때 참이고, OR인 ||는 하나만 참이어도 참입니다. 일반적인 값에 사용하면 두 연산자는 항상 boolean으로 바꾸는 것이 아니라 피연산자 중 하나를 반환합니다. !는 참과 거짓을 뒤집습니다. 초보 단계에서는 예측하기 쉬운 ===를 기본으로 삼는 편이 좋습니다.

빈 노트와 노란 원을 담은 정리함으로 값에 이름을 붙여 보관하는 개념을 표현한 삽화
변수는 값을 이름으로 구분해 사용하는 수단입니다. 정리함은 이 역할을 표현한 비유입니다.

if와 switch로 한 경로 선택하기

if는 위에서 아래로 조건을 검사하고 처음 참인 블록 하나를 실행합니다. 첫 조건이 거짓이면 else if를 확인하고, 모두 거짓이면 else로 갑니다. 여러 블록이 차례대로 모두 실행되는 구조가 아니라는 점이 핵심입니다.

switch는 하나의 값을 여러 고정 후보와 비교할 때 읽기 쉽습니다. 일치한 case에서 실행을 시작하므로 각 경우 끝에 break를 두어 다음 case까지 이어지는 것을 막습니다. 범위 비교는 if가 자연스럽고, “beginner”, “intermediate” 같은 정확한 값 선택은 switch가 잘 맞습니다.

for와 while의 값 추적하기

for 괄호에는 시작값, 계속할 조건, 한 번 실행한 뒤의 변화가 나란히 있습니다. 예제의 round는 1에서 시작해 3 이하인 동안 실행되고 매번 1씩 커집니다. total += round는 현재 total에 round를 더해 다시 저장합니다. 따라서 total은 1, 3, 6으로 변합니다.

while은 반복 횟수보다 종료 조건을 먼저 표현하고 싶을 때 씁니다. 본문에서 countdown을 줄이지 않으면 조건이 계속 참이라 반복이 끝나지 않습니다. 코드를 쓰기 전에 “어떤 값이 바뀌어 언제 조건이 거짓이 되는가”를 문장으로 답하면 무한 반복을 예방할 수 있습니다.

같은 원을 세 번 찍은 도장과 종이로 반복 작업을 표현한 삽화
반복문은 같은 처리 규칙을 되풀이합니다. 실제 반복 횟수와 누적값은 아래 코드로 확인합니다.

입력에 따라 움직이는 작은 프로그램

아래 파일은 배열이나 함수를 전제로 하지 않습니다. 위에서 아래로 실행하면서 날씨 안내, 학습 단계 선택, 합계 계산, 카운트다운을 차례로 확인합니다. 파일을 저장한 뒤 Node.js에서 실행하면 됩니다.

실습 파일

파일 경로를 확인하고 같은 프로젝트 안에 저장하세요. 이미지 등 소스 목록에 없는 파일과 실행 안내는 실습 ZIP에 포함되어 있습니다.

조건과 반복문의 실행 순서를 따라 결과를 계산합니다. 수정 전은 시작 상태, 수정 후는 비교할 완성 상태입니다.

수정 전

control-flow.mjs

const temperature = 28;
const hasWater = false;
let message = '';

if (temperature > 28 && !hasWater) {
  message = '더우니 물을 챙기세요.';
} else if (temperature >= 28) {
  message = '물을 자주 마시세요.';
} else if (temperature >= 20) {
  message = '산책하기 좋은 날씨입니다.';
} else {
  message = '겉옷을 챙기세요.';
}
console.log(message);

const level = 'beginner';
switch (level) {
  case 'beginner':
    console.log('기초 문제 3개');
    break;
  case 'intermediate':
    console.log('응용 문제 2개');
    break;
  default:
    console.log('복습 문제 1개');
}

let total = 0;
for (let round = 1; round < 3; round += 1) {
  total += round;
  console.log(`for ${round}: ${total}`);
}

let countdown = 3;
while (countdown > 0) {
  console.log(`while: ${countdown}`);
  countdown -= 1;
}
console.log('시작!');

verify.mjs

import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { runInNewContext } from 'node:vm';

const source = readFileSync(new URL('./control-flow.mjs', import.meta.url), 'utf8');
function run(temperature, hasWater, countdown = 3) {
  const lines = [];
  const changed = source
    .replace('const temperature = 28;', `const temperature = ${temperature};`)
    .replace('const hasWater = false;', `const hasWater = ${hasWater};`)
    .replace('let countdown = 3;', `let countdown = ${countdown};`);
  runInNewContext(
    changed,
    { console: { log: (value) => lines.push(value) } },
    { timeout: 1000 },
  );
  return lines;
}
assert.equal(run(27, false)[0], '산책하기 좋은 날씨입니다.');
assert.equal(run(28, false)[0], '더우니 물을 챙기세요.');
assert.equal(run(29, true)[0], '물을 자주 마시세요.');
assert.equal(run(19, false)[0], '겉옷을 챙기세요.');
assert.equal(run(20, false)[0], '산책하기 좋은 날씨입니다.');
assert.deepEqual(run(28, false).slice(1), [
  '기초 문제 3개',
  'for 1: 1',
  'for 2: 3',
  'for 3: 6',
  'while: 3',
  'while: 2',
  'while: 1',
  '시작!',
]);
assert.equal(
  run(28, false, 0).some((line) => line.startsWith('while:')),
  false,
);
console.log('조건 경계, for 누적, while 0회 검증 통과');

수정 방법

control-flow.mjs

control-flow.mjs에서 아래 차이가 있는 위치를 수정하세요. -는 삭제할 줄, +는 추가할 줄이며 표시는 실제 코드에 넣지 않습니다.

조건과 반복문의 실행 순서를 따라 결과를 계산합니다.

--- 수정 전/control-flow.mjs
+++ 수정 후/control-flow.mjs
@@ -2,7 +2,7 @@
 const hasWater = false;
 let message = '';
 
-if (temperature > 28 && !hasWater) {
+if (temperature >= 28 && !hasWater) {
   message = '더우니 물을 챙기세요.';
 } else if (temperature >= 28) {
   message = '물을 자주 마시세요.';
@@ -26,7 +26,7 @@
 }
 
 let total = 0;
-for (let round = 1; round < 3; round += 1) {
+for (let round = 1; round <= 3; round += 1) {
   total += round;
   console.log(`for ${round}: ${total}`);
 }

수정 후 탭의 전체 파일과 비교한 뒤 본문의 확인 절차를 실행하세요.

verify.mjs

이 파일은 변경하지 않습니다. 다른 파일을 수정하는 동안 기존 내용을 유지하세요.

수정 후

control-flow.mjs

const temperature = 28;
const hasWater = false;
let message = '';

if (temperature >= 28 && !hasWater) {
  message = '더우니 물을 챙기세요.';
} else if (temperature >= 28) {
  message = '물을 자주 마시세요.';
} else if (temperature >= 20) {
  message = '산책하기 좋은 날씨입니다.';
} else {
  message = '겉옷을 챙기세요.';
}
console.log(message);

const level = 'beginner';
switch (level) {
  case 'beginner':
    console.log('기초 문제 3개');
    break;
  case 'intermediate':
    console.log('응용 문제 2개');
    break;
  default:
    console.log('복습 문제 1개');
}

let total = 0;
for (let round = 1; round <= 3; round += 1) {
  total += round;
  console.log(`for ${round}: ${total}`);
}

let countdown = 3;
while (countdown > 0) {
  console.log(`while: ${countdown}`);
  countdown -= 1;
}
console.log('시작!');

verify.mjs

import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { runInNewContext } from 'node:vm';

const source = readFileSync(new URL('./control-flow.mjs', import.meta.url), 'utf8');
function run(temperature, hasWater, countdown = 3) {
  const lines = [];
  const changed = source
    .replace('const temperature = 28;', `const temperature = ${temperature};`)
    .replace('const hasWater = false;', `const hasWater = ${hasWater};`)
    .replace('let countdown = 3;', `let countdown = ${countdown};`);
  runInNewContext(
    changed,
    { console: { log: (value) => lines.push(value) } },
    { timeout: 1000 },
  );
  return lines;
}
assert.equal(run(27, false)[0], '산책하기 좋은 날씨입니다.');
assert.equal(run(28, false)[0], '더우니 물을 챙기세요.');
assert.equal(run(29, true)[0], '물을 자주 마시세요.');
assert.equal(run(19, false)[0], '겉옷을 챙기세요.');
assert.equal(run(20, false)[0], '산책하기 좋은 날씨입니다.');
assert.deepEqual(run(28, false).slice(1), [
  '기초 문제 3개',
  'for 1: 1',
  'for 2: 3',
  'for 3: 6',
  'while: 3',
  'while: 2',
  'while: 1',
  '시작!',
]);
assert.equal(
  run(28, false, 0).some((line) => line.startsWith('while:')),
  false,
);
console.log('조건 경계, for 누적, while 0회 검증 통과');

control-flow.mjs 전체 코드 보기

실행 명령은 node control-flow.mjs입니다. 온도를 18로 바꾸면 앞의 온도 조건이 모두 거짓이므로 “겉옷을 챙기세요.”가 출력됩니다. hasWater를 true로 바꾸면 첫 조건의 !hasWater가 false가 되어 28도 이상인 다음 분기로 이동하여 “물을 자주 마시세요.”를 출력합니다. 이렇게 입력 하나만 바꾸고 출력 차이를 비교하면 조건식의 역할이 선명해집니다.

조건식을 읽을 때는 한 번에 머릿속으로 계산하지 말고 작은 식부터 적습니다. 예를 들어 temperature >= 28 && !hasWater는 먼저 온도 비교 결과를 적고, 물 준비 여부에 NOT을 적용한 결과를 적은 다음, 마지막에 두 결과를 AND로 합칩니다. 이 순서를 지키면 괄호가 늘어나도 어느 부분에서 예상이 달라졌는지 찾기 쉽습니다.

짧은 조건이라도 경계값을 꼭 확인해야 합니다. 28 이상이라는 규칙에는 27, 28, 29를 각각 넣어 보세요. 28에서 참이어야 하는데 >를 썼다면 경계에서만 오류가 드러납니다. 논리 OR도 양쪽이 모두 거짓인 경우, 왼쪽만 참인 경우, 오른쪽만 참인 경우를 나누어 예측하면 뜻을 정확히 익힐 수 있습니다.

반복문은 시작 전, 본문 실행 후, 갱신 후의 세 시점을 구분합니다. for에서는 round가 1로 만들어진 뒤 조건을 검사하고, 본문에서 total이 바뀐 다음 round가 증가합니다. while에서는 갱신식이 괄호에 없기 때문에 본문 안에서 직접 countdown을 줄여야 합니다. 같은 출력 횟수라도 두 문법이 종료를 표현하는 위치가 다릅니다.

디버깅할 때는 반복문 안에 현재 제어 변수와 누적값을 함께 출력하세요. total만 출력하면 어느 회차의 결과인지 놓치기 쉽습니다. 예상표에는 회차, 조건 결과, 실행 전 total, 더한 값, 실행 후 total을 적습니다. 실제 출력과 처음 달라지는 행이 오류가 생긴 지점입니다.

마지막으로 let의 범위를 필요 이상으로 넓히지 않습니다. total처럼 반복이 끝난 뒤에도 사용할 값은 반복문 바깥에 두고, round처럼 반복 제어에만 필요한 값은 for 안에서 선언합니다. 변수의 사용 범위가 좁으면 다른 코드가 실수로 값을 바꿀 가능성이 줄고 프로그램의 흐름도 더 쉽게 설명할 수 있습니다.

연습할 때는 먼저 입력값과 예상 출력을 적고 실행하세요. 코드를 본 뒤 결과를 맞히는 과정이 제어 흐름 학습의 핵심입니다. if 순서를 바꾸었을 때 넓은 조건이 좁은 조건을 가리지 않는지, switch에서 break를 하나 제거하면 어디까지 이어지는지, for의 시작값과 비교 연산자를 하나씩 바꾸면 몇 번 실행되는지 확인합니다. while은 조건이 처음부터 거짓인 입력도 시험합니다. 이런 작은 변경은 새 문법을 추가하지 않고도 선언, 판정, 실행, 갱신, 종료의 전체 순서를 반복해서 익히게 해 줍니다. 변경마다 원본으로 되돌린 뒤 다음 항목을 시험하면 여러 변경이 섞여 원인을 놓치는 일도 줄어듭니다.

완료 기준은 문법을 외우는 것이 아니라 주어진 입력에서 실행할 블록과 반복 횟수, 마지막 변수 값을 실행 전에 설명하는 것입니다. 그 설명과 콘솔 결과가 다르면 첫 차이가 생긴 조건부터 다시 계산합니다.

연습 결과를 저장할 때는 바꾼 입력값과 예상값도 함께 기록하세요. 나중에 코드를 다시 보았을 때 출력만 남긴 기록보다 조건 선택의 근거와 반복 종료 시점을 훨씬 빠르게 복원할 수 있습니다.

실행 결과와 점검 기준

기본 입력의 예상 출력은 아래와 같습니다. 반복문을 읽을 때는 출력만 보지 말고 각 줄 직전의 변수 값을 종이에 적어 보세요. 조건을 바꿨는데 예상과 다르면 괄호 안을 별도 console.log로 출력해 true/false부터 확인합니다.

더우니 물을 챙기세요.
기초 문제 3개
for 1: 1
for 2: 3
for 3: 6
while: 3
while: 2
while: 1
시작!

제어 흐름의 문법과 반복 형태는 MDN 제어 흐름 안내MDN 반복문 안내에서 더 확인할 수 있습니다. 이제 조건이 어느 블록을 고르는지, 반복마다 무엇이 바뀌는지 설명할 수 있다면 다음 단계로 넘어가도 좋습니다.

직접 실습: 경계값 두 곳 고치기

ZIP의 starter/control-flow.mjs에는 의도적으로 두 비교 연산자를 틀리게 넣었습니다. 28도에서 물이 없으면 준비 안내가 나오고, for는 1부터 3까지 더해야 합니다. 먼저 예상 결과를 적고 조건만 수정하세요. 함수나 배열 문법을 추가할 필요는 없습니다.

터미널에서 압축을 푼 폴더로 이동한 뒤 node starter/control-flow.mjs, node starter/verify.mjs를 실행하세요. 검증 실패 메시지의 actual은 실제값, expected는 기대값입니다. 완성본은 node complete/verify.mjs로 확인합니다. 검증 코드는 학습 코드와 분리되어 있습니다.

switch는 case 값을 엄격한 동등 비교로 판별합니다. 반복문의 break는 반복 전체를 끝내고, continue는 현재 회차의 남은 본문을 건너뜁니다. for에서 continue 뒤에도 갱신식이 실행되지만 while에서 갱신을 건너뛰면 종료되지 않을 수 있으니 갱신 위치를 확인하세요.

시작 코드와 완성 코드 ZIP 내려받기

이 글이 도움이 되었나요?

조회 중

JavaScript 학습 순서

필수 13개 · 전체 14개

읽음 기록 관리

전체 과정 목차 (14개)
  1. 필수 학습 · JavaScript 조건문과 반복문: 변수 값의 흐름부터 추적하기 현재 글
  2. 필수 학습 · JavaScript 함수와 객체, import export로 모듈 나누기
  3. 필수 학습 · JavaScript 배열 메서드: map filter forEach reduce 차이
  4. 필수 학습 · JavaScript 객체 참조와 불변 갱신: 중첩 객체를 안전하게 바꾸기
  5. 필수 학습 · JavaScript reduce 사용법: 배열 누적 계산을 이해하는 기준
  6. 필수 학습 · JavaScript DOM 폼 만들기: 입력 검증과 접근성 처리
  7. 필수 학습 · JavaScript 투두리스트 만들기: 상태와 이벤트 위임으로 완성하기
  8. 필수 학습 · JavaScript URLSearchParams 사용법: URL 파라미터 읽고 수정하기
  9. 필수 학습 · JavaScript Date UTC KST 차이: 시간대 변환 기준 잡기
  10. 필수 학습 · JavaScript 실행 컨텍스트 기준: 스코프 호이스팅 클로저 연결하기
  11. 필수 학습 · JavaScript fetch 오류 처리: Promise부터 404까지
  12. 필수 학습 · JavaScript 이벤트 루프: Promise와 await 실행 순서 추적하기
  13. 필수 학습 · JavaScript 고급 비동기: AbortController와 최신 요청 경쟁 제어
  14. 선택 참고 · GSAP이 처음일 때 기초 사용법: 설치부터 기본 애니메이션까지

새 글 받아보기

RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.

RSS 피드 구독하기

댓글 남기기