>_
GolangStepByStep
Intern

Functions

Define, call, and return values from functions

# Why Functions Matter

Functions are the primary unit of behavior in Go. They help you break complex logic into small reusable blocks with clear inputs and outputs. Good function design makes code easier to test, review, and debug.

In production services, most bugs happen at boundaries: input validation, error handling, and resource cleanup. Well-structured functions make these boundaries explicit.

Real-World Analogyclick to expand

Think of functions like stations in an assembly line. One station validates input, one transforms data, one stores it. Each station has a clear contract. If one station fails, it reports the error and the line stops safely.

That is exactly the Go style: small functions, explicit returns, and explicit error values.

# Declaring Functions

Functions are declared with func. Parameters have their type after the name, and the return type comes after the parameter list.

package main

import "fmt"

func greet(name string) string {
    return "Hello, " + name + "!"
}

func main() {
    msg := greet("Gopher")
    fmt.Println(msg)
}

Breaking Down a Function Signature

func greet(name string) string

func starts a function declaration,greet is the name, (name string) is the parameter list, and final string is the return type.

Single responsibility

Keep functions focused on one job. If a function validates, transforms, logs, and persists all at once, split it into smaller functions.

# Multiple Return Values

Go functions can return multiple values. The most common pattern is returning a result and an error:

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("cannot divide by zero")
    }
    return a / b, nil
}

func main() {
    result, err := divide(10, 3)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Printf("Result: %.2f\n", result)
}

# Variadic Functions

Use ...Type to accept a variable number of arguments. Inside the function, it becomes a slice:

func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

fmt.Println(sum(1, 2, 3))       // 6
fmt.Println(sum(10, 20))         // 30

nums := []int{4, 5, 6}
fmt.Println(sum(nums...))      // spread a slice

# Named Return Values (Use Carefully)

Go allows naming return values in the function signature. This can improve readability for short functions, but overusing bare returns can make code harder to follow. Prefer explicit returns in longer functions.

func stats(nums []int) (count int, sum int) {
    count = len(nums)
    for _, n := range nums {
        sum += n
    }
    return // returns current values of count and sum
}

Rule of thumb: named returns are great for tiny helper functions and for documenting meaning of each returned value.

# Anonymous Functions & Closures

Functions are first-class values in Go. You can assign them to variables, pass them as arguments, and return them:

func makeCounter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

func main() {
    counter := makeCounter()
    fmt.Println(counter()) // 1
    fmt.Println(counter()) // 2
    fmt.Println(counter()) // 3
}

# defer — Cleanup on Exit

defer schedules a function to run when the enclosing function returns. Deferred calls execute in LIFO (stack) order:

func readFile(path string) error {
    f, err := os.Open(path)
    if err != nil {
        return err
    }
    defer f.Close() // always runs on return

    // ... work with file ...
    return nil
}

# Real-World Example: Service Function Pipeline

This example shows idiomatic function design in backend code: small helpers, explicit error returns, and composition from main.

package main

        import (
          "errors"
          "fmt"
          "strings"
        )

        func validateEmail(email string) error {
          if !strings.Contains(email, "@") {
            return errors.New("invalid email")
          }
          return nil
        }

        func normalizeEmail(email string) string {
          return strings.TrimSpace(strings.ToLower(email))
        }

        func registerUser(email string) (string, error) {
          email = normalizeEmail(email)
          if err := validateEmail(email); err != nil {
            return "", err
          }
          return "user:" + email, nil
        }

        func main() {
          id, err := registerUser("  DEV@Example.com ")
          if err != nil {
            fmt.Println("register failed:", err)
            return
          }
          fmt.Println("created", id)
        }

⚡ Key Takeaways

  • Functions return multiple values — the (result, error) pattern is idiomatic
  • Functions are first-class values — assign, pass, return them freely
  • Closures capture variables by reference, not by value
  • defer runs on function exit, in LIFO order — great for cleanup
  • Use ...Type for variadic parameters (becomes a slice inside)
practice & review