Skip to main content
Summary

프로그램은 순서대로만 움직이지 않는다

앞 장에서 우리는 값을 만들고, 비교하고, 논리 연산으로 조건을 표현하는 방법을 배웠습니다.

val score = 85
val passed = score >= 60

하지만 실제 프로그램은 여기서 멈추지 않습니다. passed가 true인지 false인지에 따라 다른 일을 해야 합니다. 또 같은 작업을 여러 번 반복해야 할 때도 있습니다.

흐름 제어는 프로그램의 실행 방향을 정하는 문법입니다.

  • 조건에 따라 다른 코드 실행하기

  • 여러 경우 중 하나 선택하기

  • 같은 작업을 정해진 횟수만큼 반복하기

  • 특정 조건에서 반복을 멈추거나 건너뛰기

프로그램은 결국 조건을 판단하고 반복을 제어하는 구조 위에서 움직입니다.


흐름 제어로 프로그램의 판단력 만들기

if로 조건에 따라 다르게 실행하기

가장 기본적인 조건문은 if입니다.

fun main() {
    val score = 72

    if (score >= 60) {
        println("합격입니다")
    }
}

if 뒤의 조건식이 true이면 중괄호 안의 코드가 실행됩니다. 조건식은 반드시 Boolean 값이어야 합니다.

if (score >= 60) {
    println("합격")
}

여기서 score >= 60은 true 또는 false가 되는 비교식입니다.

조건이 맞지 않을 때 실행할 코드는 else에 작성합니다.

fun main() {
    val score = 45

    if (score >= 60) {
        println("합격입니다")
    } else {
        println("불합격입니다")
    }
}

조건이 여러 개라면 else if를 사용할 수 있습니다.

fun main() {
    val score = 82

    if (score >= 90) {
        println("A")
    } else if (score >= 80) {
        println("B")
    } else if (score >= 70) {
        println("C")
    } else {
        println("D")
    }
}

위 코드는 위에서부터 조건을 검사합니다. 먼저 맞는 조건을 만나면 그 블록만 실행하고 나머지는 건너뜁니다. 그래서 조건의 순서가 중요합니다.

예를 들어 다음 코드는 의도와 다르게 동작합니다.

val score = 95

if (score >= 60) {
    println("D 이상")
} else if (score >= 90) {
    println("A")
}

95는 score >= 60을 먼저 만족하므로 두 번째 조건까지 가지 않습니다. 점수 등급처럼 범위가 겹치는 조건은 더 구체적인 조건을 먼저 두어야 합니다.


if를 값으로 사용하기

코틀린의 if는 단순한 문장이 아니라 값을 만들 수 있는 표현식입니다.

fun main() {
    val score = 75

    val result = if (score >= 60) {
        "합격"
    } else {
        "불합격"
    }

    println(result)
}

score >= 60이 참이면 "합격"이 result에 들어갑니다. 거짓이면 "불합격"이 들어갑니다.

이런 방식은 코틀린에서 자주 사용합니다. 변수를 먼저 만들고 나중에 값을 바꾸는 방식보다 코드가 간결하고 안전합니다.

// 덜 코틀린다운 방식
var result = ""
if (score >= 60) {
    result = "합격"
} else {
    result = "불합격"
}

// 더 코틀린다운 방식
val result = if (score >= 60) "합격" else "불합격"

코틀린에는 자바나 자바스크립트의 삼항 연산자처럼 조건 ? 값1 : 값2 문법이 없습니다. 대신 if 자체를 값으로 사용합니다.

val message = if (age >= 19) "입장 가능" else "입장 불가"

if를 값으로 사용할 때는 보통 else가 필요합니다. 조건이 거짓일 때 어떤 값을 만들지 정해야 하기 때문입니다.

val result = if (score >= 60) "합격" else "불합격"

블록을 사용할 수도 있습니다. 이때 블록의 마지막 줄이 결과값이 됩니다.

fun main() {
    val score = 95

    val grade = if (score >= 90) {
        println("우수한 점수입니다")
        "A"
    } else {
        println("조금 더 연습해 봅시다")
        "B 이하"
    }

    println(grade)
}

if 블록 안에서 여러 작업을 할 수 있지만, 값으로 사용할 때는 마지막 줄이 무엇인지 분명해야 합니다.


when으로 여러 조건 분기하기

조건이 많아지면 if - else if - else가 길어집니다.

if (grade == "A") {
    println("매우 좋음")
} else if (grade == "B") {
    println("좋음")
} else if (grade == "C") {
    println("보통")
} else {
    println("다시 확인")
}

이럴 때는 when을 사용하면 더 읽기 쉽습니다.

fun main() {
    val grade = "B"

    when (grade) {
        "A" -> println("매우 좋음")
        "B" -> println("좋음")
        "C" -> println("보통")
        else -> println("다시 확인")
    }
}

when은 괄호 안의 값과 각 조건을 비교합니다. 맞는 조건을 만나면 오른쪽 코드가 실행됩니다.

여러 값을 같은 분기로 묶을 수도 있습니다.

fun main() {
    val grade = "A"

    when (grade) {
        "A", "B" -> println("통과")
        "C" -> println("재검토")
        else -> println("미통과")
    }
}

"A" 또는 "B"이면 같은 코드가 실행됩니다.

when에서는 범위도 사용할 수 있습니다.

fun main() {
    val score = 87

    when (score) {
        in 90..100 -> println("A")
        in 80..89 -> println("B")
        in 70..79 -> println("C")
        in 0..69 -> println("D")
        else -> println("잘못된 점수")
    }
}

in 80..89는 80부터 89까지의 범위에 값이 들어 있는지 확인합니다. 앞 장에서 배운 범위 연산자가 흐름 제어에서도 자연스럽게 쓰입니다.


when을 값으로 사용하기

if처럼 when도 값을 만들 수 있습니다.

fun main() {
    val score = 87

    val grade = when (score / 10) {
        10, 9 -> "A"
        8 -> "B"
        7 -> "C"
        6 -> "D"
        else -> "F"
    }

    println(grade)
}

score / 10의 결과에 따라 문자열 값이 만들어지고, 그 값이 grade에 들어갑니다.

when을 값으로 사용할 때도 모든 경우가 처리되어야 합니다. 위 예제에서는 else가 나머지 모든 경우를 담당합니다.

괄호 안에 비교할 대상을 쓰지 않는 형태도 있습니다.

fun main() {
    val temperature = 31

    val message = when {
        temperature >= 30 -> "덥습니다"
        temperature >= 20 -> "쾌적합니다"
        temperature >= 10 -> "선선합니다"
        else -> "춥습니다"
    }

    println(message)
}

이 형태의 when은 위에서부터 조건식을 검사합니다. if - else if를 더 읽기 좋게 바꾼 형태라고 생각하면 됩니다.

when을 선택할 때 기준은 간단합니다.

  • 조건이 한두 개라면 if가 자연스럽습니다.

  • 하나의 값을 여러 경우와 비교한다면 when이 읽기 쉽습니다.

  • 조건식이 여러 줄로 늘어나면 when의 대상 없는 형태를 고려할 수 있습니다.


반복문이 필요한 순간

조건문이 "어떤 코드를 실행할지"를 정한다면, 반복문은 "같은 일을 얼마나 반복할지"를 정합니다.

예를 들어 1부터 5까지 출력하고 싶다고 해 봅시다.

println(1)
println(2)
println(3)
println(4)
println(5)

이 코드는 동작하지만 좋은 방식은 아닙니다. 반복 횟수가 100번이 되면 같은 코드를 100줄 써야 합니다. 반복문을 사용하면 이렇게 쓸 수 있습니다.

for (number in 1..5) {
    println(number)
}

반복문은 다음 상황에서 자주 사용합니다.

  • 목록의 모든 요소를 하나씩 처리할 때

  • 정해진 횟수만큼 같은 작업을 반복할 때

  • 조건이 만족될 때까지 계속 시도할 때

  • 중간에 특정 값을 찾으면 멈출 때


for: 범위와 컬렉션을 순회하기

코틀린의 for문은 in과 함께 사용합니다.

for (number in 1..5) {
    println(number)
}

1..5는 1, 2, 3, 4, 5를 의미합니다. 끝값을 포함합니다.

끝값을 포함하지 않고 싶다면 until을 사용합니다.

for (index in 0 until 5) {
    println(index)
}

이 코드는 0, 1, 2, 3, 4를 출력합니다. 5는 포함하지 않습니다. 배열이나 리스트의 인덱스를 다룰 때 자주 쓰는 형태입니다.

val names = listOf("민지", "도윤", "서연")

for (index in 0 until names.size) {
    println("$index: ${names[index]}")
}

하지만 인덱스와 값을 함께 다룰 때는 indices나 withIndex()가 더 자연스러울 수 있습니다.

for (index in names.indices) {
    println("$index: ${names[index]}")
}
for ((index, name) in names.withIndex()) {
    println("$index: $name")
}

목록의 값만 필요하다면 더 단순하게 씁니다.

for (name in names) {
    println(name)
}

for문은 숫자 범위뿐 아니라 컬렉션과도 잘 어울립니다. 리스트, 집합, 맵처럼 여러 값을 가진 자료를 하나씩 처리할 때 기본적으로 떠올릴 수 있는 반복문입니다.


step과 downTo로 반복 방향 조절하기

숫자 반복에서는 간격과 방향을 조절할 수 있습니다.

step은 몇 칸씩 건너뛸지 정합니다.

for (number in 0..10 step 2) {
    println(number)
}

출력은 0, 2, 4, 6, 8, 10입니다.

큰 수에서 작은 수로 내려가려면 downTo를 사용합니다.

for (number in 5 downTo 1) {
    println(number)
}

출력은 5, 4, 3, 2, 1입니다.

downTo와 step을 함께 쓸 수도 있습니다.

for (number in 10 downTo 0 step 2) {
    println(number)
}

출력은 10, 8, 6, 4, 2, 0입니다.

숫자 반복은 다음처럼 읽으면 됩니다.

  • 1..5: 1부터 5까지

  • 0 until 5: 0부터 5 전까지

  • 5 downTo 1: 5부터 1까지 내려가며

  • 0..10 step 2: 0부터 10까지 2씩 증가하며


while: 조건이 참인 동안 반복하기

for는 반복 범위나 컬렉션이 분명할 때 좋습니다. 반면 반복 횟수를 미리 알기 어렵고, 어떤 조건이 참인 동안 계속해야 한다면 while을 사용합니다.

fun main() {
    var count = 3

    while (count > 0) {
        println("count = $count")
        count--
    }

    println("끝")
}

while은 조건을 먼저 검사합니다. 조건이 처음부터 거짓이면 블록은 한 번도 실행되지 않습니다.

var number = 0

while (number > 0) {
    println(number)
}

위 코드는 아무것도 출력하지 않습니다. number > 0이 처음부터 거짓이기 때문입니다.

while을 사용할 때는 조건이 언젠가 거짓이 되도록 만들어야 합니다. 그렇지 않으면 무한 반복이 됩니다.

var count = 1

while (count <= 5) {
    println(count)
    count++ // 이 줄이 없으면 반복이 끝나지 않는다
}

do-while: 적어도 한 번은 실행하기

do-while은 조건을 나중에 검사합니다. 그래서 블록이 적어도 한 번은 실행됩니다.

fun main() {
    var input: String

    do {
        print("명령을 입력하세요: ")
        input = readln()
    } while (input != "quit")
}

사용자 입력처럼 "일단 한 번 실행한 뒤 계속할지 결정하는" 흐름에 어울립니다.

while과 do-while의 차이는 검사 시점입니다.

  • while: 조건을 먼저 검사한다.

  • do-while: 블록을 먼저 실행하고 조건을 검사한다.

처음에는 for와 while을 가장 많이 사용하게 됩니다. do-while은 "최소 한 번 실행"이 자연스러운 상황에서 선택하면 됩니다.


break: 반복을 즉시 끝내기

반복문 안에서 더 이상 반복할 필요가 없을 때는 break를 사용합니다.

fun main() {
    val numbers = listOf(3, 7, 10, 15, 20)

    for (number in numbers) {
        if (number % 2 == 0) {
            println("첫 번째 짝수: $number")
            break
        }
    }
}

이 코드는 첫 번째 짝수를 찾으면 반복을 끝냅니다. 10을 찾은 뒤에는 15, 20을 더 확인하지 않습니다.

break는 반복 전체를 빠져나갑니다. 필요한 값을 찾았거나, 더 이상 진행하면 안 되는 조건을 만났을 때 사용합니다.


continue: 이번 반복만 건너뛰기

continue는 반복문을 끝내지는 않습니다. 대신 현재 차례의 나머지 코드를 건너뛰고 다음 반복으로 넘어갑니다.

fun main() {
    val numbers = listOf(1, 2, 3, 4, 5)

    for (number in numbers) {
        if (number % 2 == 0) {
            continue
        }

        println(number)
    }
}

이 코드는 홀수만 출력합니다. 짝수를 만나면 println(number)까지 가지 않고 다음 숫자로 넘어갑니다.

continue는 "이 값은 처리하지 않고 넘어간다"는 뜻을 표현할 때 유용합니다.

val names = listOf("민지", "", "도윤", "   ", "서연")

for (name in names) {
    if (name.isBlank()) continue

    println("이름: $name")
}

빈 문자열은 건너뛰고, 실제 이름만 출력합니다.


label로 중첩 반복문 제어하기

반복문이 중첩되어 있을 때 break와 continue는 기본적으로 가장 가까운 반복문에만 영향을 줍니다.

for (row in 1..3) {
    for (col in 1..3) {
        if (row == 2 && col == 2) {
            break
        }
        println("row=$row, col=$col")
    }
}

위 코드의 break는 안쪽 for만 끝냅니다. 바깥쪽 for는 계속됩니다.

바깥 반복문까지 한 번에 끝내고 싶다면 라벨을 사용할 수 있습니다.

outer@ for (row in 1..3) {
    for (col in 1..3) {
        if (row == 2 && col == 2) {
            break@outer
        }
        println("row=$row, col=$col")
    }
}

outer@는 바깥 반복문에 붙인 이름입니다. break@outer는 그 이름이 붙은 반복문을 끝냅니다.

continue에도 라벨을 사용할 수 있습니다.

outer@ for (row in 1..3) {
    for (col in 1..3) {
        if (col == 2) {
            continue@outer
        }
        println("row=$row, col=$col")
    }
}

이 코드는 col == 2를 만나면 안쪽 반복의 다음 차례가 아니라 바깥 반복의 다음 row로 넘어갑니다.

라벨은 강력하지만 자주 쓰면 흐름을 따라가기 어려워질 수 있습니다. 중첩 반복이 복잡해진다면 별도 함수로 분리하고 return을 사용하는 편이 더 읽기 좋을 때도 많습니다.


조건문과 반복문을 함께 사용하기

실제 코드는 조건문과 반복문을 함께 사용합니다. 예를 들어 점수 목록에서 유효한 점수만 골라 평균을 구해 봅시다.

fun main() {
    val scores = listOf(90, 85, -1, 100, 120, 70)

    var total = 0
    var count = 0

    for (score in scores) {
        if (score !in 0..100) {
            continue
        }

        total += score
        count++
    }

    val average = if (count == 0) {
        0.0
    } else {
        total.toDouble() / count
    }

    println("유효한 점수 개수: $count")
    println("평균: $average")
}

이 예제에는 여러 흐름 제어가 함께 들어 있습니다.

  • for로 점수 목록을 하나씩 봅니다.

  • if로 유효하지 않은 점수를 판단합니다.

  • continue로 잘못된 점수는 건너뜁니다.

  • 마지막에 if 표현식으로 평균을 계산합니다.

나중에 컬렉션 함수를 배우면 이 코드를 더 짧게 쓸 수 있습니다. 하지만 먼저 반복문과 조건문의 기본 흐름을 이해해야 합니다. 그래야 더 간결한 문법을 보더라도 내부에서 어떤 일이 일어나는지 이해할 수 있습니다.


자주 하는 실수

if 조건에 Boolean이 아닌 값을 넣는다

val count = 3

// if (count) { } // 오류

코틀린의 if 조건은 반드시 Boolean이어야 합니다. 숫자 0을 거짓처럼 취급하지 않습니다.

if (count > 0) {
    println("값이 있습니다")
}

if 표현식에서 else를 빠뜨린다

val score = 80

// val result = if (score >= 60) "합격" // 오류가 될 수 있음

값을 만들어 변수에 넣으려면 조건이 거짓일 때의 값도 필요합니다.

val result = if (score >= 60) "합격" else "불합격"

조건 순서를 잘못 둔다

val score = 95

val grade = if (score >= 60) {
    "D"
} else if (score >= 90) {
    "A"
} else {
    "F"
}

이 코드는 95에도 "D"를 반환합니다. 더 구체적인 조건을 먼저 검사해야 합니다.

val grade = if (score >= 90) {
    "A"
} else if (score >= 60) {
    "D"
} else {
    "F"
}

while 반복 조건을 바꾸지 않는다

var count = 1

while (count <= 5) {
    println(count)
    // count++가 없으면 끝나지 않는다
}

while을 사용할 때는 반복이 끝날 수 있는 조건을 반드시 확인해야 합니다.

break와 continue의 차이를 헷갈린다

  • break: 반복문을 끝냅니다.

  • continue: 이번 차례만 건너뛰고 다음 반복으로 갑니다.

둘 다 흐름을 바꾸는 문법이므로 남용하지 않는 편이 좋습니다. 조건을 반대로 정리하거나 함수를 분리하면 더 읽기 쉬운 코드가 될 때도 많습니다.


연습 문제

다음 문제를 직접 입력해 보세요. 흐름 제어는 눈으로 읽는 것보다 직접 실행하며 결과를 확인할 때 훨씬 빨리 익숙해집니다.

문제 1

점수 하나를 변수로 선언하고 다음 규칙에 따라 등급을 출력해 보세요.

  • 90 이상: A

  • 80 이상 90 미만: B

  • 70 이상 80 미만: C

  • 그 외: D

if 표현식과 when 표현식 두 가지 방식으로 작성해 보세요.

문제 2

1부터 100까지의 숫자 중 짝수만 출력해 보세요.

힌트: for, 범위 연산자, % 연산자를 사용하세요.

문제 3

1부터 10까지의 합계를 구해 출력해 보세요.

합계: 55

문제 4

다음 리스트에서 빈 문자열을 제외하고 이름만 출력해 보세요.

val names = listOf("민지", "", "도윤", "   ", "서연")

힌트: isBlank()와 continue를 사용해 보세요.

문제 5

숫자 리스트에서 첫 번째로 10보다 큰 값을 찾아 출력하고, 찾으면 반복을 멈추세요.

val numbers = listOf(3, 8, 12, 5, 20)

예상 출력은 다음과 같습니다.

첫 번째 큰 값: 12

정리

이번 장에서는 프로그램의 실행 흐름을 제어하는 방법을 배웠습니다.

  • if는 조건이 참일 때 코드를 실행합니다.

  • if는 값을 만드는 표현식으로도 사용할 수 있습니다.

  • 코틀린에는 삼항 연산자가 없고, 대신 if 표현식을 사용합니다.

  • when은 여러 경우를 분기할 때 읽기 좋은 문법입니다.

  • when도 값을 만드는 표현식으로 사용할 수 있으며, 모든 경우를 처리해야 합니다.

  • for는 범위나 컬렉션을 순회할 때 사용합니다.

  • while은 조건이 참인 동안 반복합니다.

  • do-while은 블록을 최소 한 번 실행한 뒤 조건을 검사합니다.

  • .., until, downTo, step으로 숫자 반복 범위와 방향을 조절할 수 있습니다.

  • break는 반복을 끝내고, continue는 현재 차례만 건너뜁니다.

  • 중첩 반복문에서는 라벨을 사용해 바깥 반복문을 제어할 수 있지만, 복잡해지면 함수 분리를 먼저 고려하는 편이 좋습니다.

값을 만들고 계산하는 방법에 조건과 반복이 더해지면, 프로그램은 상황에 따라 다르게 움직일 수 있습니다. 다음 장에서는 이런 코드 조각에 이름을 붙이고 재사용할 수 있도록 함수 문법을 배웁니다.

책 목록

처음 배우는 코틀린: 문법에서 실전 프로젝트까지

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기