AlgoMaster Logo

goto Statement

Last Updated: May 22, 2026

Low Priority
9 min read

goto is the one control-flow keyword in C# you'll rarely reach for. It jumps execution to a labeled statement somewhere else in the same method, sidestepping the normal top-to-bottom flow that if, switch, and loops give you. Knowing how it works (and why most codebases avoid it) is part of being fluent in the language, because goto shows up in switch fall-through, generated code, and the occasional deeply nested loop where there's no cleaner exit.

Syntax

A goto statement has two parts: the label (a name followed by a colon) that marks the target, and the goto keyword that jumps to it.

The label is just a marker. It doesn't do anything on its own, it only gives the goto somewhere to land. Labels follow the same naming rules as variables, and by convention they use PascalCase.

Here's a tiny program that uses both:

The Retry: label sits before the work. goto Retry; jumps back to it as long as attempts < 3. The same logic would be a one-line while loop, which is the first hint that goto is rarely the cleanest tool.

A few syntax rules worth pinning down upfront:

  • A label must be followed by a statement. SomeLabel: on its own at the end of a block is a compile error.
  • Labels live in the method's scope. You can't goto a label in another method, and you can't goto a label from a different method's body.
  • Each label name must be unique within its method's scope.

goto Label

The simplest form of goto jumps to a label in the same method. The jump can go forward (skip ahead) or backward (re-run earlier code).

A forward jump that skips a block:

The goto Done; skips the tax calculation. The same code with an early return from a method would be more idiomatic, but the structure of the jump is clear.

A backward jump that re-runs a block:

That's a while loop with extra steps. Once you've seen the pattern, it's obvious that the loop form reads better. Hold on to that intuition, it's the reason most of the goto examples in this chapter come with a "here's the cleaner alternative" right after.

The diagram shows what goto does to reading flow. Normal code runs top to bottom, one statement after the next. goto introduces a sideways arrow that breaks that pattern, jumping from one point to a label elsewhere. The dotted arrow is the jump. The reader's eye has to track it, and the more of these arrows you add, the harder the method becomes to follow.

goto case and goto default

Inside a switch statement, goto has two extra forms: goto case X; and goto default;. These jump to another case label in the same switch and let you intentionally fall through from one case to the next. We touched on these in the switch chapter, here's the fuller picture.

Unlike C or C++, C# does not allow implicit fall-through between non-empty case blocks. Every case has to end in a statement that doesn't fall through (break, return, throw, continue, or one of the goto forms). That rule plus goto case is how C# lets you share work between cases without surprises.

The goto case chains let "Placed" run through the work of "Confirmed" and "Shipped". A "Confirmed" input would skip the "Placed" line and pick up from "Payment confirmed.", which is exactly the kind of staged behavior goto case is designed for.

goto default; works the same way but jumps to the default label:

With rating = 5, control goes straight to default. If rating were 2, the goto default; would take over after the "Low rating." line so both messages would print.

Two rules to remember about goto case:

  • The target case label must exist in the same switch. goto case "Refunded"; is a compile error if there's no case "Refunded": in that switch.
  • The case value must be a compile-time constant. You can't goto case a variable.

goto case is the one goto form modern C# developers actually use with some regularity, and even then only when the case-to-case relationships are genuinely staged like an order workflow. For simple shared logic, extract a method.

Rules and Restrictions

C# puts real guard rails on where goto can jump. Most of them stop you from writing code that would leave the program in a half-initialized or unsafe state. Hitting one of these rules produces a compile error, never a runtime surprise.

Can't Jump Into a Nested Scope

You can goto out of a nested block (a loop, an if, a try) into an enclosing block. You cannot go the other way and jump into a block that has its own local variables you'd skip over.

What's wrong with this code?

The goto Inside; jumps into the if block, but the path skips the line that declares couponDiscount. The compiler reports CS0159: No such label 'Inside' within the scope of the goto statement. From the perspective of the outer scope, the Inside: label doesn't exist because it lives inside a block the outer code can't see into directly.

Fix: restructure so the label is in the same scope as the goto:

Now couponDiscount is declared before the jump, and the label is in the same scope. The compiler is happy.

Can't Jump Across Method Boundaries

A label belongs to the method it's defined in. goto can only target a label in the same method.

This produces CS0159: No such label 'Target' within the scope of the goto statement. If you need to redirect execution across methods, that's what return and method calls are for.

Can't Skip Variable Initialization

C# requires local variables to be definitely assigned before they're read. A goto that skips an initialization in a way the compiler can't prove safe is rejected:

The path from goto Read; to Console.WriteLine(orderTotal); never assigns orderTotal. The compiler reports CS0165: Use of unassigned local variable 'orderTotal'. This rule is the reason you can't accidentally read garbage values through a goto.

Can't Leave a finally Block

A try-finally block guarantees the finally runs as part of the normal control flow. goto-ing out of a finally would break that guarantee, so the compiler forbids it:

The compiler reports CS0157: Control cannot leave the body of a finally clause. You can goto inside a finally to another label inside the same finally, you just can't jump out of it.

A related rule: you can goto out of a try block, but the finally still runs first. The jump doesn't happen until cleanup completes.

Legitimate Use Cases

For most everyday C# code, you can go years without writing a goto. There are a few specific situations where it's still the cleanest option.

Breaking Out of Deeply Nested Loops

C# doesn't have labeled break, so a break only exits the innermost loop. If you're searching across nested loops and want to bail out of all of them on a hit, goto is one option that doesn't require flag variables or extracting the loops into a method.

The goto Found; exits both loops in one step. The cleaner alternative is usually to extract the search into a method and return on a hit:

The method version is longer in code but easier to test, easier to read at a glance, and doesn't require the reader to track a forward jump. That's the trade most teams make.

State-Machine-Like Dispatch in switch

goto case is well-suited to an order-like workflow where cases genuinely build on each other. An order in state "Placed" might want to also run the work for "Confirmed" and "Shipped" if the input represents a re-run from a known earlier step. The switch chapter has the simple form; a slightly fuller example:

Resuming from "Packed" picks up at the label step and runs through "Shipped". The relationships between cases are the actual workflow, and goto case expresses them directly.

The alternative is a chain of method calls (PrintLabel(); NotifyCustomer();), which is what most production systems end up with once the steps get more complex. The goto case form is fine for small workflows that fit comfortably on one screen.

Generated Code

Source generators and tools that emit C# sometimes produce goto because they're translating from a state-machine representation where labels and jumps are the most direct mapping. Hand-written code rarely needs to do this, but decompiled C# and generated regex engines use goto freely. That's a normal thing for a generator to do, and it's not a sign that the generator is buggy or low quality.

Why goto is Discouraged for Most Code

The case against goto isn't that it's broken or buggy. It's that hand-written code with goto tends to be harder to read and refactor than the same logic expressed with loops, methods, and structured control flow.

A few specific frictions:

  • Reading order breaks. Code without goto reads top to bottom. A goto introduces a sideways jump that the reader has to track in their head, especially when there's more than one in a method.
  • Refactor-hostile. Renaming, extracting, or reordering blocks around a label is easy to get wrong. Move a declaration past the jump and the code that used to work now fails the definite-assignment check.
  • No structured-programming guarantees. Loops and if/else have entry and exit points you can reason about. goto lets execution enter the middle of a block (within the language's scope rules) and leave from anywhere, which makes "what runs before X" harder to answer.
  • Tooling doesn't help much. Step-debuggers handle goto correctly, but most refactor tools, code-shape analyzers, and pattern-recognizers assume structured flow.

Mainstream C# style guides (Microsoft's own, plus the major analyzer rule sets) include rules that flag goto outside goto case and goto default. That doesn't mean the keyword is forbidden, it means you'll have to justify each use to a reviewer. For most code, the alternatives below are easier to explain.

Alternatives

When you find yourself reaching for goto, one of these patterns usually fits better.

Extract a Method and Use return

The deeply nested loop example earlier is the classic case. Wrap the loops in a method, return on the hit, and the outer code reads as a straight line.

The method gives the loop a name (FindStockIndex), makes it testable on its own, and replaces the goto with a return that every reader already understands.

Flag Variables

When extracting a method feels heavy, a flag variable can stop a loop cleanly:

The !foundEmpty in the loop condition takes the role the goto would have played. This pattern is fine for a single loop, but if you have multiple nested loops you want to break out of, the flag has to be re-checked at each level, and at that point extracting a method is usually the better trade.

Restructured Control Flow

Sometimes the goto is hiding a logic structure that another keyword expresses better. The retry-loop example from the start of the chapter is a while. A forward jump that skips a section is often an else branch or an early return. Look at what the goto does and ask: which built-in construct has that shape?

Same behavior as the earlier goto Done; version, no jump required.

Switch Expressions

For dispatching to a value based on a state, a switch expression replaces both goto case chains and if/else if ladders. It's an expression, so it evaluates to a value you can assign or return:

When the cases don't need to share work, this is much cleaner than a switch statement with goto case plumbing. The switch chapter has the full coverage.