The defer keyword in Go is extremely useful for indicating that you want code to run just before a function exits, regardless of where it exits. It’s perfect for ensuring database connections get closed, that files are cleaned up, or that mutexes are always unlocked.

Quick recap of what defer does

Consider the following code:

func SetupUser() error {

db, err := ConnectToDatabase()

if err != nil {

return err

}

defer db.Close() // here it is user, err := AddUser(db)

if err != nil {

return err

}

if err := CreateAPIKey(db, user); err != nil {

return err

}

return nil

}

Once we establish a real connection to the database we defer the `db.Close()` call. This means that regardless of where the function returns (either if AddUser returns an error, CreateAPIKey does, or we return nil at the end), we can be sure that db.Close() will be called.

More importantly, it’s very clear what we are trying to say. We are saying that, regardless of what happens, we are going to close the database connection.

Using defer to clean up readability

Consider the following function that saves a user to an in-memory map called s.users, protected by a sync.Mutex called `s.mu`:

func (s *Service) SaveUser(id string) bool { // do stuff first s.mu.Lock()

id, present := s.users[id]

if present {

s.UpdateUser(id)

s.mu.Unlock() // unlock here

return false

} else {

user := s.AddUser(id)

s.users[id] = user

s.mu.Unlock() // and unlock here

} // do more stuff... return true }

This kind of code is typical, and includes many Unlock() calls. This is manageable (readable) when we have two exit points, but gets messy and hard to follow as these increase.

It quickly becomes tricky to keep track of all the places you have to unlock, and it’s easy to miss one creating a deadlock.

It’s possible to use defer in this kind of non-function way by using an inline func to wrap a set of operations instead:

func (s *Service) SaveUser(id string) { // do stuff first... func() {

s.mu.Lock()

defer s.mu.Unlock() // safely modify the map

id, present := s.users[id]

if present {

s.UpdateUser(id)

return

}

user := s.AddUser(id)

s.users[id] = user

}() // do more stuff... }

Here we lock once, and defer the unlocking once in a very clear and easy to read way. We define the function (without assigning it to a variable) and call it immediately.

Note that we’re not prefixing this with the go keyword, so it won’t run in the background, it’s just like having the code in the top-level function, except by wrapping it with func we can use the defer keyword.

A note about performance

Defer has some performance considerations, as does locking for long periods of time, particularly in high-frequent code (it all adds up), but the improved readability of the code is worth the hit in most cases and you can always come back and optimise it later if you need to.