
Go 반복문, 왜 하나만 써야 할까요?
파이썬을 쓰다가 Go 언어를 처음 접한 개발자라면 당황스러운 순간이 찾아와요. 다른 언어에서는 while이나 do-while 같은 다양한 반복 키워드를 볼 수 있지만, Go에는 오직 for 하나만 존재하기 때문이에요. “어떻게 반복문을 다 구현하라는 거지?”라는 의문이 들 수도 있어요.
처음에는 이 단순함이 오히려 불편하게 느껴질 수 있어요. 하지만 Go의 철학을 이해하면 이 방식이 얼마나 강력하고 명확한지 알게 돼요. 복잡한 문법을 외울 필요 없이 for 하나만 제대로 다룰 줄 알면, 모든 반복 로직을 우아하게 작성할 수 있거든요. 반복문의 종류가 적다는 것은 그만큼 코드의 가독성이 높아지고, 프로그래머가 고민해야 할 문법적 비용이 줄어든다는 뜻이기도 해요.
이 글은 Go의 반복문을 처음 배우는 입문자분들을 위해 준비했어요. 단순한 문법 나열이 아니라, 실제 현업에서 어떤 식으로 코드를 짜는지, 그리고 초보자가 가장 많이 빠지는 함정은 무엇인지 위주로 정리했어요. 이 치트시트 하나만 잘 읽어두어도 반복문 때문에 코드를 수정하며 밤을 지새우는 일은 없을 거예요.
오늘 이 글을 통해 배우게 될 내용은 다음과 같아요.
- Go 반복문의 세 가지 핵심 형태와 사용 시점
- range 키워드를 활용한 효율적인 데이터 순회 방법
- 실무에서 즉시 복사해서 쓸 수 있는 유용한 코드 패턴
- 초보 개발자가 가장 빈번하게 저지르는 5가지 실수와 교정법
반복문을 다루기 전 꼭 알아야 할 기본 개념
Go의 반복문을 본격적으로 작성하기 전에, 우리가 제어해야 할 요소들이 무엇인지 먼저 정리해봐요. 반복문은 결국 시작점, 조건, 변화량이라는 세 가지 축으로 움직여요. Go는 이 세 가지를 하나의 문장 안에 담거나, 혹은 조건만 남겨서 아주 유연하게 변형할 수 있도록 설계되었어요.
가장 먼저 이해해야 할 것은 Go의 for 문이 가진 유연함이에요. Go에서는 조건식만 있다면 그것이 바로 while 문의 역할을 대신해요. 별도의 키워드를 찾으러 다닐 필요가 전혀 없다는 것이죠. 또한, 슬라이스(Slice)나 맵(Map) 같은 자료구조를 다룰 때는 range라는 마법 같은 키워드를 함께 사용하게 돼요.
Go의 반복문은 다른 언어의 while 문을 포함하는 더 넓은 개념이에요. 따라서 문법을 공부할 때 “이게 while인가?”라고 묻기보다 “이 for 문이 어떤 조건으로 작동하는가?”에 집중하는 것이 훨씬 효율적이에요.
어떤 상황에서 어떤 형태의 반복문을 선택해야 할지 판단 기준을 아래 표로 정리해 보았어요. 이 기준을 머릿속에 넣어두면 코드를 짤 때 고민하는 시간을 크게 줄일 수 있어요.
| 반복 형태 | 주요 용도 | 추천 상황 |
|---|---|---|
| 기본형 (3요소) | 카운터를 이용한 횟수 제어 | 특정 횟수만큼 반복할 때 |
| 조건형 (1요소) | 특정 조건이 참인 동안 유지 | while 문이 필요한 경우 |
| range형 | 컬렉션 요소 순회 | 슬라이스, 배열, 맵 순회 시 |
| 무한 루프 | 정해진 종료 조건이 없을 때 | 서버 리스닝, 이벤트 대기 |
이처럼 Go의 반복문은 매우 단순해 보이지만, 어떻게 구성하느냐에 따라 코드의 의도가 완전히 달라져요. 단순히 돌아가는 코드를 만드는 것을 넘어, 동료 개발자가 읽었을 때 의도가 명확히 드러나는 코드를 작성하는 연습이 필요해요. 이제 준비가 되었다면 본격적인 실전 문법으로 들어가 볼까요?
Go 반복문 실전 활용 가이드
이제부터는 실제 코드와 함께 Go의 반복문을 어떻게 사용하는지 단계별로 깊이 있게 살펴볼게요. 각 단계는 가장 빈번하게 사용되는 패턴을 중심으로 구성했어요. 단순히 코드를 눈으로 보는 것에 그치지 말고, 각 문법이 왜 그렇게 작동하는지 원리를 이해하는 것이 중요해요.
STEP 1. 가장 기본적인 for 루프 사용법
가장 먼저 익혀야 할 것은 우리가 흔히 알고 있는 for(init; condition; post) 형태예요. 이 형태는 반복의 시작점, 지속 조건, 그리고 한 번의 반복이 끝난 후 수행할 행동을 한 줄에 정의할 수 있어요. 정해진 횟수만큼 숫자를 세며 반복해야 할 때 가장 적합해요.
// 0부터 9까지 출력하는 가장 표준적인 방법
for i := 0; i < 10; i++ {
fmt.Println(i)
}
여기서 i := 0은 초기화 식이고, i < 10은 조건식이에요. 조건이 참인 동안에는 계속 루프가 돌고, 조건이 거짓이 되는 순간 루프를 빠져나가게 되죠. 주의할 점은 조건식을 잘못 설정하면 루프가 끝나지 않는 무한 루프에 빠질 수 있다는 거예요. 항상 조건식이 언젠가는 거짓이 될 수 있는지 검토해야 해요.
STEP 2. 조건만 사용하는 while 스타일 루프
Go에는 별도의 while 키워드가 없다고 말씀드렸죠? 대신 for 문에 조건식 하나만 남겨두면 그게 바로 while 문이 돼요. 반복 횟수가 정해져 있지 않고, 어떤 특정 상태가 유지되는 동안 계속 무언가를 해야 할 때 아주 유용해요.
// n이 100보다 작을 때까지 계속 2배씩 늘리는 루프
n := 1
for n < 100 {
fmt.Println(n)
n *= 2
}
이 방식은 코드가 매우 깔끔해진다는 장점이 있어요. 초기화 식이나 증감 식이 필요 없는 경우, 억지로 for 문 안에 넣으려고 애쓸 필요가 없어요. 조건만 깔끔하게 명시해서 코드의 가독성을 높여주세요.
STEP 3. range를 활용한 데이터 순회
Go 프로그래밍에서 가장 많이 쓰이는 형태는 단연 range를 사용하는 방식이에요. 슬라이스(Slice)나 배열(Array), 맵(Map) 같은 자료구조를 하나씩 꺼내서 처리할 때 사용해요. 인덱스와 값을 동시에 얻을 수 있어 매우 편리하죠.
// 슬라이스의 인덱스와 값을 모두 사용하는 예제
fruits := []string{"사과", "바나나", "포도"}
for index, value := range fruits {
fmt.Printf("%d번째 과일: %s\n", index, value)
}
// 인덱스가 필요 없다면 빈 식별자(_)를 사용하세요
for _, value := range fruits {
fmt.Println("과일 이름:", value)
}
여기서 꿀팁을 하나 드리자면, 만약 인덱스가 필요 없고 값만 필요하다면 반드시 _(blank identifier)를 사용해야 해요. Go는 선언한 변수를 사용하지 않으면 컴파일 에러를 내뱉는 엄격한 언어이기 때문이에요. 또한, 맵(Map)을 순회할 때는 순서가 보장되지 않는다는 점을 꼭 기억해야 해요. 맵을 돌 때마다 결과의 순서가 바뀔 수 있으니, 순서가 중요하다면 별도의 정렬 과정을 거쳐야 해요.
STEP 4. 무한 루프와 탈출 전략
가끔은 조건 없이 무한히 돌아야 하는 상황이 생겨요. 예를 들어 서버가 클라이언트의 요청을 계속 기다리거나, 특정 신호가 올 때까지 대기해야 하는 경우죠. 이때는 조건식을 아예 생략한 형태를 사용해요.
// 무한 루프 예시
for {
fmt.Println("무언가 작업 중...")
if someCondition {
break // 특정 조건에서 루프 탈출
}
}
무한 루프 내에서는 반드시 루프를 탈출할 수 있는 break 또는 return 문을 설계해야 해요. 그렇지 않으면 프로그램이 멈추지 않고 CPU 자원을 계속 잡아먹게 돼요. 만약 현재 루프의 이번 차례만 건너뛰고 다음 차례로 넘어가고 싶다면 continue를 사용하면 돼요.
STEP 5. 중첩 루프와 레이블(Label) 사용하기
루프 안에 또 다른 루프가 있는 중첩 루프 상황에서는 break를 썼을 때 어떤 루프를 빠져나갈지 모호할 때가 있어요. Go는 이 문제를 해결하기 위해 레이블(Label)이라는 아주 강력한 기능을 제공해요.
// 레이블을 사용해 바깥쪽 루프까지 한 번에 탈출하기
OuterLoop:
for i := 0; i < 5; i++ {
for j := 0; j < 5; j++ {
if i == 2 && j == 2 {
fmt.Println("모든 루프 종료!")
break OuterLoop // OuterLoop 레이블이 붙은 루프를 탈출
}
fmt.Printf("i:%d, j:%d\n", i, j)
}
}
레이블을 사용하면 복잡한 다중 루프 구조에서도 코드의 흐름을 명확하게 제어할 수 있어요. 하지만 레이블을 너무 남발하면 코드가 스파게티처럼 꼬일 수 있으니, 정말 필요한 상황에서만 신중하게 사용하는 것이 좋아요.
복잡한 로직을 짤 때는 루프의 깊이(Depth)를 가급적 3단계 이하로 유지하세요. 중첩이 너무 깊어지면 가독성이 떨어지고 버그가 발생할 확률이 급격히 높아져요. 로직을 별도의 함수로 분리하는 것이 더 좋은 방법일 수 있어요.
자주 하는 실수와 해결법
Go 반복문을 작성하다 보면 문법은 맞는데 결과가 이상하거나, 심지어 프로그램이 컴파일조차 되지 않는 상황을 마주하게 돼요. 초보자가 가장 많이 겪는 5가지 사례를 모아봤어요.
- ❌ 무한 루프 발생
왜 발생하는가: for 문의 조건식이 항상 참이거나, 증감식(i++)을 빠뜨렸을 때 발생해요.
✅ 해결법: 루프가 종료되는 조건을 명확히 하고, 매 반복마다 조건에 영향을 주는 변수가 변화하는지 확인하세요. - ❌ Index out of range 에러
왜 발생하는가: 슬라이스의 길이를 초과하는 인덱스에 접근하려고 할 때 발생해요. 특히 i <= len(slice)처럼 등호를 잘못 넣는 경우가 많아요.
✅ 해결법: 인덱스는 항상 0부터 len(slice)-1까지임을 명심하고, 가급적 range를 사용하세요. - ❌ 고루틴(Goroutine) 내 변수 캡처 문제
왜 발생하는가: for 문 안에서 루프 변수의 주소를 고루틴에 전달하면, 모든 고루틴이 마지막 루프의 변수 값을 공유하게 돼요.
✅ 해결법: 루프 내부에서 변수를 새로 선언하여 값을 복사해 전달하세요. (예:v := value) - ❌ 슬라이스 순회 중 요소 삭제/추가
왜 발생하는가: 루프를 도는 도중에 원본 슬라이스의 길이를 바꾸면 인덱스 계산이 꼬여 버려요.
✅ 해결법: 요소를 삭제해야 한다면 역순으로 루프를 돌거나, 새로운 슬라이스에 결과를 담는 방식을 사용하세요. - ❌ 사용하지 않는 변수 에러
왜 발생하는가: range를 통해 인덱스를 받았는데 실제 코드에서 쓰지 않으면 Go 컴파일러가 에러를 냅니다.
✅ 해결법: 사용하지 않는 변수는 반드시 _(언더스코어)로 처리해 주세요.
Go는 매우 엄격한 언어예요. "대충 넘어가겠지"라는 생각은 컴파일 에러로 돌아옵니다. 에러 메시지를 두려워하지 말고, 언어가 요구하는 규칙을 정확히 지켜주는 것이 가장 빠른 길이에요.
자주 묻는 질문
Q. Go에 while 문이 정말 없나요?
네, 맞아요. 하지만 for 문이 그 모든 역할을 대신할 수 있도록 설계되어 있어서 문법적으로는 필요하지 않아요. 조건식만 적으면 바로 while처럼 동작합니다.
Q. range를 쓸 때 성능 차이가 많이 나나요?
가장 기본적인 for i := 0; i < len(s); i++ 방식과 비교했을 때, 현대적인 컴파일러에서는 거의 차이가 없어요. 가독성을 생각한다면 range를 적극적으로 쓰는 것을 추천해요.
Q. 루프 안에서 break를 쓰면 전체 루프가 끝나나요?
네, 현재 진행 중인 가장 가까운 루프 하나를 빠져나갑니다. 만약 중첩된 루프의 바깥쪽까지 한 번에 나가고 싶다면 앞서 설명한 레이블(Label) 기능을 사용해야 해요.
Q. 맵(Map)을 순회할 때 순서가 왜 계속 바뀌나요?
Go 설계자들은 맵 순회 시 예측 가능한 특정 순서를 강제함으로써 개발자들이 데이터 순서에 의존하는 나쁜 습관을 갖지 않도록 의도적으로 무작위성을 부여했어요.
Q. 무한 루프를 안전하게 멈추는 방법이 있나요?
context 패키지를 사용하여 외부에서 신호를 보내 루프를 종료시키는 패턴이 실무에서 가장 많이 쓰이는 안전한 방법이에요.
반복문 마스터를 위한 마지막 정리
지금까지 Go 언어의 유일한 반복문인 for 문에 대해 깊이 있게 살펴보았어요. 처음에는 문법이 하나뿐이라는 사실이 낯설 수 있지만, 익숙해지면 이보다 명확하고 강력한 도구도 없다는 걸 깨닫게 될 거예요. 오늘 배운 내용을 바탕으로 더 깔끔하고 버그 없는 코드를 작성해 보세요.
- Go에는 for 키워드 하나로 모든 반복을 처리해요.
- 조건식만 사용하면 while 문처럼 쓸 수 있어요.
- 컬렉션 순회는 range를 사용하는 것이 가장 안전하고 편리해요.
- 사용하지 않는 인덱스나 변수는 반드시 _로 처리해야 컴파일 에러를 피해요.
- 중첩 루프 탈출이 필요할 땐 레이블(Label) 기능을 활용하세요.
- 고루틴 내부에서 루프 변수를 쓸 때는 반드시 변수를 복사해서 전달하세요.
오늘 배운 내용을 실제 프로젝트에 바로 적용해 보는 건 어떨까요? 이론으로 아는 것과 직접 타이핑하며 에러를 마주하는 것은 하늘과 땅 차이예요. 지금 바로 에디터를 열고 슬라이스를 만들어 range 문으로 하나씩 출력해 보는 것부터 시작해 보세요.
작성하시다가 도저히 해결되지 않는 반복문 로직이나 이해되지 않는 에러 메시지가 있다면 언제든 댓글로 남겨 주세요. 여러분의 고민을 함께 나누고 답변해 드릴게요! 여러분의 Go 프로그래밍 여정을 진심으로 응원합니다.
함께 읽으면 좋은 글: Go 반복문 완벽 가이드