Topic 51 of 100 Control Flow in C Interactive Tutorial

Using goto in C: Labels, Jump Statements, and Examples

12 mins read Last updated: 22 Aug 2026 23,500 views
C Compiler 0.00%

What is Using goto in C? Core Concept & Direct Summary

Core Objective & Problem Statement

How does the `goto` jump statement work, why is unstructured jumping discouraged, and what are valid use cases (e.g. error cleanup in Linux kernel)?

Direct Answer Summary

Every statement we've studied in this cluster plays by structured rules: loops loop, branches branch, break exits one level. Then there's goto — the statement that ignores all the rules. It doesn't loop, it doesn't branch on a condition, it doesn't respect levels. It simply teleports execution to a labeled line anywhere in the function, forward or backward, no questions asked.

For that raw power, goto carries the worst reputation in all of programming. "Goto is dangerous," "goto considered harmful," "never use goto" — if you've heard anything about C folklore, you've heard this. And the reputation is mostly deserved.

But here's the twist that makes goto worth a full tutorial: despite fifty years of warnings, it remains in the language, and skilled programmers still use it — deliberately — in a couple of specific situations where it genuinely produces cleaner code than the alternatives. Understanding goto means understanding both sides: why it earned its infamy, and where it quietly survives.

(Suggested image: the hero graphic — structured code (clean flowchart arrows) on the left vs. goto-jumped code (arrows crossing everywhere like spaghetti) on the right)


How Does Using goto in C Work? In-Depth Mechanics & Architecture

The Syntax: Labels and Jumps

Two pieces work together:

main.c
Run Online
goto label;
// jump to the label
label: // the destination — a name followed by a colon    // execution continues from here

A label is just an identifier with a colon, placed before a statement. A goto jumps directly to it — forward or backward within the same function.

A first (deliberately simple) example:

main.c
Run Online
#include <stdio.h>
int main() {
    printf("Step 1\n");
    goto skip;
    printf("Step 2 — this never prints!\n");
    skip: printf("Step 3\n");
    return 0;
}

Output:

Execution hit goto skip, teleported straight to the skip: label, and "Step 2" became unreachable code. That jumping ability — over anything, to anywhere in the function — is the entire feature.

Backward jumps work too (and yes, this creates a loop without any loop statement):

main.c
Run Online
int i = 1;
start: printf("%d ", i);
i++;
if (i <= 5) {
    goto start;
    // jump back up
}
// Output: 1 2 3 4 5

It works... and it should also make you slightly uncomfortable. That discomfort is correct — we've just reinvented the while loop, badly. Hold that thought.


Why goto Earned Its Bad Reputation

In 1968, computer scientist Edsger Dijkstra published a famous letter titled "Go To Statement Considered Harmful" — and programming was never the same. His argument: programs whose execution jumps around arbitrarily become nearly impossible to follow, verify, or maintain.

Watch it happen. Can you trace what this prints?

main.c
Run Online
int i = 0;
a: i++;
if (i > 10) goto d;
if (i % 2 == 0) goto b;
printf("%d odd\n", i);
goto c;
b: printf("%d even\n", i);
goto a;
c: i += 2;
goto b;
d: puts("done");

Exactly. You'd need a pencil and ten minutes. Every goto creates an invisible thread through the code, and with enough threads you get the classic term: spaghetti code — where understanding any line requires knowing how you arrived there, and arrival paths multiply beyond human tracking.

Structured statements (if, loops, break) exist precisely to tame this. They restrict control flow to patterns the human brain can hold: enter at top, exit at bottom, no teleporting. goto is the escape hatch from that discipline — powerful, and dangerous in direct proportion.

(Suggested image: the spaghetti diagram deserves its own graphic — try tracing the jumps above with crossing arrows; the visual chaos makes Dijkstra's point instantly)


The Two Legitimate Uses (Where goto Still Survives)

So why is goto still in C? Because in two specific situations, the structured alternatives are worse.

Use 1: Escaping Deeply Nested Loops

Remember from the break tutorial: break exits exactly one loop. Breaking out of three nested loops needs flag variables:

main.c
Run Online
// The flag approach — works, but noisy:int found = 0;
for (int i = 0;
i < n && !found;
i++) {
    for (int j = 0;
    j < n && !found;
    j++) {
        for (int k = 0;
        k < n;
        k++) {
            if (data[i][j][k] == target) {
                found = 1;
                break;
            }
        }
    }
}

Every loop header carries the && !found baggage. Now the goto version:

main.c
Run Online
// The goto approach — direct and readable:for (int i = 0;
i < n;
i++) {
    for (int j = 0;
    j < n;
    j++) {
        for (int k = 0;
        k < n;
        k++) {
            if (data[i][j][k] == target) {
                goto found_it;
                // straight out of all three
            }
        }
    }
}
found_it: printf("Target located.\n");

One jump, no flags, intent obvious. Even many "never use goto" programmers make an exception here.

Use 2: Centralized Cleanup (The One That Actually Matters)

This is goto's real modern home — and if you ever read the Linux kernel or systems code, you'll see it everywhere. The pattern handles functions that acquire multiple resources, where any step can fail:

main.c
Run Online
int process_file() {
    FILE *fp = fopen("data.txt", "r");
    if (fp == NULL) {
        goto error_file;
    }
    char *buffer = malloc(1024);
    if (buffer == NULL) {
        goto error_malloc;
    }
    int *results = malloc(500 * sizeof(int));
    if (results == NULL) {
        goto error_results;
    }
    // ... actual work here ...
    free(results);
    free(buffer);
    fclose(fp);
    return 0;
    error_results: free(buffer);
    error_file: fclose(fp);
    return -1;
}

Look at the structure: each error label cleans up exactly what was acquired before the failure point, then falls through to the next cleanup. Without goto, you'd need nested if-else pyramids or repeated cleanup code in every failure branch. With it, error handling flows in one clean column at the bottom. This pattern is so established in systems C that it has a name — and it's genuinely the tidiest solution the language offers.

(Suggested image: the cleanup pattern as a diagram — three acquisition steps with arrows from each failure point down to the cascade of cleanup labels)


The Rules and Limits

  1. goto can't leave the function. Jumps stay within the current function only — no teleporting between functions.
  2. Jumping into the middle of a loop or block from outside it is legal syntax in some forms but produces undefined or chaotic behavior with variable initialization — never do it.
  3. Jumping forward past variable declarations with initialization (especially variable-length arrays) is restricted in modern C — the compiler will complain, and rightly so.
  4. Labels are function-scoped — two labels with the same name in one function = compile error.
  5. A label must be followed by a statement — if you want a label at the very end of a block, put an empty statement after it:
    main.c
    Run Online
    done:
    ;
    // null statement — label needs something to label

goto vs break vs continue: The Jump Statement Family

gotobreakcontinue
Jumps toAny label in the functionJust past the current loop/switchNext iteration of current loop
Escape nested loops✓ All levels at once✗ One level only✗ Stays in its loop
DirectionForward or backwardForward (out) onlyTo loop's next pass
Needs a label✓ YesNoNo
ReputationUse with extreme careEveryday toolEveryday tool

The Common goto Mistakes

  1. Using it where a loop belongs — if your goto creates repetition, you wanted for/while. Always.
  2. Backward jumps in general — they create loops-in-disguise; nearly all defensible goto uses jump forward (exits and cleanup).
  3. Multiple goto targets converging from everywhere — if a label is jumped to from five places, nobody can reason about the program state at that label anymore.
  4. Using goto to avoid restructuring bad logic — goto patches design problems; it doesn't fix them. Deep nesting that "needs" many gotos usually needs functions instead.
  5. Skipping variable initializations — jumping over int x = 5; leaves x uninitialized when used below; modern compilers warn for good reason.

(Suggested image: "5 goto Mistakes" checklist graphic — consistent with the series)


The Verdict: A Simple Policy

After fifty years of debate, the working consensus among C programmers is remarkably stable:

  • Learning C? Pretend goto doesn't exist. Build structured habits first.
  • Normal application code? Loops, break, continue, and functions cover everything. You will not miss it.
  • Systems code with multi-resource cleanup? The cleanup-label pattern is idiomatic, expected, and respected.
  • Deeply nested loop escape? Acceptable when flags would be uglier.

Dijkstra's warning wasn't "goto is evil" — it was that unrestricted jumping destroys readability. Restricted, disciplined, forward-only, cleanup-focused goto survives precisely because it stays inside those fences.


Final Thoughts

goto completes your tour of C's jump statements, and with it, the entire control flow story. You now know the full spectrum: the disciplined structures (if, switch, loops), the controlled escapes (break, continue), and the raw teleport (goto) that the first two were invented to replace.

The mature takeaway isn't fear — it's judgment. goto is a power tool with no safety guard: in disciplined hands it solves two real problems elegantly; in casual hands it writes spaghetti. Learn the structured tools until they're instinct, and you'll know exactly when — if ever — reaching past them makes sense.


Runnable C Code Example & Practical Implementation

main.c
Run Online
#include <stdio.h>
#include <stdlib.h>
int processResources() {
    int *buf1 =(int*)malloc(sizeof(int) * 10);
    if (!buf1) goto err_buf1;
    int *buf2 =(int*)malloc(sizeof(int) * 10);
    if (!buf2) goto err_buf2;
    printf("All resources allocated successfully.\
");
    // Free in reverse order
    free(buf2);
    free(buf1);
    return 0;
    err_buf2:
    free(buf1);
    err_buf1:
    return -1;
    // Allocation failure
}
int main() {
    processResources();
    return 0;
}

Key Takeaways, Best Practices & Memory Safety Rules

Best Practice & Key Takeaways
Limit goto strictly to downward error-cleanup labels at the end of functions; never jump upwards to create arbitrary loops.

Frequently Asked Questions & Technical Interview Insights

Technical Interview & Viva Questions

Frequently asked C programming interview questions
Q

Q: Why does the Linux kernel extensively use `goto` for error handling?

A: Because C lacks C++/Java `try...finally` exception handling. `goto` provides clean, centralized resource deallocation and error unwinding without duplicating cleanup code across dozens of nested `if` blocks.
Q

Q: Can a `goto` jump into another function?

A: No, labels have function scope in C.

Related Tools, Tutorials & Authoritative References

Recommended Interactive Tools

Authoritative Standards & Citations