Go Tutorials: Go Methods

Go methods become much easier once you understand one idea:

A method is simply a function attached to a type.

And the important rule is:

A receiver can be based on almost any type you define in the same package — not only a struct.

You can create methods on your own defined types based on:

struct
int
string
float
slice
map
array
function
channel

The struct case is simply the most common one.


1. What is a Method in Go?

A normal function looks like this:

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

You call it like:

greet("Rajesh")

A method has one extra part called the receiver:

func (u User) Greet() {
	fmt.Println("Hello", u.Name)
}

The receiver is:

(u User)

So:

func (receiver) MethodName(parameters) return-types

Example:

func (u User) Greet() {
	fmt.Println("Hello", u.Name)
}

You call the method through the value:

u.Greet()

instead of:

Greet(u)

2. Why Do We Need Methods?

Suppose we have:

type User struct {
	Name string
}

Without a method:

func greet(u User) {
	fmt.Println("Hello", u.Name)
}

Call:

greet(user)

With a method:

func (u User) Greet() {
	fmt.Println("Hello", u.Name)
}

Call:

user.Greet()

The method version is usually easier to understand because the behavior belongs naturally to User.

Think:

User
 ├── Name
 ├── Greet()
 ├── Login()
 ├── Logout()
 └── ChangePassword()

Instead of having unrelated-looking functions:

greet(user)
login(user)
logout(user)
changePassword(user)

3. Function vs Method

FeatureFunctionMethod
Attached to type
Has receiver
Called using .Usually no
Exampleadd(10,20)user.Greet()
Best forGeneral operationsBehavior belonging to a type

Function:

func Add(a, b int) int {
	return a + b
}

Call:

Add(10, 20)

Method:

func (c Calculator) Add(a, b int) int {
	return a + b
}

Call:

calculator.Add(10, 20)

4. Basic Method Example

package main

import "fmt"

// User is our own defined type.
type User struct {
	Name string
}

// Greet is a method attached to User.
//
// u     = receiver variable
// User  = receiver type
func (u User) Greet() {
	fmt.Println("Hello", u.Name)
}

func main() {

	// Create a User value.
	user := User{
		Name: "Rajesh",
	}

	// Call the method.
	user.Greet()
}

Output:

Hello Rajesh

The important line is:

func (u User) Greet()

Break it down:

func
 │
 │    receiver
 │    ┌──────┐
func (u User) Greet()
      │ │     │
      │ │     └── method name
      │
      └── receiver variable

5. What Is a Receiver?

The receiver tells Go:

“This function belongs to this type.”

Example:

func (u User) Greet() {
}

Here:

u

is the receiver variable.

And:

User

is the receiver type.

Inside the method, u gives you access to that particular User.

Example:

func (u User) Greet() {
	fmt.Println(u.Name)
}

If:

user := User{Name: "Rajesh"}

and you call:

user.Greet()

then inside the method:

u.Name

is:

Rajesh

6. The Most Important Receiver Rule

A simplified rule that is excellent for learning is:

You can define methods on your own defined types in the same package.

The underlying type does not have to be a struct.

For example:

type Age int
type Name string
type Numbers []int
type Scores map[string]int
type Coordinates [2]int
type Task func()
type MessageChannel chan string

All of these are types you defined.

Therefore, methods can be attached to them.


7. Struct Receiver

This is by far the most common case.

package main

import "fmt"

type User struct {
	Name string
}

func (u User) Greet() {
	fmt.Println("Hello", u.Name)
}

func main() {

	u := User{
		Name: "Rajesh",
	}

	u.Greet()
}

Output:

Hello Rajesh

When should you use it?

Struct methods are excellent when modeling things such as:

User
Employee
Customer
Order
Product
Server
Database
Car
BankAccount

For example:

type BankAccount struct {
	Balance float64
}

Possible methods:

Deposit()
Withdraw()
Balance()

8. Custom int Receiver

Methods can also belong to your own integer type.

package main

import "fmt"

// Age is a new defined type
// whose underlying type is int.
type Age int

// IsAdult is a method on Age.
func (a Age) IsAdult() bool {
	return a >= 18
}

func main() {

	var age Age = 25

	fmt.Println(age.IsAdult())
}

Output:

true

Notice:

type Age int

This does not mean Age is merely a variable.

It creates a new defined type.

Then:

func (a Age) IsAdult() bool

attaches behavior to it.

This can make code very readable:

age.IsAdult()

9. Custom string Receiver

You can create methods on your own string type.

package main

import (
	"fmt"
	"strings"
)

type Name string

func (n Name) Upper() string {
	return strings.ToUpper(string(n))
}

func main() {

	name := Name("rajesh")

	fmt.Println(name.Upper())
}

Output:

RAJESH

Notice this:

string(n)

Because Name is our own type:

type Name string

we convert it back to string when calling a standard library function expecting a string.


10. Slice Receiver

A slice can also be the underlying type of your own defined type.

package main

import "fmt"

type Numbers []int

func (n Numbers) Sum() int {

	total := 0

	for _, value := range n {
		total += value
	}

	return total
}

func main() {

	nums := Numbers{10, 20, 30}

	fmt.Println(nums.Sum())
}

Output:

60

This is quite useful.

Instead of writing:

Sum(nums)

you can write:

nums.Sum()

It reads naturally:

Numbers, calculate your sum.


11. Map Receiver

Maps can also have methods when you create your own defined map type.

package main

import "fmt"

type Scores map[string]int

func (s Scores) Get(name string) int {
	return s[name]
}

func main() {

	scores := Scores{
		"Rajesh": 90,
		"Amit":   80,
	}

	fmt.Println(scores.Get("Rajesh"))
}

Output:

90

You could also create methods such as:

Add()
Remove()
Exists()
Average()
Highest()

Example conceptually:

scores.Get("Rajesh")
scores.Add("John", 95)
scores.Remove("Amit")

12. Array Receiver

Arrays can also be used.

package main

import "fmt"

type Coordinates [2]int

func (c Coordinates) Print() {
	fmt.Println(c[0], c[1])
}

func main() {

	c := Coordinates{10, 20}

	c.Print()
}

Output:

10 20

Here:

type Coordinates [2]int

creates a new defined type based on an array.

Then:

func (c Coordinates) Print()

adds behavior to it.


13. Function Receiver

This is surprising when first learning Go.

Even a function can be the underlying type of a type with methods.

package main

import "fmt"

type Task func()

func (t Task) Run() {

	fmt.Println("Starting task...")

	t()
}

func main() {

	task := Task(func() {
		fmt.Println("Task running")
	})

	task.Run()
}

Output:

Starting task...
Task running

Here:

type Task func()

defines a function type.

Then:

func (t Task) Run()

adds a method to it.

And because t itself is a function, this runs it:

t()

14. Channel Receiver

Channels can also be used.

package main

import "fmt"

type MessageChannel chan string

func (m MessageChannel) Send(message string) {
	m <- message
}

func main() {

	ch := make(MessageChannel, 1)

	ch.Send("Hello")

	fmt.Println(<-ch)
}

Output:

Hello

Instead of writing:

ch <- "Hello"

you created behavior:

ch.Send("Hello")

This can sometimes make APIs easier to understand.


15. Float Receiver

The same idea applies to floating-point types.

package main

import "fmt"

type Price float64

func (p Price) WithTax() Price {
	return p * 1.18
}

func main() {

	price := Price(100)

	fmt.Println(price.WithTax())
}

Output:

118

Or approximately that value depending on formatting.

This gives you expressive code:

price.WithTax()

16. You Cannot Attach a Method Directly to int

This is invalid:

func (x int) Double() int {
	return x * 2
}

Go will reject it.

Why?

Because:

int

is a built-in type defined by Go.

You did not define it in your package.

Instead, define your own type:

type MyInt int

Then:

func (x MyInt) Double() MyInt {
	return x * 2
}

Complete example:

package main

import "fmt"

type MyInt int

func (x MyInt) Double() MyInt {
	return x * 2
}

func main() {

	number := MyInt(10)

	fmt.Println(number.Double())
}

Output:

20

17. Same Package Rule

Suppose package people defines:

package people

type User struct {
	Name string
}

From another package you cannot do this:

package main

import "example.com/people"

func (u people.User) Greet() {
}

❌ Not allowed.

Why?

Because User belongs to package people.

Methods for User must be declared in the package where the receiver base type is defined.

So conceptually:

package people

type User struct{}

func (u User) Greet() {}

✅ Correct.


18. Why Does Go Have This Rule?

It keeps ownership clear.

Imagine package A creates:

User

and package B could secretly add:

User.Delete()

and package C adds:

User.Save()

Then it would become difficult to know what methods actually belong to User.

Go avoids this.

The package defining the type controls its methods.


19. Value Receiver

So far, we have mostly written:

func (u User) Greet()

This is called a value receiver.

Example:

package main

import "fmt"

type User struct {
	Name string
}

func (u User) ChangeName() {

	u.Name = "Amit"

	fmt.Println("Inside:", u.Name)
}

func main() {

	user := User{
		Name: "Rajesh",
	}

	user.ChangeName()

	fmt.Println("Outside:", user.Name)
}

Output:

Inside: Amit
Outside: Rajesh

Why?

Because:

(u User)

receives a copy of the User value.

Conceptually:

Original User
     |
     | copy
     v
Method receiver

Changing the copy does not change the original struct.


20. Pointer Receiver

If you want the method to modify the original value, you commonly use a pointer receiver:

func (u *User) ChangeName()

Example:

package main

import "fmt"

type User struct {
	Name string
}

func (u *User) ChangeName() {
	u.Name = "Amit"
}

func main() {

	user := User{
		Name: "Rajesh",
	}

	user.ChangeName()

	fmt.Println(user.Name)
}

Output:

Amit

Now the original struct was modified.


21. Value Receiver vs Pointer Receiver

This is one of the most important method concepts in Go.

Value receiver:

func (u User) Method()

Pointer receiver:

func (u *User) Method()

Main difference:

ReceiverGetsCan modify original?Typical use
UserCopyUsually ❌Read-only behavior
*UserPointerModify the value

Easy memory rule:

(User)
   ↓
copy

(*User)
   ↓
original

22. Complete Value vs Pointer Example

package main

import "fmt"

type Counter struct {
	Value int
}

// Value receiver gets a copy.
func (c Counter) Show() {
	fmt.Println("Current:", c.Value)
}

// Pointer receiver can change original.
func (c *Counter) Increment() {
	c.Value++
}

func main() {

	counter := Counter{
		Value: 10,
	}

	counter.Show()

	counter.Increment()

	counter.Show()
}

Output:

Current: 10
Current: 11

23. Why Don’t We Write &counter?

You may notice:

counter.Increment()

works even though:

Increment()

expects:

*Counter

Go automatically handles this in many ordinary method calls.

You could think of:

counter.Increment()

roughly as:

(&counter).Increment()

when counter is addressable.

This is one reason Go pointer receiver syntax stays relatively clean.


24. Methods Can Have Parameters

A receiver is not a replacement for parameters.

You can have both.

Example:

package main

import "fmt"

type Calculator struct{}

func (c Calculator) Add(a int, b int) int {
	return a + b
}

func main() {

	calc := Calculator{}

	result := calc.Add(10, 20)

	fmt.Println(result)
}

Output:

30

Here:

(c Calculator)

is the receiver.

And:

a int, b int

are normal function parameters.


25. Methods Can Return Values

Exactly like functions.

package main

import "fmt"

type Rectangle struct {
	Width  float64
	Height float64
}

func (r Rectangle) Area() float64 {
	return r.Width * r.Height
}

func main() {

	r := Rectangle{
		Width:  10,
		Height: 5,
	}

	fmt.Println(r.Area())
}

Output:

50

26. Methods Can Return Multiple Values

Methods have the same return capabilities as normal functions.

package main

import "fmt"

type Calculator struct{}

func (c Calculator) Divide(a, b float64) (float64, bool) {

	if b == 0 {
		return 0, false
	}

	return a / b, true
}

func main() {

	calc := Calculator{}

	result, ok := calc.Divide(10, 2)

	fmt.Println(result)
	fmt.Println(ok)
}

Output:

5
true

27. One Type Can Have Many Methods

This is where methods become especially useful.

package main

import "fmt"

type BankAccount struct {
	Owner   string
	Balance float64
}

func (b BankAccount) ShowBalance() {
	fmt.Println("Balance:", b.Balance)
}

func (b *BankAccount) Deposit(amount float64) {
	b.Balance += amount
}

func (b *BankAccount) Withdraw(amount float64) {
	b.Balance -= amount
}

func main() {

	account := BankAccount{
		Owner:   "Rajesh",
		Balance: 1000,
	}

	account.ShowBalance()

	account.Deposit(500)

	account.Withdraw(200)

	account.ShowBalance()
}

Output:

Balance: 1000
Balance: 1300

Now the type feels like one logical object:

BankAccount
     │
     ├── Owner
     ├── Balance
     │
     ├── ShowBalance()
     ├── Deposit()
     └── Withdraw()

28. When Should I Create a Method?

Create a method when the operation logically belongs to the type.

For example:

type User struct{}

Good candidates:

user.Login()
user.Logout()
user.FullName()
user.IsAdmin()

Because all these describe behavior of a User.

For:

type Order struct{}

Methods might be:

order.Total()
order.Cancel()
order.Pay()
order.Ship()

For:

type Numbers []int

methods might be:

numbers.Sum()
numbers.Average()
numbers.Max()

29. When Should I Use a Normal Function Instead?

Not every function needs to be a method.

Suppose:

func Add(a, b int) int

There may be no meaningful object that owns the operation.

Then a normal function is perfectly appropriate.

Use a method when:

"This operation belongs to this type."

Use a function when:

"This is a general operation."

Example:

user.Save()

makes sense.

But:

Math.Add()

may be unnecessary in Go when:

Add(a, b)

is simpler.


30. Where Are Methods Declared?

Methods are usually declared in the same package as their receiver type.

You might have:

project/
│
├── go.mod
│
├── main.go
│
└── user/
    ├── user.go
    └── methods.go

For example:

user/user.go

package user

type User struct {
	Name string
}

user/methods.go

package user

import "fmt"

func (u User) Greet() {
	fmt.Println("Hello", u.Name)
}

These methods don’t have to physically be in the same .go file.

They just need to belong to the same package as the receiver’s defined type.

That distinction is important:

Same package does not mean same file.


31. Receiver Name Can Be Anything

This:

func (u User) Greet()

could technically be:

func (x User) Greet()

or:

func (user User) Greet()

But Go commonly uses short receiver names.

For example:

func (u User)
func (c Customer)
func (o Order)
func (p Product)
func (s Server)

This is common Go style.


32. Avoid this and self

Coming from Java, C++, Python, etc., you may expect:

this

or:

self

Go does not have a special this or self keyword.

Instead you explicitly choose the receiver name:

func (u User) Greet() {
	fmt.Println(u.Name)
}

So:

u

plays the role that this or self often plays in other languages.


33. Methods Are Important for Interfaces

This is a major reason methods matter in Go.

Suppose we define an interface:

type Speaker interface {
	Speak()
}

And a type:

type Dog struct{}

with:

func (d Dog) Speak() {
	fmt.Println("Woof")
}

Then Dog automatically satisfies Speaker.

Complete example:

package main

import "fmt"

type Speaker interface {
	Speak()
}

type Dog struct{}

func (d Dog) Speak() {
	fmt.Println("Woof")
}

func makeSound(s Speaker) {
	s.Speak()
}

func main() {

	dog := Dog{}

	makeSound(dog)
}

Output:

Woof

Notice there is no:

implements Speaker

statement.

Go checks the methods.

Because Dog has:

Speak()

it satisfies:

Speaker

34. Methods Create the Method Set

Every type has a set of methods associated with it.

Suppose:

type User struct{}

and:

func (u User) Greet() {}

func (u User) Name() {}

func (u User) IsAdmin() {}

Then conceptually:

User Method Set
│
├── Greet()
├── Name()
└── IsAdmin()

Interfaces check these methods to determine whether the type satisfies them.


35. Pointer Receivers and Interfaces

This is a slightly more advanced but very important topic.

Consider:

type Speaker interface {
	Speak()
}

type Dog struct{}

func (d *Dog) Speak() {
	fmt.Println("Woof")
}

Because Speak() has a pointer receiver:

*Dog

implements Speaker.

But the plain Dog value does not have that pointer-receiver method in its method set for interface satisfaction.

So this works:

dog := &Dog{}

makeSound(dog)

But this does not:

dog := Dog{}

makeSound(dog)

when Speak() exists only on *Dog.

This distinction matters especially with interfaces.


36. One Important Technical Precision

Earlier we used the simple rule:

“A receiver can be almost any named type you define.”

The precise Go rule is slightly more specific.

The receiver base type must be a defined type belonging to the same package, and the base type cannot be an interface or pointer type.

For normal learning, remember:

You define a type
        ↓
in your package
        ↓
attach methods to it

Examples:

type Age int
type Name string
type User struct{}
type Numbers []int

Perfectly valid.


37. Defined Type vs Alias — Important

Consider:

type Age int

This creates a new defined type.

Therefore:

func (a Age) IsAdult() bool

is valid.

But this:

type Age = int

is a type alias.

It means:

Age is simply another spelling for int.

It does not create a new local defined type that lets you attach methods to built-in int.

So understand the difference:

type Age int

New defined type.

Versus:

type Age = int

Alias.

For methods, you usually want:

type Age int

38. Complete Example: Multiple Receiver Types

Here is one program demonstrating several types.

package main

import (
	"fmt"
	"strings"
)

// ---------- Struct ----------

type User struct {
	Name string
}

func (u User) Greet() {
	fmt.Println("Hello", u.Name)
}

// ---------- Integer ----------

type Age int

func (a Age) IsAdult() bool {
	return a >= 18
}

// ---------- String ----------

type Name string

func (n Name) Upper() string {
	return strings.ToUpper(string(n))
}

// ---------- Slice ----------

type Numbers []int

func (n Numbers) Sum() int {

	total := 0

	for _, number := range n {
		total += number
	}

	return total
}

// ---------- Map ----------

type Scores map[string]int

func (s Scores) Get(name string) int {
	return s[name]
}

// ---------- Array ----------

type Coordinates [2]int

func (c Coordinates) Print() {
	fmt.Println(c[0], c[1])
}

// ---------- Function ----------

type Task func()

func (t Task) Run() {
	fmt.Println("Starting task...")
	t()
}

// ---------- Channel ----------

type MessageChannel chan string

func (m MessageChannel) Send(message string) {
	m <- message
}

func main() {

	// Struct
	user := User{Name: "Rajesh"}
	user.Greet()

	// Integer
	age := Age(25)
	fmt.Println("Adult:", age.IsAdult())

	// String
	name := Name("rajesh")
	fmt.Println("Upper:", name.Upper())

	// Slice
	numbers := Numbers{10, 20, 30}
	fmt.Println("Sum:", numbers.Sum())

	// Map
	scores := Scores{
		"Rajesh": 90,
	}
	fmt.Println("Score:", scores.Get("Rajesh"))

	// Array
	coordinates := Coordinates{10, 20}
	coordinates.Print()

	// Function
	task := Task(func() {
		fmt.Println("Task running")
	})
	task.Run()

	// Channel
	channel := make(MessageChannel, 1)

	channel.Send("Hello from channel")

	fmt.Println(<-channel)
}

Expected output:

Hello Rajesh
Adult: true
Upper: RAJESH
Sum: 60
Score: 90
10 20
Starting task...
Task running
Hello from channel

39. Receiver Types Summary

Underlying typeExampleMethod allowed?
Structtype User struct{}
Integertype Age int
Stringtype Name string
Floattype Price float64
Slicetype Numbers []int
Maptype Scores map[string]int
Arraytype Point [2]int
Functiontype Task func()
Channeltype Messages chan string
Built-in int directlyint
Built-in string directlystring
Type from another packagehttp.Client

40. Method Anatomy

Take:

func (u *User) ChangeName(name string) bool {
	u.Name = name
	return true
}

Breakdown:

func
 │
 │    receiver
 │    ┌───────┐
 │    │
func (u *User) ChangeName(name string) bool {
      │ │       │          │          │
      │ │       │          │          └── return type
      │ │       │          │
      │ │       │          └── parameter
      │ │       │
      │ │       └── method name
      │ │
      │ └── receiver type
      │
      └── receiver variable

So a method contains:

Receiver
Method name
Parameters
Return values
Body

Almost exactly like a function, except for the receiver.


41. How to Design a Method

When creating a type, ask:

What actions naturally belong to this value?

For:

type Employee struct {
	Name   string
	Salary float64
}

Potential methods:

employee.FullName()
employee.GiveRaise()
employee.CalculateTax()
employee.PrintDetails()

You probably wouldn’t create:

employee.ConnectDatabase()

unless database connection genuinely belongs to the employee concept.

Method design should represent logical behavior.


42. Real-World Example

package main

import "fmt"

type BankAccount struct {
	AccountHolder string
	Balance       float64
}

func (a BankAccount) ShowBalance() {
	fmt.Println("Balance:", a.Balance)
}

func (a *BankAccount) Deposit(amount float64) {

	if amount <= 0 {
		fmt.Println("Invalid deposit")
		return
	}

	a.Balance += amount
}

func (a *BankAccount) Withdraw(amount float64) {

	if amount > a.Balance {
		fmt.Println("Insufficient balance")
		return
	}

	a.Balance -= amount
}

func main() {

	account := BankAccount{
		AccountHolder: "Rajesh",
		Balance:       1000,
	}

	account.ShowBalance()

	account.Deposit(500)

	account.ShowBalance()

	account.Withdraw(300)

	account.ShowBalance()
}

Output:

Balance: 1000
Balance: 1500
Balance: 1200

This is a natural place for methods because:

BankAccount
    │
    ├── data
    │   ├── AccountHolder
    │   └── Balance
    │
    └── behavior
        ├── ShowBalance()
        ├── Deposit()
        └── Withdraw()

43. How Method Calls Work Mentally

Given:

account.Deposit(500)

Read it naturally as:

“Ask this account to deposit 500.”

Given:

numbers.Sum()

read:

“Ask these numbers for their sum.”

Given:

age.IsAdult()

read:

“Ask this age whether it represents an adult.”

This mental model makes method APIs much easier to design.


44. Method vs Struct vs Interface

These three concepts are closely connected.

Struct

Stores data.

type Dog struct {
	Name string
}

Method

Adds behavior.

func (d Dog) Speak() {
	fmt.Println("Woof")
}

Interface

Defines required behavior.

type Speaker interface {
	Speak()
}

Together:

        STRUCT
       Dog{Name}
          │
          │ has
          ▼
        METHOD
        Speak()
          │
          │ satisfies
          ▼
       INTERFACE
       Speaker

This relationship is central to Go.


45. The Big Picture

Suppose:

type Dog struct {
	Name string
}

Data:

Name

Method:

func (d Dog) Speak()

Now Dog has behavior:

Dog
 ├── Name
 └── Speak()

Then:

type Speaker interface {
	Speak()
}

Because Dog has Speak():

Dog satisfies Speaker

You do not explicitly declare that relationship.

Go discovers it from the methods.


46. Best Practice: Value or Pointer Receiver?

A useful beginner rule:

Use a pointer receiver when the method changes the value.

func (u *User) ChangeName(name string)

Use a value receiver when it only reads the value and the type is reasonably small.

func (u User) FullName() string

For larger structs, pointer receivers are also often preferred to avoid copying the entire struct.

Example:

type User struct {
	Name  string
	Email string
}

Read-only:

func (u User) DisplayName() string {
	return u.Name
}

Modification:

func (u *User) ChangeEmail(email string) {
	u.Email = email
}

47. One Style Recommendation

If a type has several methods and some need pointer receivers, it is often cleaner to use pointer receivers consistently for that type.

For example:

func (u *User) ChangeName() {}
func (u *User) Save() {}
func (u *User) Validate() {}

rather than randomly mixing receiver styles without reason.

This becomes especially important when interfaces and method sets are involved.


48. Common Beginner Mistakes

Mistake 1: Thinking methods require structs

Wrong:

Method = struct function

Better:

Method = function attached to your own defined type

Structs are just the most common receiver types.


Mistake 2: Trying to attach a method to int

Wrong:

func (x int) Double() int

Correct:

type MyInt int

func (x MyInt) Double() MyInt

Mistake 3: Expecting value receiver changes to persist

This:

func (u User) ChangeName()

works on a copy.

For modification, usually use:

func (u *User) ChangeName()

Mistake 4: Thinking receiver is a normal parameter

This:

func (u User) Greet(name string)

contains:

u      → receiver
name   → parameter

They serve different roles.


Mistake 5: Thinking method must be in same file

No.

It must belong to the appropriate package, not necessarily the same source file.


49. Quick Decision Guide

Ask these questions:

QuestionChoice
Does behavior belong to a type?Method
Is it a general operation?Function
Need to change original value?Pointer receiver
Only reading small value?Value receiver often fine
Want type to satisfy interface?Implement required methods
Want method on int/string?First define your own type
Type belongs to another package?Cannot add methods directly

50. Final Mental Model

Remember this:

TYPE
 │
 │ define data/value
 │
 ▼
METHOD
 │
 │ adds behavior
 │
 ▼
INTERFACE
 │
 │ describes required behavior
 │
 ▼
POLYMORPHISM

And the core syntax is simply:

func (receiver ReceiverType) MethodName() {
}

Example:

func (u User) Greet() {
}

Or with a pointer:

func (u *User) ChangeName() {
}

The most important lesson is:

A Go method is a function attached to a locally defined type through a receiver. The receiver is not limited to structs. Your own types based on integers, strings, floats, slices, maps, arrays, functions, and channels can also have methods.

For everyday Go, however, you will see methods most frequently on:

type Something struct {
	...
}

because structs are how Go commonly groups related data, and methods naturally provide the behavior associated with that data.

Related Posts

Go Tutorials: Modules, Packages, Subpackages, Submodules, and Workspaces

This tutorial gives you one complete mental model for: The most important idea is: And sometimes: These two designs are very different. 1. What is a Go…

Read More

Go Tutorials: Numeric Types Beginner Tutorial

Go has four main families of numeric types: The simplest way to remember them is: Type family Stores Example int Whole numbers, positive or negative -10, 0,…

Read More

Go Tutorials: Go Testing, Benchmarking, and Profiling

These three topics become much easier once you separate the questions they answer: Topic Main question Main Go tool Result Testing Does my code work correctly? go…

Read More

Go Tutorials: Generics

1. What are Generics? Generics let you write one piece of code that works with multiple types while keeping Go’s compile-time type safety. Suppose you want a…

Read More

Go Concurrency Management Made Simple

This tutorial covers: The goal is: One concept → one purpose → one complete runnable example. Every example is independent. Save any example as: and run: 0….

Read More

Go Tutorials: Concurrency Made Simple

This tutorial covers: The goal is simple: One concept → one mental model → one complete example. Every example is independent. You can copy it into main.go…

Read More