Skip to content

Design Patterns: Observer

Posted on:June 10, 2025 at 12:27 PM

Table of Contents

Open Table of Contents

Introduction

In this post, we’ll walk through how to implement the Observer Pattern in Go using a practical example: a YouTube channel that notifies subscribers when new videos are published.

The Observer Pattern is useful when you want to notify multiple components about an event — like subscribers being notified when a new video is out.

Define the Interfaces

We start by defining two interfaces:

type Observer interface {
	Update(msg string)
}

type Subject interface {
	RegisterObserver(Observer)
	RemoveObserver(Observer)
	Notify(msg string)
}

Create the Subject — YouTubeChannel

This struct will manage the list of observers and notify them:

type YouTubeChannel struct {
	name      string
	observers map[Observer]struct{}
}

We’re using a map[Observer]struct to efficiently:

Implement the Subject Methods

func NewYouTubeChannel(name string) *YouTubeChannel {
	return &YouTubeChannel{
		name:      name,
		observers: make(map[Observer]struct{}),
	}
}

func (c *YouTubeChannel) RegisterObserver(o Observer) {
	if o != nil {
		c.observers[o] = struct{}{}
	}
}

func (c *YouTubeChannel) RemoveObserver(o Observer) {
	delete(c.observers, o)
}

func (c *YouTubeChannel) Notify(videoName string) {
	msg := fmt.Sprintf("%s by %s", videoName, c.name)
	for o := range c.observers {
		o.Update(msg)
	}
}

func (c *YouTubeChannel) ReleaseNewVideo(videoName string) {
	c.Notify(videoName)
}

Create Observers — Subscribers

type Subscriber struct {
	name string
}

func NewSubscriber(name string) *Subscriber {
	return &Subscriber{name: name}
}

func (s *Subscriber) Update(msg string) {
	fmt.Printf("I'm %q, and I'll watch the video: %q\n", s.name, msg)
}

Each Subscriber implements the Observer interface. When notified, it prints a message with the video title.

Put It All Together in main()

package main

import (
	"fmt"
)

// Observer defines an interface for receiving updates from a Subject.
type Observer interface {
	Update(msg string)
}

// Subject defines an interface for managing and notifying observers.
type Subject interface {
	RegisterObserver(Observer)
	RemoveObserver(Observer)
	Notify(msg string)
}

// Compile-time check to ensure YouTubeChannel implements Subject interface.
var _ Subject = (*YouTubeChannel)(nil)

// YouTubeChannel represents a subject that notifies subscribers about new videos.
type YouTubeChannel struct {
	name      string
	observers map[Observer]struct{}
}

// NewYouTubeChannel creates a new instance of YouTubeChannel.
func NewYouTubeChannel(name string) *YouTubeChannel {
	return &YouTubeChannel{
		name:      name,
		observers: make(map[Observer]struct{}),
	}
}

// RegisterObserver adds a new observer to the map (no duplicates allowed).
func (c *YouTubeChannel) RegisterObserver(o Observer) {
	if o == nil {
		return // Skip nil observers
	}
	c.observers[o] = struct{}{} // Add observer as key
}

// RemoveObserver deletes the observer from the map.
func (c *YouTubeChannel) RemoveObserver(o Observer) {
	delete(c.observers, o)
}

// Notify sends a message to all registered observers.
func (c *YouTubeChannel) Notify(videoName string) {
	msg := fmt.Sprintf("%s by %s", videoName, c.name)
	for observer := range c.observers {
		observer.Update(msg)
	}
}

// ReleaseNewVideo publishes a new video and notifies observers.
func (c *YouTubeChannel) ReleaseNewVideo(videoName string) {
	c.Notify(videoName)
}

// Subscriber represents an observer that receives notifications from a channel.
type Subscriber struct {
	name string
}

// NewSubscriber creates a new instance of Subscriber.
func NewSubscriber(name string) *Subscriber {
	return &Subscriber{name: name}
}

// Update is called when the subscriber receives a notification.
func (s *Subscriber) Update(msg string) {
	fmt.Printf("I'm %q, and I'll watch the video: %q\n", s.name, msg)
}

func main() {
	channel := NewYouTubeChannel("gustavocd")

	john := NewSubscriber("John Doe")
	alice := NewSubscriber("Alice Doe")
	lee := NewSubscriber("Lee Mon Chu-pao")

	channel.RegisterObserver(john)
	channel.RegisterObserver(alice)
	channel.RegisterObserver(lee)

	channel.ReleaseNewVideo("How to implement the Observer Pattern in Go? 🔥")

	channel.RemoveObserver(alice)

	fmt.Printf("\nAfter removing Alice:\n\n")

	channel.ReleaseNewVideo("Working with maps in Go 🐹")
}

Execute the code in this playground

Conclusion