C Goto Statement

The goto statement makes execution jump directly to a labeled statement in the same function. It is one of C’s simplest control-flow tools, but also one of the easiest to misuse. Most everyday choices and loops are clearer with if, switch, for, and while, but goto still has practical uses in careful C code, especially for shared cleanup paths and leaving deeply nested logic.

Overview: How goto Works

A goto statement names a label. A label is an identifier followed by a colon, placed before another statement. When C executes goto label_name;, control transfers immediately to the statement marked by label_name:. Execution then continues normally from that point.

Labels have function scope. That means a label can be used before or after it appears in the function, but it belongs only to that function. You cannot use goto to jump into another function, return to a caller, or continue in a different source file. For those jobs, C has function calls and return.

Internally, goto is a direct branch in the generated machine code. It does not allocate memory, create a value, or call a function. It changes the next instruction that will run. Because it is so direct, it can bypass ordinary visual structure in the source code. A jump can move forward over several statements or backward to an earlier statement, so the reader must trace labels as well as braces.

That power is the reason goto has a bad reputation. If a program uses jumps for ordinary decisions, loops, and shortcuts, the control flow can become tangled. Code like that is often called spaghetti code because execution moves through the function in surprising directions. Modern C style avoids goto for normal loops and branches, but it does not ban it completely. A single forward jump to a cleanup label can be clearer than repeating the same release code after every error check.

There are also language limits. A goto cannot jump past the declaration of a variably modified object, such as a variable length array, into that object’s scope. C prevents that because the object would not have been created correctly. Even where a jump is legal, you should avoid jumps that skip important setup or make variable values unclear.

Syntax

This complete program shows the general form: a goto statement names a label, and the label marks the statement where execution should continue.

#include <stdio.h>

int main(void)
{
    int value = 3;

    if (value < 0) {
        goto negative_value;
    }

    printf("Value is non-negative: %d\n", value);
    goto done;

negative_value:
    printf("Value is negative: %d\n", value);

done:
    printf("Finished check\n");

    return 0;
}

Output:

Value is non-negative: 3
Finished check
Part Meaning
goto negative_value; Jump statement. If executed, control moves to the matching label.
negative_value: A label. It marks a statement location inside the same function.
done: A second label used as a shared exit point.
Statement after label The code that runs after control reaches the label.

The semicolon belongs to the goto statement, not to the label. A label is followed by a statement. If you need a label at the end of a block, put an empty statement after it, written as a plain semicolon.

Examples

Example 1: Jumping to a Shared Exit

A forward goto can skip the rest of a function’s main path after a special case is found.

#include <stdio.h>

int main(void)
{
    int balance = 40;
    int withdrawal = 75;

    if (withdrawal > balance) {
        printf("Withdrawal denied\n");
        goto finish;
    }

    balance -= withdrawal;
    printf("Withdrawal approved\n");

finish:
    printf("Final balance: %d\n", balance);

    return 0;
}

Output:

Withdrawal denied
Final balance: 40

Because the withdrawal is larger than the balance, the program prints the denial message and jumps to finish. The subtraction is skipped, but the final balance is still printed from one shared location.

Example 2: Leaving Nested Loops

break leaves only the nearest loop. A carefully placed goto can leave multiple nested loops at once.

#include <stdio.h>

int main(void)
{
    int target_row = 2;
    int target_col = 3;
    int found_row = -1;
    int found_col = -1;

    for (int row = 1; row <= 3; row++) {
        for (int col = 1; col <= 4; col++) {
            printf("Checking row %d col %d\n", row, col);

            if (row == target_row && col == target_col) {
                found_row = row;
                found_col = col;
                goto found;
            }
        }
    }

found:
    if (found_row != -1) {
        printf("Found target at row %d col %d\n", found_row, found_col);
    } else {
        printf("Target not found\n");
    }

    return 0;
}

Output:

Checking row 1 col 1
Checking row 1 col 2
Checking row 1 col 3
Checking row 1 col 4
Checking row 2 col 1
Checking row 2 col 2
Checking row 2 col 3
Found target at row 2 col 3

When the target is found, the jump goes directly to found, which is after both loops. Without goto, this example would need a flag checked by both loops, or the search could be moved into a helper function and ended with return.

Example 3: One Cleanup Path for Several Errors

In C, resource cleanup is manual. A common professional use of goto is to send every failure path to the same cleanup code.

#include <stdio.h>

int main(void)
{
    int opened_config = 0;
    int opened_log = 0;
    int config_ok = 1;
    int log_ok = 0;
    int status = 0;

    if (!config_ok) {
        printf("Could not open config\n");
        status = 1;
        goto cleanup;
    }
    opened_config = 1;
    printf("Config opened\n");

    if (!log_ok) {
        printf("Could not open log\n");
        status = 2;
        goto cleanup;
    }
    opened_log = 1;
    printf("Log opened\n");

    printf("Work completed\n");

cleanup:
    if (opened_log) {
        printf("Log closed\n");
    }
    if (opened_config) {
        printf("Config closed\n");
    }
    printf("Status: %d\n", status);

    return status;
}

Output:

Config opened
Could not open log
Config closed
Status: 2

This program simulates opening resources using integer flags so it compiles and produces fixed output. The important pattern is the shape: after a failure, the program sets a status code and jumps to cleanup. The cleanup code checks which resources were actually acquired before releasing them.

How It Works Step by Step

  1. Execution runs statements in normal order until it reaches a goto.
  2. C looks up the named label in the current function. Labels are not variables and do not store values.
  3. Control transfers to the statement after that label.
  4. Statements between the goto and the label are skipped for a forward jump.
  5. For a backward jump, statements run again from the label onward. This can create a loop, although ordinary loop statements are usually clearer.
  6. Local variables keep their current stored values unless the skipped or repeated code would have changed them.

A label does not create a new scope. The scope of ordinary local variables still comes from blocks marked with braces. Also remember that goto does not clean up resources automatically. If you opened a file or allocated memory, your code must still close or free it on every path that leaves the function.

Common Mistakes

Using goto for Ordinary Loops

A backward jump can repeat code, but a while or for loop communicates the intention better. Use loop statements for normal repetition.

#include <stdio.h>

int main(void)
{
    int count = 1;

    while (count <= 3) {
        printf("Count: %d\n", count);
        count++;
    }

    return 0;
}

Output:

Count: 1
Count: 2
Count: 3

This loop is easier to read than a label with goto jumping backward. The initialization, condition, and update are visible in the normal loop structure.

Trying to Jump to Another Function

goto cannot transfer control to a label in a different function. A label name is visible only within the function where it appears. To move work to another function, call that function. To leave the current function, use return.

Jumping Over Important Setup

A legal jump can still be a bad jump if it skips code that prepares data. For example, jumping past an assignment may leave a variable with an old value that does not match what later code expects. Prefer forward jumps to cleanup labels that do not depend on skipped setup, or initialize variables before any possible jump.

Jumping Into a Variable Length Array Scope

C does not allow a goto to jump into the scope of a variable length array or other variably modified type. The array’s size and storage would not have been established. Keep jumps outside such scopes, or restructure the code with functions and ordinary conditionals.

Best Practices

  • Use goto rarely. Reach first for if, switch, loops, break, continue, and return.
  • Prefer forward jumps to clearly named labels such as cleanup, fail, or done.
  • Keep labels near the bottom of the function when they are used for cleanup or a shared exit path.
  • Do not use goto to simulate normal loops unless there is a very specific low-level reason.
  • Initialize status variables and resource handles before any jump can reach cleanup code.
  • When cleaning up multiple resources, release only the resources that were actually acquired.
  • Avoid jumping into the middle of a block. It makes variable state and lifetime harder to reason about.
  • If a function needs many labels and many jumps, split the function into smaller functions.

Practice Exercises

  1. Write a program with two nested loops that searches a small multiplication table for the first product greater than 20. Use goto to leave both loops and print the row and column.
  2. Modify the cleanup example so both simulated resources open successfully. Confirm that both cleanup messages print in the correct order.
  3. Rewrite a simple backward-goto counting loop as a while loop. Compare which version makes the stopping condition easier to see.

Summary

  • goto jumps to a named label inside the same function.
  • Labels have function scope and are written as an identifier followed by a colon.
  • goto is a direct control-flow branch; it does not call functions or clean up resources automatically.
  • Most ordinary control flow is clearer with structured statements such as loops, if, and switch.
  • Practical C code sometimes uses goto for one cleanup path or for leaving multiple nested loops.
  • Use clear label names, initialize state before jumps, and avoid jumps that skip important setup.