The os package in Go provides the Exit function to immediately terminate the program with a given status code. Exit is typically used to indicate whether a program completed successfully or encountered an error. By convention, exit code 0 indicates success, while non-zero codes indicate various types of failures.
💡 Key Points
os.Exit(0) indicates successful program termination- Non-zero exit codes indicate errors or abnormal termination
- Exit immediately terminates without running deferred functions
- Use
log.Fatal() to log and exit with status 1 panic() exits with status 2 if unrecovered- Exit codes can be checked by shell scripts and CI/CD systems
- Prefer returning errors from main over calling Exit directly
Basic Exit Usage
Use os.Exit to terminate a program with a specific status code:
package main
import (
"fmt"
"os"
)
func main() {
fmt.Println("Starting program")
// Exit with success code
if true {
fmt.Println("Operation successful")
os.Exit(0)
}
// This will not execute
fmt.Println("This won't print")
}
Output:Starting program
Operation successful
Exit with Error Codes
Use different exit codes to indicate different types of failures:
package main
import (
"fmt"
"os"
)
func main() {
args := os.Args
if len(args) < 2 {
fmt.Println("Error: missing required argument")
os.Exit(1)
}
value := args[1]
if value == "invalid" {
fmt.Println("Error: invalid input")
os.Exit(2)
}
fmt.Println("Processing:", value)
os.Exit(0)
}
Output (no args):Error: missing required argument
Exit code: 1
Exit vs Deferred Functions
Important: os.Exit does NOT run deferred functions:
package main
import (
"fmt"
"os"
)
func main() {
defer fmt.Println("This deferred function will NOT run")
fmt.Println("About to exit")
os.Exit(1)
}
Output: Exit Code Conventions
| Exit Code | Meaning | Usage |
| 0 | Success | Program completed successfully |
| 1 | General error | Catchall for general errors |
| 2 | Misuse | Misuse of shell command or unrecovered panic |
| 126 | Cannot execute | Command invoked cannot execute |
| 127 | Command not found | Command not found or path issue |
| 128+n | Fatal error signal | Fatal error signal n (e.g., 130 = Ctrl+C) |
| 130 | Terminated by Ctrl+C | Script terminated by Control-C (SIGINT) |
| 255 | Exit status out of range | Exit status out of range (0-255) |
Exit Functions Comparison
| Function | Exit Code | Runs Defers | Use Case |
os.Exit(0) | 0 | No | Successful termination |
os. Exit(n) | n | No | Error termination with code |
log.Fatal() | 1 | No | Log error and exit |
log.Fatalf() | 1 | No | Log formatted error and exit |
log.Fatalln() | 1 | No | Log error with newline and exit |
panic() | 2 | Yes | Panic (unrecovered exits with 2) |
return from main | 0 | Yes | Normal function return |
Exit Patterns
| Pattern | Code | Use Case |
| Successful Exit | os.Exit(0) | Explicitly exit with success |
| Error Exit | fmt.Fprintln(os.Stderr, err); os.Exit(1) | Print error to stderr and exit |
| Log and Exit | log.Fatal("error message") | Log error and exit with code 1 |
| Conditional Exit | if err != nil { os. Exit(1) } | Exit only on error |
| Cleanup Before Exit | cleanup(); os.Exit(code) | Manual cleanup (defers won't run) |
| Return from Main | return // from main() | Preferred - runs deferred functions |
| Custom Exit Code | const ErrConfig = 3; os.Exit(ErrConfig) | Application-specific error codes |
Best Practices
| Practice | Recommendation | Reason |
| Prefer Return | Return from main() instead of os.Exit | Allows deferred functions to run |
| Use Exit in Main | Only call os.Exit from main or init | Makes testing easier, clearer control flow |
| Return Errors | Return errors from functions, handle in main | Better testability and composability |
| Document Exit Codes | Document what each exit code means | Helps users and scripts understand failures |
| Use log. Fatal Sparingly | Prefer returning errors and exiting in main | Makes code more testable |
| Stderr for Errors | Print errors to os.Stderr, not os.Stdout | Follows Unix conventions |
| Cleanup Resources | Manually cleanup before calling os.Exit | Deferred functions won't run |
Exit vs Return in Main
| Aspect | os.Exit() | return from main() |
| Deferred functions | Do not run | Run normally |
| Exit code | Custom code | Always 0 |
| Testability | Harder to test | Easier to test |
| Resource cleanup | Manual only | Automatic via defer |
| Use case | When you need non-zero exit code | Normal successful completion |
Testing Exit Behavior
| Technique | Approach | Benefits |
| Separate Logic | Extract logic to testable functions | Test logic without testing exit |
| Return Exit Code | Have main() call run() that returns int | Test run() function independently |
| Subprocess Tests | Use exec. Command in tests | Test actual exit behavior |
| Mock Exit | Create variable for exit function | Replace os.Exit in tests |
Common Exit Scenarios
| Scenario | Recommended Exit Code | Example |
| Successful completion | 0 | Normal program completion |
| Invalid arguments | 1 or 64 | Missing or incorrect CLI arguments |
| Configuration error | 78 | Invalid configuration file |
| File not found | 66 | Required file doesn't exist |
| Permission denied | 77 | Insufficient permissions |
| Network error | 1 | Connection failed |
| Database error | 1 | Database connection failed |
| User interruption | 130 | Ctrl+C pressed |