A function is a named block of code that takes inputs (called parameters), does something with them, and optionally returns a value. You’ve already used one — main — and you’ve called many: fmt.Println, len, append.
This lesson teaches you to write your own.
Defining a function
package main
import "fmt"
func greet(name string) string {
return "Hello, " + name + "!"
}
func main() {
message := greet("Manikandan")
fmt.Println(message)
}
Hello, Manikandan!
Reading the function declaration:
func— keyword that starts every function declarationgreet— the function’s name(name string)— the parameter list. One parameter namednameof typestringstring(after the parens) — the return type. This function returns a string.return ...— the return statement, which sends a value back to the caller
Every function follows the same shape: func name(parameters) returnType { ... }.
Functions with multiple parameters
package main
import "fmt"
func add(a int, b int) int {
return a + b
}
func main() {
fmt.Println(add(3, 5))
}
8
When parameters share the same type, you can collapse them: func add(a, b int) int. Same meaning, less typing.
Functions with no return value
If a function doesn’t return anything, leave the return type out:
func sayHi(name string) {
fmt.Println("Hi,", name)
}
You can still use return to exit early:
func sayHi(name string) {
if name == "" {
return
}
fmt.Println("Hi,", name)
}
Multiple return values
This is one of Go’s most distinctive features: a function can return more than one value. There’s no need for tuples, structs, or output parameters — just list the return types.
package main
import "fmt"
func divide(a, b float64) (float64, float64) {
quotient := a / b
remainder := float64(int(a) % int(b))
return quotient, remainder
}
func main() {
q, r := divide(17, 5)
fmt.Println("Quotient: ", q)
fmt.Println("Remainder:", r)
}
Quotient: 3.4
Remainder: 2
This is used everywhere in Go — most importantly to return a value alongside a possible error:
result, err := someFunction()
You’ll see this exact pattern hundreds of times in real Go code. We’ll cover errors properly in a later section.
Named return values
You can name the return values right in the function signature. Two effects:
- The names act as auto-declared variables inside the function
- A bare
returnreturns whatever those variables currently hold
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return // returns x and y automatically
}
It’s a Go-only convenience. Use it for short functions where named returns make the intent clearer; avoid it in long functions where the bare return becomes hard to follow.
Variadic functions
A variadic function takes a variable number of arguments of the same type. Use ... before the type:
package main
import "fmt"
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
func main() {
fmt.Println(sum(1, 2, 3))
fmt.Println(sum(1, 2, 3, 4, 5, 6, 7))
fmt.Println(sum())
}
6
28
0
Inside the function, nums is just a []int. The caller can pass any number of int values and Go bundles them into a slice.
If you already have a slice, expand it with ...:
// inside main()
numbers := []int{10, 20, 30, 40}
fmt.Println(sum(numbers...))
fmt.Println itself is variadic — that’s why you can pass it any number of values.
Functions are values
In Go, functions are first-class values. You can store them in variables, pass them as arguments, and return them from other functions:
package main
import "fmt"
func add(a, b int) int { return a + b }
func mul(a, b int) int { return a * b }
func compute(op func(int, int) int, x, y int) int {
return op(x, y)
}
func main() {
fmt.Println(compute(add, 3, 4))
fmt.Println(compute(mul, 3, 4))
}
7
12
The parameter op func(int, int) int reads as: “a function that takes two ints and returns an int.” Pass add or mul (or any compatible function) and compute calls it.
This is the foundation for callbacks, sort functions, HTTP handlers, and a hundred other patterns.
Anonymous functions and closures
Sometimes you need a function exactly once — there’s no need to give it a name. Use an anonymous function:
// inside main()
greet := func(name string) {
fmt.Println("Hi,", name)
}
greet("Mani")
Hi, Mani
Anonymous functions can also access variables from the surrounding scope. That’s called a closure:
package main
import "fmt"
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
}
1
2
3
makeCounter returns a function that “captures” the local variable count. Each call to that function modifies the same count. Closures are extremely useful for things like configuration, callbacks, and small state machines.
defer
Go has a unique keyword called defer that schedules a function call to run right before the surrounding function returns — no matter how it returns:
package main
import "fmt"
func main() {
defer fmt.Println("Goodbye!")
fmt.Println("Hello!")
fmt.Println("Doing some work...")
}
Hello!
Doing some work...
Goodbye!
The defer line is encountered first, but its effect runs last. This is mainly used to clean up resources — closing a file, releasing a lock, disconnecting from a database. We’ll see real examples of defer later.
What’s next
Functions are reusable logic. Methods are functions attached to a type — they’re how you build up behavior around your structs. That’s next.