DEV Community

Cover image for GoLang101: Mastering Functions
Kazem
Kazem

Posted on • Edited on

GoLang101: Mastering Functions

Welcome back to our Go series! đź‘‹

In this lesson, we’re going to explore how to define behavior in Go programs using functions. It's time to go a bit deeper and honestly, there's no better place to start than functions. If you're planning to "go" anywhere in Go, you'll be writing a lot of them.

So, let's get into it. Don’t worry, we’ll keep it clean, clear, and practical.


Why Functions Matter (More Than You Think)

You’ve probably already seen a function in Go:

func main() {
    fmt.Println("Hello World!")
}
Enter fullscreen mode Exit fullscreen mode

That’s the entry point for every Go program — the main() function. But functions in Go aren’t just necessary boilerplate. They're powerful tools for breaking your code into manageable, reusable, testable chunks.

A function is like a named recipe. It describes what ingredients (parameters) it needs and what dish (result) it returns. The best part? You can reuse it as many times as you want.


Writing Your Own Function

Let’s write our first custom function.

func greet(name string) {
    fmt.Println("Hello,", name)
}

func main() {
    greet("Gopher")
}
Enter fullscreen mode Exit fullscreen mode

That’s it. The function greet takes one string parameter and prints a friendly hello. Notice the parentheses after the function name — that’s where parameters live.

Reuse: The Real Power of Functions

Imagine needing to print greetings for 100 users. Would you copy-paste the fmt.Println line 100 times? Hopefully not. You just reuse the function:

greet("Alice")
greet("Bob")
greet("Charlie")
Enter fullscreen mode Exit fullscreen mode

Functions make your code smaller, cleaner, and less error-prone.

Functions with Return Values

Need a function that does something and then returns a result? Go makes this really straightforward:

func add(x int, y int) int {
    return x + y
}

func main() {
    sum := add(3, 4)
    fmt.Println("Sum is:", sum)
}
Enter fullscreen mode Exit fullscreen mode

Multiple Return Values? Yup.

func minMax(x int) (int, int) {
    return x - 1, x + 1
}
Enter fullscreen mode Exit fullscreen mode

Passing Data: By Value vs. Reference

By default, Go copies arguments when calling functions. This is called call by value.

func increment(val int) {
    val++
}

func main() {
    x := 5
    increment(x)
    fmt.Println(x) // Still prints 5
}
Enter fullscreen mode Exit fullscreen mode

Want the function to actually change the value? Pass a pointer:

func increment(val *int) {
    *val++
}

func main() {
    x := 5
    increment(&x)
    fmt.Println(x) // Now it prints 6
}
Enter fullscreen mode Exit fullscreen mode

Pointers can be a little intimidating at first, but once you get used to them, they’re a game changer.

Arrays, Slices, and Function Arguments

Passing arrays copies the whole thing — not great for big data. Instead, Go has slices, which are lightweight and efficient.

func doubleFirstElement(nums []int) {
    nums[0] *= 2
}

func main() {
    numbers := []int{1, 2, 3}
    doubleFirstElement(numbers)
    fmt.Println(numbers) // [2, 2, 3]
}
Enter fullscreen mode Exit fullscreen mode

Slices behave like references but are safe and predictable. In Go, slices > arrays, almost always.

Go-Style Functional Programming (Just a Bit)

Go isn’t a “functional” language, but you can treat functions as values.

func apply(fn func(int) int, val int) int {
    return fn(val)
}

func main() {
    double := func(x int) int { return x * 2 }
    fmt.Println(apply(double, 5)) // 10
}
Enter fullscreen mode Exit fullscreen mode

You can also return functions — hello, closures!

func makeMultiplier(factor int) func(int) int {
    return func(x int) int {
        return x * factor
    }
}
Enter fullscreen mode Exit fullscreen mode

This flexibility lets you write dynamic, clean, and composable code — when you need it.

Variadic Functions – The Flexible Argument Trick

Sometimes, you don’t know exactly how many arguments you’ll need. Maybe you want a function that can take 2, 3, or 50 integers — all at once. Go makes this easy with variadic functions.

Let’s build a simple one: getMax, which finds the largest number from a list of integers.

func getMax(vals ...int) int {
    max := vals[0]
    for _, v := range vals {
        if v > max {
            max = v
        }
    }
    return max
}

func main() {
    fmt.Println(getMax(1, 3, 6, 4)) // Outputs 6
}
Enter fullscreen mode Exit fullscreen mode

So What’s Going On Here?

  • The ...int means the function can accept any number of int values.
  • Inside the function, vals behaves just like a slice ([]int).

You Can Also Pass a Slice

Already have your values in a slice? Just use the ... syntax to unpack it:

vals := []int{1, 3, 6, 4}
fmt.Println(getMax(vals...)) // Still prints 6
Enter fullscreen mode Exit fullscreen mode

This makes your functions super flexible — you can accept both raw arguments and pre-built slices.

Defer – Clean Up Later, Worry Less Now

In Go, you can schedule a function to run after the current function finishes, using the defer keyword.

Here’s a simple example:

func main() {
    defer fmt.Println("Bye")
    fmt.Println("Hello")
}

// output: 
Hello
Bye
Enter fullscreen mode Exit fullscreen mode

That’s right — defer delays the call until main() is about to return.

Why Use defer?

It’s perfect for cleanup tasks:

  • Closing files
  • Unlocking resources
  • Logging exits

it's better to use it in prior article, right?

file := openFile()
defer file.Close() // Closes when function ends
Enter fullscreen mode Exit fullscreen mode

Quick Gotcha: Argument Evaluation Timing

Even though the function call is deferred, its arguments are evaluated immediately.

Check this out:

func main() {
    i := 1
    defer fmt.Println(i + 1)
    i++
    fmt.Println("Hello")
}

// output: 
Hello
2
Enter fullscreen mode Exit fullscreen mode

Why not 3? Because i + 1 was evaluated when defer was called, not when it ran.

This little quirk can save you some debugging time later.

--

Function Design Tips (The Human Side)

Let’s get real: code isn't just for machines. It’s for humans too — including future you who returns to this code six months from now.

Here are some friendly tips:

  • Name your functions well. computeRMS() is better than doMath().
  • Keep them focused. One function = one job.
  • Avoid long parameter lists. If you need many values, consider a struct.
  • Shorter is better. If a function's more than ~15 lines, ask yourself why.

We’ve covered a lot of ground in this article. From understanding what functions really are to writing your own, passing data in and out, and keeping things clean and understandable — you’ve now got a solid grasp on how Go handles functions. We also dipped into some more advanced ideas like using functions as values, creating flexible functions with variadic arguments, and making your code safer and cleaner with defer.

Up next, we’ll dive into how go take advantage objected oriented programming. Stay tuned, and happy coding!

Top comments (0)