---
title: "바깥은 행, 안쪽은 열이다"
description: "바깥이 한 번 돌 때마다 안쪽은 처음부터 끝까지 전부 돕니다. 별찍기는 줄마다 개수를 세는 문제입니다."
author: "유니얼"
pubDate: 2026-09-08
category: "programming/csharp/basics"
tags: ["C#", "기초"]
canonical: https://blog.unialgames.com/posts/csharp-nested-loop/
---
반복문 안에도 코드를 넣을 수 있으니, 반복문 안에 반복문을 넣을 수도 있습니다.
새로운 문법이 아닙니다. 지금까지 배운 `for` 를 그대로 겹쳐 놓은 것입니다.

```csharp
for (int i = 1; i <= 3; i++)
{
    for (int j = 1; j <= 3; j++)
    {
        Console.Write("*");
    }
    Console.WriteLine();
}
```

> **바깥이 한 번 돌 때마다 안쪽은 처음부터 끝까지 전부 돕니다.**

아파트를 생각하면 됩니다. 한 층의 모든 집을 다 돌고 나서야 다음 층으로 올라갑니다.
바깥이 층, 안쪽이 호수입니다.

총 실행 횟수는 **바깥 횟수 × 안쪽 횟수**입니다. 위 코드는 별을 아홉 번 찍습니다.

## Write 와 WriteLine 이 행을 만든다

여기서 처음 보는 것이 `Console.Write` 입니다.

| | 하는 일 |
| --- | --- |
| `Console.Write("*")` | 찍고 그 자리에 멈춘다. 다음 것이 옆에 붙는다 |
| `Console.WriteLine()` | 줄을 바꾼다 |

안쪽 반복문이 `Write` 로 별을 옆으로 이어 찍고,
안쪽이 끝난 뒤 바깥에서 `WriteLine()` 으로 줄을 바꿉니다.
**줄바꿈을 어디에 두느냐가 행을 만듭니다.**

`WriteLine()` 을 안쪽 반복문 안에 넣으면 별 하나마다 줄이 바뀌어서
세로로 한 줄씩 아홉 줄이 나옵니다. 위치 하나로 모양이 완전히 달라집니다.

## 안쪽은 매번 1부터 다시 센다

안쪽 조건에 `i` 를 넣으면 줄마다 개수가 달라집니다.

```csharp
for (int i = 1; i <= 3; i++)
{
    for (int j = 1; j <= i; j++)   // 조건에 i 가 들어갔습니다
    {
        Console.Write("*");
    }
    Console.WriteLine();
}
```

<x-flow>
<script type="application/json">
{
  "label": "삼각형이 만들어지는 과정",
  "dwell": 1.1,
  "lines": [
    "for (int i = 1; i <= 3; i++)",
    "{",
    "    for (int j = 1; j <= i; j++)",
    "    {",
    "        Console.Write(\"*\");",
    "    }",
    "    Console.WriteLine();",
    "}"
  ],
  "cases": [
    {
      "label": "N = 3",
      "state": "i = 1",
      "path": [
        { "line": 0, "why": "첫 줄을 시작합니다.", "state": "i = 1" },
        { "line": 2, "why": "안쪽은 언제나 1 부터 셉니다. 조건은 j <= 1 입니다.", "state": "i = 1 · j = 1" },
        { "line": 4, "why": "줄바꿈 없이 찍습니다.", "state": "i = 1 · 지금 줄: *" },
        { "line": 6, "why": "j 가 2 가 되어 안쪽이 끝났습니다. 여기서 줄을 바꿉니다.", "out": "*" },
        { "line": 0, "why": "바깥이 한 칸 올라갑니다.", "state": "i = 2" },
        { "line": 2, "why": "안쪽은 또 1 부터입니다. 이번 조건은 j <= 2 입니다.", "state": "i = 2 · j = 1" },
        { "line": 4, "state": "i = 2 · 지금 줄: *" },
        { "line": 2, "why": "아직 j 가 i 이하입니다. 한 번 더 돕니다.", "state": "i = 2 · j = 2" },
        { "line": 4, "state": "i = 2 · 지금 줄: **" },
        { "line": 6, "why": "두 개를 찍고 줄을 바꿉니다.", "out": "**" },
        { "line": 0, "state": "i = 3" },
        { "line": 2, "why": "다시 1 부터. 이번엔 세 번 돕니다.", "state": "i = 3 · j = 1" },
        { "line": 4, "state": "i = 3 · 지금 줄: *" },
        { "line": 2, "state": "i = 3 · j = 2" },
        { "line": 4, "state": "i = 3 · 지금 줄: **" },
        { "line": 2, "state": "i = 3 · j = 3" },
        { "line": 4, "state": "i = 3 · 지금 줄: ***" },
        { "line": 6, "why": "세 줄이 완성됐습니다.", "out": "***" }
      ],
      "note": "안쪽 반복 횟수는 그때의 i 값과 같습니다. 그래서 삼각형이 됩니다."
    }
  ]
}
</script>
</x-flow>

`j` 가 1부터 다시 시작하는 것이 중요합니다.
`int j = 1` 은 안쪽 `for` 의 시작 칸이고, 시작 칸은 그 반복문에 들어갈 때마다 실행됩니다.
바깥이 한 바퀴 돌 때마다 안쪽 반복문에 새로 들어가는 것이니 `j` 도 새로 1이 됩니다.

달력과 같습니다. 달이 바뀌면 날짜는 다시 1일부터입니다.

| 바깥 i | 안쪽 j 가 도는 값 | 별 개수 | 출력 |
| --- | --- | --- | --- |
| 1 | 1 | 1 | `*` |
| 2 | 1, 2 | 2 | `**` |
| 3 | 1, 2, 3 | 3 | `***` |
| 4 | 1, 2, 3, 4 | 4 | `****` |
| 5 | 1, 2, 3, 4, 5 | 5 | `*****` |

## 별찍기가 뭔가

방금 만든 삼각형이 이른바 **별찍기** 입니다.
이중 반복문 연습으로 거의 항상 나오는 문제라 이름이 붙었습니다.

하는 일은 단순합니다. 별(`*`)과 공백만 써서 정해진 모양을 콘솔에 출력합니다.
줄 수 `N` 을 주면 그 크기에 맞게 나와야 합니다.

`N = 5` 일 때 자주 나오는 모양들입니다.

**직각 삼각형** (방금 만든 것)

```text
*
**
***
****
*****
```

**오른쪽 정렬**

```text
    *
   **
  ***
 ****
*****
```

**피라미드**

```text
    *
   ***
  *****
 *******
*********
```

모양만 보면 셋이 전혀 다른 문제 같습니다. 실제로는 뼈대가 똑같습니다.
**바깥 반복문이 줄을 세고, 안쪽 반복문이 그 줄에 찍을 것을 셉니다.**
방금 본 그 구조 그대로입니다.

이 문제가 연습으로 쓰이는 이유가 있습니다.

- 바깥이 행, 안쪽이 열이라는 것이 눈에 그대로 보입니다.
- 틀리면 모양이 바로 깨지니 결과를 확인하기 쉽습니다.
- 줄마다 개수가 달라지므로 **안쪽 조건을 바깥 변수로 쓰는 연습**이 됩니다.

여기서 어려운 것은 문법이 아닙니다. 문법은 지금까지 본 `for` 가 전부입니다.
어려운 것은 **몇 개를 찍을지를 줄 번호로 나타내는 것**입니다.

## 별찍기는 규칙을 먼저 찾는 문제다

그래서 별찍기 문제를 만나면 코드부터 쓰고 싶어지는데, 그러면 잘 안 됩니다.
코드는 규칙을 찾고 나서 기계적으로 나옵니다.

먼저 격자를 그리고 **`i` 번째 줄에 공백 몇 개, 별 몇 개**인지만 셉니다.
오른쪽 정렬 삼각형(N = 5)을 예로 들면 이렇습니다.

<figure class="diagram">
<svg viewBox="0 0 680 240" role="img" aria-label="오른쪽 정렬 삼각형의 5x5 격자. 줄마다 공백이 줄고 별이 늘어난다">
<g font-size="12" fill="var(--text-muted)" text-anchor="end" class="bit">
<text x="82" y="42">i=1</text>
<text x="82" y="82">i=2</text>
<text x="82" y="122">i=3</text>
<text x="82" y="162">i=4</text>
<text x="82" y="202">i=5</text>
</g>
<g fill="var(--bg)" stroke="var(--border)">
<rect x="96" y="24" width="30" height="30" rx="4"></rect>
<rect x="130" y="24" width="30" height="30" rx="4"></rect>
<rect x="164" y="24" width="30" height="30" rx="4"></rect>
<rect x="198" y="24" width="30" height="30" rx="4"></rect>
<rect x="96" y="64" width="30" height="30" rx="4"></rect>
<rect x="130" y="64" width="30" height="30" rx="4"></rect>
<rect x="164" y="64" width="30" height="30" rx="4"></rect>
<rect x="96" y="104" width="30" height="30" rx="4"></rect>
<rect x="130" y="104" width="30" height="30" rx="4"></rect>
<rect x="96" y="144" width="30" height="30" rx="4"></rect>
</g>
<g fill="var(--accent)" stroke="none">
<rect x="232" y="24" width="30" height="30" rx="4"></rect>
<rect x="198" y="64" width="30" height="30" rx="4"></rect>
<rect x="232" y="64" width="30" height="30" rx="4"></rect>
<rect x="164" y="104" width="30" height="30" rx="4"></rect>
<rect x="198" y="104" width="30" height="30" rx="4"></rect>
<rect x="232" y="104" width="30" height="30" rx="4"></rect>
<rect x="130" y="144" width="30" height="30" rx="4"></rect>
<rect x="164" y="144" width="30" height="30" rx="4"></rect>
<rect x="198" y="144" width="30" height="30" rx="4"></rect>
<rect x="232" y="144" width="30" height="30" rx="4"></rect>
<rect x="96" y="184" width="30" height="30" rx="4"></rect>
<rect x="130" y="184" width="30" height="30" rx="4"></rect>
<rect x="164" y="184" width="30" height="30" rx="4"></rect>
<rect x="198" y="184" width="30" height="30" rx="4"></rect>
<rect x="232" y="184" width="30" height="30" rx="4"></rect>
</g>
<g font-size="13" fill="var(--text-muted)">
<text x="300" y="44">공백 4 · 별 1</text>
<text x="300" y="84">공백 3 · 별 2</text>
<text x="300" y="124">공백 2 · 별 3</text>
<text x="300" y="164">공백 1 · 별 4</text>
<text x="300" y="204">공백 0 · 별 5</text>
</g>
<text x="470" y="124" font-size="14" fill="var(--accent)">공백 = N - i</text>
<text x="470" y="146" font-size="14" fill="var(--accent)">별 = i</text>
</svg>
<figcaption>줄 번호 i 로 개수를 나타내는 식만 찾으면 된다.</figcaption>
</figure>

식을 찾았으면 코드는 그대로 옮겨 적으면 됩니다.
공백을 찍는 반복문 하나, 별을 찍는 반복문 하나를 나란히 둡니다.

```csharp
for (int i = 1; i <= N; i++)
{
    for (int s = 0; s < N - i; s++)   // 공백 = N - i
    {
        Console.Write(" ");
    }

    for (int j = 0; j < i; j++)       // 별 = i
    {
        Console.Write("*");
    }

    Console.WriteLine();
}
```

안쪽 반복문이 두 개라도 이중 반복문입니다.
안쪽끼리는 형제이지 겹친 것이 아닙니다.

## 네 가지 패턴이 전부 같은 문제다

별찍기 문제는 모양만 다를 뿐 푸는 방법이 같습니다.
격자를 그리고 표를 채우면 끝입니다.

| 문제 | 공백 개수 | 별 개수 | 메모 |
| --- | --- | --- | --- |
| 역직각 삼각형 | 0 | `N - i + 1` | 줄이 갈수록 별이 줄어든다 |
| 오른쪽 정렬 | `N - i` | `i` | 공백을 먼저 찍는다 |
| 피라미드 | `N - i` | `2 * i - 1` | 별이 홀수 개로 늘어난다 |
| 다이아몬드 | `N - i` | `2 * i - 1` | 피라미드 뒤에 역피라미드 `N - 1` 줄 |

피라미드에서 별이 `2 * i - 1` 인 이유는 좌우로 하나씩 늘어나기 때문입니다.
1, 3, 5, 7, 9. 홀수만 나옵니다.

다이아몬드는 새 문제가 아닙니다. **피라미드를 찍고, 이어서 역피라미드를 찍는 것**입니다.
반복문을 두 번 쓰면 됩니다. 처음부터 다이아몬드 공식을 찾으려 하면 어렵고,
둘로 쪼개면 이미 푼 문제 두 개입니다.

## 정리

- 이중 반복문은 새 문법이 아니라 `for` 안에 `for` 를 넣은 것입니다.
- 바깥이 한 번 돌 때마다 안쪽은 처음부터 끝까지 전부 돕니다.
- 총 실행 횟수는 바깥 횟수 × 안쪽 횟수입니다.
- 안쪽의 시작 칸은 바깥이 한 바퀴 돌 때마다 다시 실행됩니다. `j` 는 매번 1부터입니다.
- 안쪽 조건에 `i` 를 쓰면 줄마다 개수가 달라져서 삼각형이 됩니다.
- `Write` 는 옆으로 이어 찍고 `WriteLine` 은 줄을 바꿉니다. 줄바꿈 위치가 행을 만듭니다.
- 별찍기는 코드 문제가 아니라 **줄 번호로 개수를 나타내는 식을 찾는 문제**입니다.