
Go 반복문, 왜 하나만 알면 될까요?
Go 언어를 처음 공부하다 보면 정말 당황스러운 순간이 찾아와요. 다른 언어인 파이썬이나 자바에서는 당연하게 쓰던 while 문이 코드 어디에도 보이지 않거든요. ‘어? 반복문은 어떻게 돌려야 하지?’라는 생각과 함께 코드 에디터만 멍하니 바라본 적이 분명히 있으실 거예요.
하지만 너무 걱정하지 마세요. Go는 설계 철학 자체가 아주 간결하고 명확해요. 복잡한 반복문 키워드를 여러 개 만드는 대신, 오직 for 하나만 사용하여 모든 반복 로직을 처리하도록 만들었거든요. 이 단순함이 처음에는 낯설지만, 익숙해지면 코드가 얼마나 깔끔해지는지 금방 깨닫게 돼요.
단순히 문법을 외우는 것보다 중요한 건, 상황에 따라 어떤 형태의 반복문을 써야 효율적인지 판단하는 능력이에요. 슬라이스를 훑을 때, 맵의 키를 찾을 때, 혹은 특정 조건이 충족될 때까지 무한히 실행해야 할 때 등 상황은 정말 다양해요.
이 글을 끝까지 읽고 나면 다음과 같은 것들을 확실히 얻어갈 수 있어요.
- Go의 유일한 반복문인 for의 3가지 핵심 형태
- 슬라이스와 맵을 가장 빠르게 다루는 range 활용법
- 실무에서 바로 복사해서 쓸 수 있는 실전 코드 패턴
- 초보자가 가장 많이 하는 실수와 해결 방법
이제 복잡한 고민은 접어두고, Go의 반복문 세계로 함께 들어가 봐요!
반복문 시작 전, 꼭 알아야 할 기초 지식
Go의 반복문을 본격적으로 다루기 전에, 우리가 가져야 할 마음가짐이 있어요. 바로 “단순함이 곧 강력함이다”라는 생각이에요. Go는 언어 차원에서 불필요한 복잡성을 제거했기 때문에, 우리는 단 하나의 키워드만 잘 다루면 돼요.
먼저, Go에서 반복문을 사용할 때 우리가 선택할 수 있는 세 가지 주요 경로를 이해해야 해요. 무턱대고 코드를 짜기 전에, 내가 지금 어떤 상황에 놓여 있는지 먼저 판단하는 과정이 필요하거든요.
| 반복 방식 | 주요 용도 | 특징 |
|---|---|---|
| 기본 for 루프 | 횟수가 정해진 반복 | 초기값, 조건, 증감식을 직접 제어해요. |
| 조건식 for 루프 | 특정 조건이 맞을 때까지 실행 | 다른 언어의 while 문과 같은 역할을 해요. |
| range 루프 | 컬렉션(슬라이스, 맵) 순회 | 인덱스와 값을 동시에 가져와서 매우 편리해요. |
왜 이렇게 형식을 나누어 두었을까요? 그것은 바로 코드의 가독성 때문이에요. 단순히 0부터 10까지 숫자를 세는 것과, 사용자 목록(Slice)을 하나씩 꺼내어 확인하는 것은 논리적인 흐름이 완전히 달라야 하거든요. 적절한 도구를 선택하는 것만으로도 동료 개발자가 여러분의 코드를 읽을 때 훨씬 빠르게 이해할 수 있게 도와줘요.
Go에서는 루프 변수의 범위를 주의 깊게 살펴봐야 해요. 특히 Go 1.22 버전 이후부터는 루프 변수가 매 반복마다 새로운 인스턴스로 생성되도록 변경되어, 예전에 흔히 발생하던 ‘루프 변수 캡처’ 문제가 크게 완화되었답니다.
자, 이제 준비는 모두 끝났어요. 기본적인 도구들의 특징을 파악했으니, 이제 직접 코드를 작성하며 각 방식이 어떻게 돌아가는지 구체적으로 살펴볼게요.
실전 Go 반복문 마스터하기
이제 본격적으로 Go 반복문의 핵심 패턴들을 하나씩 파헤쳐 볼게요. 각 단계별로 실제 코드가 어떻게 구성되는지, 그리고 어떤 상황에서 써야 하는지 아주 자세히 설명해 드릴게요.
STEP 1. 클래식한 방식의 기본 for 루프
가장 전통적인 방식이에요. C나 Java를 공부해 보셨다면 아주 익숙할 형태죠. 이 방식은 초기화, 조건식, 증감식이라는 세 가지 요소를 한 줄에 명시해요. 숫자를 카운트하거나, 특정 간격으로 건너뛰며 반복해야 할 때 가장 강력해요.
// 0부터 4까지 출력하기
for i := 0; i < 5; i++ {
fmt.Println(i)
}
// 2씩 증가하며 출력하기
for i := 0; i < 10; i += 2 {
fmt.Println(i)
}
이 구조의 장점은 제어권이 온전히 개발자에게 있다는 점이에요. 단순히 1씩 더하는 게 아니라, `i *= 2`처럼 값을 기하급수적으로 늘릴 수도 있고, 특정 조건에서 인덱스를 조절할 수도 있어요. 하지만 조건식을 잘못 설정하면 무한 루프에 빠질 수 있으니 항상 종료 조건을 명확히 확인해야 해요.
STEP 2. while 문을 대체하는 조건식 루프
Go에는 `while` 키워드가 없다고 말씀드렸죠? 대신 `for` 키워드 뒤에 바로 조건식만 적어주면 그것이 바로 `while` 문이 돼요. 특정 상태가 유지되는 동안 계속해서 어떤 작업을 수행해야 할 때 아주 유용해요.
// 변수 n이 10보다 작을 때까지 계속 실행
n := 1
for n < 10 {
fmt.Println("현재 값:", n)
n *= 2
}
이 방식은 루프의 시작 조건이 루프 몸체 내부의 로직에 의해 결정될 때 주로 사용해요. 예를 들어, 데이터 스트림에서 더 이상 읽을 데이터가 없을 때까지 계속해서 데이터를 가져오는 로직을 짤 때 이 패턴이 자주 등장해요.
STEP 3. 컬렉션 순회의 꽃, range 활용법
Go 개발자라면 가장 많이, 그리고 가장 사랑하게 될 방식이 바로 range 루프예요. 슬라이스(Slice), 배열(Array), 맵(Map), 채널(Channel) 같은 데이터 묶음을 하나씩 꺼내올 때 이보다 편한 방법은 없어요.
슬라이스를 순회할 때는 인덱스(index)와 값(value)을 동시에 받을 수 있어요. 만약 인덱스가 필요 없다면 언더스코어(`_`)를 사용하여 무시할 수 있죠.
// 슬라이스 순회 예제
fruits := []string{"사과", "바나나", "포도"}
for index, value := range fruits {
fmt.Printf("%d번째 과일: %s\n", index, value)
}
// 인덱스는 무시하고 값만 필요할 때
for _, value := range fruits {
fmt.Println("과일 이름:", value)
}
맵(Map)을 순회할 때는 인덱스 대신 키(key)가 반환돼요. 맵은 순서가 보장되지 않는다는 점을 꼭 기억해야 해요! 루프를 돌 때마다 결과의 순서가 바뀔 수 있거든요.
STEP 4. 흐름 제어: break와 continue
루프를 돌다가 중간에 탈출하거나, 이번 차례만 건너뛰고 싶을 때가 있죠? 이때 사용하는 것이 `break`와 `continue`예요. 이 두 키워드만 잘 써도 루프의 논리를 훨씬 정교하게 만들 수 있어요.
- break: 루프를 즉시 종료하고 빠져나와요.
- continue: 현재 차례의 남은 코드를 건너뛰고 다음 반복으로 넘어가요.
예를 들어, 1부터 100까지 숫자 중 7의 배수만 제외하고 출력하고 싶다면 `continue`를 사용하면 돼요. 혹은 특정 숫자를 찾는 즉시 루프를 멈추고 싶다면 `break`를 사용하면 되고요.
STEP 5. 실무 시나리오: 사용자 데이터 처리하기
자, 그럼 지금까지 배운 것들을 하나로 뭉쳐서 실제 개발 환경에서 마주할 법한 시나리오를 만들어 볼게요. 사용자 이름 목록이 담긴 슬라이스가 있고, 여기서 특정 이름을 가진 사용자를 찾아 상태를 업데이트하는 로직이에요.
type User struct {
Name string
IsActive bool
}
func main() {
users := []User{
{"Kim", true},
{"Lee", false},
{"Park", true},
{"Choi", false},
}
targetName := "Lee"
for i := range users {
if users[i].Name == targetName {
fmt.Printf("%s님을 찾았습니다! 활성화 상태로 변경합니다.\n", targetName)
users[i].IsActive = true
break // 찾았으니 더 이상 루프를 돌 필요가 없어요
}
}
}
여기서 한 가지 팁을 드리자면, `for i := range users`와 같이 인덱스만 가져온 뒤 `users[i]`로 접근하는 방식은 큰 슬라이스를 다룰 때 메모리 복사 비용을 줄여줄 수 있어 성능 면에서 유리할 때가 많아요. `for _, user := range users` 방식은 매 반복마다 `user` 변수에 값을 복사하기 때문이죠.
맵(Map)을 순회하면서 해당 맵의 요소를 삭제하거나 추가하는 작업은 주의해야 해요. Go의 맵은 순회 중에 요소를 삭제하는 것은 허용하지만, 논리적으로 예기치 못한 동작을 유발할 수 있으니 가급적 삭제할 키들을 별도의 슬라이스에 모아두었다가 루프가 끝난 뒤 처리하는 것을 추천해요.
자주 하는 실수와 해결법 + FAQ
반복문을 작성하다 보면 누구나 한 번쯤은 실수를 하기 마련이에요. 특히 Go의 독특한 문법 특성 때문에 발생하는 실수들이 있죠. 다음의 사례들을 미리 익혀두면 디버깅 시간을 엄청나게 줄일 수 있어요.
자주 하는 실수와 해결법
❌ 실수: 무한 루프에 빠져 프로그램이 멈춤
왜 발생하는가: `for` 조건식이나 내부의 증감식이 잘못되어 종료 조건에 영원히 도달하지 못할 때 발생해요.
✅ 해결법: 루프를 설계할 때 탈출 조건(`break`)이나 조건식이 확실히 `false`가 되는 경로가 있는지 반드시 검증하세요.
❌ 실수: range에서 인덱스만 쓰고 값을 사용하지 않음
왜 발생하는가: `for i := range slice`라고만 쓰면 `i`는 값(value)이 아니라 인덱스(index)예요. 값을 쓰려고 `fmt.Println(i)`를 하면 엉뚱한 숫자가 출력돼요.
✅ 해결법: 값을 직접 쓰고 싶다면 `for i, v := range slice`처럼 두 개의 변수를 선언하세요.
❌ 실수: 슬라이스 요소를 삭제하면서 순회하기
왜 발생하는가: 루프 중간에 요소를 삭제하면 슬라이스의 인덱스가 앞으로 당겨지면서, 다음 순서의 요소를 건너뛰게 돼요.
✅ 해결법: 역순으로 루프를 돌거나(뒤에서부터 앞으로), 삭제할 인덱스를 따로 저장했다가 루프가 끝난 뒤 한꺼번에 처리하세요.
❌ 실수: 맵(Map)의 순서가 매번 달라져서 결과가 불일치함
왜 발생하는가: Go의 맵은 순서가 없는 자료구조예요. 루프를 돌 때마다 데이터가 나오는 순서가 달라지는 것은 정상이에요.
✅ 해결법: 일정한 순서가 필요하다면 맵의 키(key)들을 먼저 슬라이스로 추출한 뒤, 그 슬라이스를 정렬하여 순회하세요.
❌ 실수: 루프 변수를 고루틴(Goroutine) 안에서 바로 사용
왜 발생하는가: 루프 변수는 공유 메모리 공간을 사용하므로, 고루틴이 실행될 때 이미 루프가 다음 단계로 넘어가 버릴 수 있어요.
✅ 해결법: 고루틴을 호출할 때 함수 인자로 변수 값을 넘겨주어 복사본을 전달하세요.
자주 묻는 질문
Q. Go에는 왜 while 문이 따로 없나요?
Go의 설계자들은 언어의 복잡성을 낮추기 위해 의도적으로 키워드를 줄였어요. `for` 하나만으로 모든 반복 형태를 표현할 수 있기 때문에, 굳이 비슷한 기능을 하는 `while`을 따로 만들 필요가 없다고 판단한 것이에요.
Q. for와 range 중 어떤 것을 써야 할까요?
데이터의 처음부터 끝까지 차례대로 훑어야 하는 대부분의 상황에서는 `range`를 쓰는 것이 훨씬 안전하고 가독성이 좋아요. 하지만 특정 인덱스부터 시작해야 하거나, 2칸씩 건너뛰어야 하는 등 세밀한 제어가 필요할 때는 클래식한 `for` 루프가 정답이에요.
Q. 무한 루프를 만드는 가장 깔끔한 방법은 무엇인가요?
조건 없이 `for { ... }`라고 작성하면 됩니다. 이 방식은 서버가 계속해서 요청을 기다리거나, 특정 신호가 올 때까지 대기해야 하는 백그라운드 프로세스에서 아주 흔하게 사용되는 패턴이에요.
Q. 루프 안에서 다른 루프를 돌릴 때(중첩 루프), 바깥쪽 루프를 한 번에 탈출할 수 있나요?
네, 가능해요! Go에서는 '라벨(Label)' 기능을 제공해요. `OuterLoop: for ...`와 같이 이름을 붙여주면, 안쪽 루프에서 `break OuterLoop`라고 작성하여 바깥쪽 루프까지 한 번에 빠져나올 수 있답니다.
반복문 마스터를 위한 마지막 정리
지금까지 Go 언어의 핵심인 반복문 `for`에 대해 깊이 있게 살펴보았어요. 처음에는 `while` 문이 없다는 사실에 당황할 수 있지만, 결국 `for` 하나로 모든 것을 해결하는 Go의 방식은 여러분의 코드를 더 간결하고 단단하게 만들어 줄 거예요.
- Go의 반복문은 오직 for 키워드 하나로 통일되어 있어요.
- 전통적인 방식, 조건식 방식, range 방식의 세 가지 형태를 구분해 사용하세요.
- 컬렉션 순회 시에는 가독성과 안전성을 위해 range를 우선 고려하세요.
- 중첩 루프 탈출이 필요할 때는 라벨(Label)을 활용해 보세요.
- 슬라이스 삭제 시 인덱스 변화에 주의하고, 맵 순회 시 순서가 무작위임을 기억하세요.
오늘 배운 내용을 머릿속으로만 알고 있으면 금방 잊어버려요. 지금 바로 개발 도구를 열고 아래 단계들을 하나씩 실천해 보세요!
- 오늘 할 일: 위에서 소개한 `range` 슬라이스 예제를 직접 타이핑하고 실행해 보기
- 이번 주 할 일: 자신이 작성 중인 프로젝트의 루프를 `range`로 바꿀 수 있는지 검토하기
- 실행 직전 할 일: 반복문 안에서 인덱스 범위를 벗어나지 않도록 경계값 테스트해 보기
혹시 코드를 작성하다가 막히는 부분이 있거나, 이해가 잘 안 되는 패턴이 있다면 언제든 댓글로 질문을 남겨 주세요. 함께 고민하고 답변해 드릴게요! 여러분의 즐거운 Golang 공부를 응원합니다.
관련하여 더 깊이 있는 내용이 궁금하시다면 Go 반복문 완전 정복 가이드 글도 함께 읽어 보시는 것을 추천드려요.