Strange Parentheses Before Function Name in Go: A Deep Dive
Imagine: you open someone else's Go code and see something that makes your eye twitch:
func (u User) Greet() string { return "Hello, " + u.Name}Stop. What is this (u User) before the function name? In Python, methods use def greet(self):, in Java — public String greet() inside a class. But here — some parentheses with a type inside. Nonsense? No, it's genius!
This construct is one of the most powerful features of Go, which often confuses beginners. In fact, it's a receiver — the key to understanding object-oriented programming in Go.
In this article, we'll look under the hood of the Go compiler, break down how these parentheses work, and why without them your code will look like it's from the 90s. Ready? Let's go!
What is a receiver and why is it needed?
Go doesn't have classes in the classical sense. But it does have structs and methods. The parentheses before the function name are the declaration of a method for a specific type. Technically, this is called a receiver parameter.
The syntax is simple:
func (receiver_name ReceiverType) MethodName(params) return_type { // method body}Here, receiver_name is a variable through which you access the struct's fields (like self in Python or this in Java). And ReceiverType is your type (usually a struct).
A real-life example:
package main
import "fmt"
type User struct { Name string Age int}
// Method with receiverfunc (u User) SayHello() { fmt.Printf("Hello! I am %s, I am %d years old.", u.Name, u.Age)}
func main() { user := User{Name: "Alice", Age: 25} user.SayHello() // Output: Hello! I am Alice, I am 25 years old.}Notice: we call the method using a dot, just like in any OOP language. But under the hood, Go performs a clever trick.
Value receiver vs Pointer receiver: battle of the titans
In Go, there are two types of receivers: value receiver and pointer receiver. The difference is colossal, and misunderstanding this is a common cause of bugs.
Value receiver
When you write func (u User) ..., Go copies the entire struct. The method works with a copy of the object. Changes inside the method will not affect the original.
func (u User) Birthday() { u.Age++ // This will only change the copy!}
func main() { user := User{Name: "Bob", Age: 30} user.Birthday() fmt.Println(user.Age) // Still 30! Bug?}This code will compile without an error, but it is logically incorrect. You expect the age to increase, but it stays the same. Welcome to the world of value receiver!
Pointer receiver
If you use a pointer: func (u *User) ..., the method receives a reference to the original object. Now changes will be visible to everyone.
func (u *User) Birthday() { u.Age++ // Modifying the original}
func main() { user := &User{Name: "Bob", Age: 30} // Creating a pointer user.Birthday() fmt.Println(user.Age) // 31! It works!}Rule of thumb: if the method modifies the struct's state or the struct is large (to avoid copying), use a pointer receiver. If the method only reads data, use a value receiver.
How does it work under the hood?
Now for the interesting part. When the Go compiler sees:
type Rectangle struct { Width, Height float64}
func (r Rectangle) Area() float64 { return r.Width * r.Height}It transforms this into a regular function, where the receiver becomes the first argument:
Blogs
Book Recommendations
Read also
A/B Testing in PHP: How to Stop Guessing and Start Earning
Caching in TypeScript: Redis, Memcached, and Built-in Solutions (Advanced Level)
json.Unmarshal vs NewDecoder.Decode: which is faster in Go?
Introduction to Go in 2025: Where to Start for Beginners
Yield return in C#: Lazy Magic for Beginners