Error handling and defer in Go (Concurrency and synchronization) | Patterns, idioms, and best practices in Go

Error handling in Go is significantly different from the familiar Java approach with exceptions. Instead of try/catch, Go uses returning an error as a separate value, and `defer` helps safely release resources regardless of whether an error occurred or not.

1. Error handling idioms

Error handling idioms - Explicit error checking ⚠️. errors are returned and checked immediately after the call

In Go, it is customary to return an error as the second value of a function and check it immediately. This is a simple but powerful idiom.


// Go: error checking
func readFile(path string) ([]byte, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, err // return error up
    }
    return data, nil
}

func main() {
    content, err := readFile("file.txt")
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Println(string(content))
}

// Java: error checking via exception
try {
    byte[] content = Files.readAllBytes(Paths.get("file.txt"));
    System.out.println(new String(content));
} catch (IOException e) {
    System.out.println("Error: " + e.getMessage());
}
In Go, error handling is explicit and local. This allows you to immediately see where a problem may arise, without hidden exceptions.

2. defer for cleanup

Error handling idioms - Explicit error checking ⚠️. errors are returned and checked immediately after the call

`defer` allows postponing the execution of a function until exiting the current function. Very convenient for releasing resources: closing files, connections, mutexes, etc.


// Go: defer for closing a file
func processFile(path string) error {
    file, err := os.Open(path)
    if err != nil {
        return err
    }
    defer file.Close() // guaranteed closure on any exit from the function

    // reading the file
    content, err := io.ReadAll(file)
    if err != nil {
        return err
    }
    fmt.Println(string(content))
    return nil
}

// Java: try-with-resources
try (FileInputStream file = new FileInputStream("file.txt")) {
    byte[] content = file.readAllBytes();
    System.out.println(new String(content));
} catch (IOException e) {
    System.out.println("Error: " + e.getMessage());
}
defer guarantees that resources will be released even if an error occurs in the function. This makes the code cleaner and safer.

3. Common Patterns and Tips

  • Check for errors immediately after calling functions: if err != nil.
  • Use defer to release any resources: files, connections, locks.
  • You can defer multiple calls — they execute in reverse order (stack).
  • Don't use panic for ordinary errors — only for truly critical situations.
  • For Java developers: defer ≈ try-with-resources, explicit error checking ≈ catch/throw, but without exceptions.
The combination of explicit error checking and defer allows writing reliable code that is safe in a multithreaded or parallel context.

Output

In Go, error handling and resource management via defer form the basis of safe and stable code. Unlike Java, where errors are handled through exceptions and try-with-resources, Go emphasizes clarity: each function returns an error that must be handled, and resources are released automatically via defer.

The main advantages of this approach:

  • Explicit error checking makes it easier to understand the program flow and reduces the chance of missing a critical situation.
  • defer guarantees resource release even in the case of errors or early exit from the function.
  • Fewer hidden effects compared to exceptions — the code is predictable and readable.
  • Suitable for parallel and concurrent code, where errors should be handled locally, and resources should close regardless of the order of completion of goroutines.

For a Java developer, this is useful as a contrast: Go requires more discipline in error checking, but in return offers a simple, reliable, and straightforward model of resource management and error handling.

Comparison of Go vs Java on Error Handling and defer

Concept Go Java Comment
Error handling Return error as value (error), explicit check if err != nil Exceptions try/catch, automatic propagation up the stack Go makes errors visible locally; Java hides errors until catch
Resource release defer executes on function exit, guaranteed closing of files, connections, mutex try-with-resources or finally, more verbose In Go, release is tied to function, easier to read and prevents resource leaks
Error handling in concurrent code Errors are local, each goroutine handles its own errors Exceptions can "bubble up" between threads, requires separate handling Go simplifies error management in parallel threads
Code complexity Explicit, linear, fewer hidden effects Hidden effects through exceptions, requires try/catch/finally, sometimes harder to read Go is a predictable flow, easier to analyze and test
Advantages Simplicity, reliability, safety for concurrency Automatic resource management through try-with-resources, familiar to Java developers Go is suitable for production code with high concurrency, fewer hidden pitfalls

🌐 На русском
Total Likes:0

Оставить комментарий

My social media channel
By sending an email, you agree to the terms of the privacy policy

Useful Articles:

Variables and Constants in Java
Variables in Java — concept, types, scope, and constants Hello everyone! This is Vitaly Lesnykh. In this lesson, we will discuss what variables are in Java, why they are needed, what types there are, ...
When HashMap starts killing production: the engineering story of ConcurrentHashMap
Imagine a typical production service. 32 CPU hundreds of threads configuration / session / rate limits cache tens of thousands of operations per second And somewhere inside — a regular Map. At first...
Bitwise Operators in Java
Bitwise Operators in Java In the Java programming language, several bitwise operators are defined. These operators are applied to integer data types, such as byte, short, int, long, and char. List of ...

New Articles:

Concurrency is not about “starting many threads”. It’s about agreements between them. Imagine a restaurant kitchen: — cooks (threads / goroutines) — orders (tasks) — and the main question: how do th...
When HashMap starts killing production: the engineering story of ConcurrentHashMap
Imagine a typical production service. 32 CPU hundreds of threads configuration / session / rate limits cache tens of thousands of operations per second And somewhere inside — a regular Map. At first...
Zero Allocation in Java: what it is and why it matters
Zero Allocation — is an approach to writing code in which no unnecessary objects are created in heap memory during runtime. The main idea: fewer objects → less GC → higher stability and performance. ...
Fullscreen image