Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Understanding Goto and Why to Avoid It
Understand why goto creates unmaintainable code and when it might be acceptable.
Goto Statements
An unconditional jump causes execution to jump to another location in the code without checking any condition. In C++, this is implemented via goto statements and statement labels.
Here's an example:
#include <iostream>
#include <cmath>
int main()
{
double value{};
retry: // statement label
std::cout << "Enter a positive number: ";
std::cin >> value;
if (value < 0.0)
goto retry; // goto statement
std::cout << "The square root of " << value << " is " << std::sqrt(value) << '\n';
return 0;
}
This program asks for a positive number. If the user enters a negative number, goto jumps back to the retry label, asking again.
Sample run:
Enter a positive number: -10
Enter a positive number: 16
The square root of 16 is 4
Statement labels have function scope
Statement labels use function scope, meaning they're visible throughout the entire function, even before their declaration. Both the goto and its target label must be in the same function.
You can jump forward as well as backward:
#include <iostream>
void printAnimals(bool skipDogs)
{
if (skipDogs)
goto finish; // jump forward (label not declared yet, but visible due to function scope)
std::cout << "dogs\n";
finish:
; // labels must be followed by a statement (null statement here)
}
int main()
{
printAnimals(true); // jumps over print, no output
printAnimals(false); // prints "dogs"
return 0;
}
Output:
dogs
Note that labels must be attached to a statement, which is why we use a null statement (;) at finish:.
There are two main restrictions on goto:
- You cannot jump between functions
- You cannot jump forward over variable initializations that are still in scope:
int main()
{
goto skip; // ERROR: jumps over initialization
int counter{ 5 }; // this variable would be in scope at skip
skip:
counter += 10; // what value does counter have?
return 0;
}
However, you can jump backward over initializations, and the variable will be reinitialized when the initialization is executed again.
Why you should avoid goto
The goto statement is widely discouraged in modern C++. Almost every use of goto can be replaced with clearer structured control flow (if/else, while, for, break, continue, return).
The goto statement is widely discouraged in modern C++. Computer scientist Edsger Dijkstra wrote a famous paper called "Go To Statement Considered Harmful" explaining the problems with goto.
The main issue is that goto allows jumping arbitrarily through code, creating what's called spaghetti code - code where the execution path is tangled and difficult to follow, like a bowl of spaghetti.
As Dijkstra humorously stated: "the quality of programmers is a decreasing function of the density of go to statements in the programs they produce."
Rewriting goto with modern alternatives
Our earlier retry example using goto:
double value{};
retry:
std::cout << "Enter a positive number: ";
std::cin >> value;
if (value < 0.0)
goto retry;
Can be rewritten more clearly with a do-while loop:
double value{};
do
{
std::cout << "Enter a positive number: ";
std::cin >> value;
}
while (value < 0.0);
The do-while version is clearer because:
- The loop structure is immediately visible
- There's no jumping around to trace
- The condition is in a standard location
The one legitimate use case
Almost any code using goto can be rewritten more clearly with if statements and loops. One exception is breaking out of nested loops without exiting the function:
Advanced note: Here's an example where goto provides a clean exit from nested loops:
#include <iostream>
int main()
{
for (int row{ 1 }; row < 5; ++row)
{
for (int col{ 1 }; col < 5; ++col)
{
std::cout << row << " * " << col << " = " << row * col << '\n';
if (row * col % 7 == 0)
{
std::cout << "Found multiple of 7. Exiting early.\n";
goto done;
}
}
std::cout << "Finished row " << row << '\n';
}
done:
std::cout << "Completed." << '\n';
return 0;
}
Avoid goto statements unless the alternatives significantly harm code readability.
Summary
Unconditional jumps transfer execution to another location without checking conditions, implemented in C++ through goto statements and statement labels.
Statement labels have function scope, meaning they're visible throughout the entire function even before declaration. Both the goto and its target label must exist in the same function.
Jump restrictions prevent jumping between functions and jumping forward over variable initializations that remain in scope, as this would leave variables uninitialized. Backward jumps over initializations are allowed since the initialization executes again.
Spaghetti code results from excessive goto usage, creating tangled execution paths that are difficult to follow and maintain. This earned goto widespread criticism in the programming community.
Modern alternatives like if statements and loops can replace nearly all goto usage with clearer, more maintainable code. The rare exception is breaking out of deeply nested loops without exiting the function.
Goto statements remain in C++ primarily for backward compatibility and niche use cases. Modern programming practice strongly discourages their use except when alternatives would significantly complicate the code.
Understanding Goto and Why to Avoid It - Quiz
Test your understanding of the lesson.
Practice Exercises
Refactoring goto
Understand why goto should be avoided by refactoring goto-based code into structured alternatives.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!