Why Should You Care About the Observer Pattern?

Have you ever felt the FOMO when you miss out on an important update? Imagine a world where you must constantly refresh your email to check for new messages. Sounds painful, right? 😩

Well, that’s exactly how inefficient polling works in software. Instead of constantly checking, wouldn’t it be better if you just got notified automatically when something changed?

That’s where the Observer Pattern comes in! This pattern lets different parts of your system subscribe to updates and automatically receive notifications when an event occurs. It’s the backbone of event-driven programming and is used in real-world systems like:

Live sports score updates
Stock market price alerts
Job portal notifications
E-commerce product price drop alerts
Hashtag-based content subscription (which we’ll build today!)

In this article, we’ll demystify the Observer Pattern and implement a Hashtag Subscription System in Golang, where users can subscribe to specific hashtags and receive notifications when new articles are published.

What is the Observer Pattern?

The Observer Pattern is a behavioural design pattern where:

  • A Subject (Publisher) maintains a list of Observers (Subscribers).
  • Whenever the subject’s state changes, it notifies all registered observers automatically.
  • Observers can subscribe or unsubscribe dynamically without affecting the subject’s core logic. This pattern decouples objects, making systems more modular and scalable.

Implementing the Observer Pattern in Go

Let’s build a Hashtag Subscription System, where:

📌 Users subscribe to hashtags they’re interested in.
📌 When an article is published under a hashtag, subscribers get notified.
📌 Users can unsubscribe from hashtags at any time.
📌 Users can block specific authors if they don’t want to see their content.

Step 1: Define the Observer Interface (Users)

package publisher

import "fmt"

// Observer Interface (Subscribers)
type IHashtagObserver interface {
 Notify(articleID, author string)
}

Each user who wants to receive hashtag updates must implement the Notify method.

Step 2: Define the Subject (Hashtag Manager)

// Hashtag acts as a subject (Publisher)
type Hashtag struct {
 observers map[string]IHashtagObserver
 name      string
 articles  map[string]bool
}

// Register an observer (User)
func (tag *Hashtag) Register(name string, observer IHashtagObserver) {
 tag.observers[name] = observer
}

// Deregister an observer (User)
func (tag *Hashtag) Deregister(name string) {
 delete(tag.observers, name)
}

// Publish a new article
func (tag *Hashtag) NewArticlePublished(articleID, author string) {
 fmt.Printf("\n📢 Notifying subscribers of #%s about new article (ID: %s) by %s\n", tag.name, articleID, author)
 tag.notify(articleID, author)
}

// Notify all observers
func (tag *Hashtag) notify(articleID, author string) {
 for _, observer := range tag.observers {
  observer.Notify(articleID, author)
 }
}

The Hashtag Manager maintains a list of subscribers and notifies them when a new article is published.

Step 3: Implement the Concrete Observer (User)

// User struct acts as an Observer
type User struct {
 id            string
 name          string
 email         string
 slackUserID   string
 hashtags      map[string]bool
 blockedAuthor map[string]bool
}

// Add an author to the blocked list
func (u *User) AddBlockedAuthor(author string) {
 u.blockedAuthor[author] = true
}

// Remove an author from the blocked list
func (u *User) RemoveBlockedAuthor(author string) {
 delete(u.blockedAuthor, author)
}

// Subscribe to a hashtag
func (u *User) AddHashtag(tagName string) {
 u.hashtags[tagName] = true
 publisher.GetHashtag(tagName).Register(u.name, u)
}

// Unsubscribe from a hashtag
func (u *User) RemoveHashtag(tagName string) {
 delete(u.hashtags, tagName)
 publisher.GetHashtag(tagName).Deregister(u.name)
}

// Receive notifications for new articles
func (u *User) Notify(articleID, author string) {
 if u.blockedAuthor[author] {
  fmt.Printf("🚫 %s skipped notification for article (ID: %s) - blocked author: %s\n", u.name, articleID, author)
  return
 }
 fmt.Printf("\n📨 Sending notification to %s\n", u.name)
 if u.email != "" {
  fmt.Printf("📧 Email sent to %s for new article (ID: %s) by %s\n", u.email, articleID, author)
 }
 if u.slackUserID != "" {
  fmt.Printf("💬 Slack notification sent to %s for new article (ID: %s) by %s\n", u.slackUserID, articleID, author)
 }
}

Each user can subscribe/unsubscribe from hashtags dynamically!

Step 4: Bringing It All Together

func main() {
 var (
  tagCleanCode           = "Clean Code"
  tagSoftwareDevelopment = "Software Development"
 )
 publisher.AddNewHashtag(tagSoftwareDevelopment)
 publisher.AddNewHashtag(tagCleanCode)

 // Create users and subscribe them to hashtags
 user.InitUser("1", "Alice", "[email protected]", "alice_slack", []string{tagSoftwareDevelopment}, nil)
 user.InitUser("2", "Bob", "[email protected]", "bob_slack", []string{tagSoftwareDevelopment, tagCleanCode}, nil)
 user3 := user.InitUser("3", "Charlie", "[email protected]", "charlie_slack", []string{tagSoftwareDevelopment, tagCleanCode}, []string{"author1"})

 // Publish new articles under hashtags
 publisher.GetHashtag(tagSoftwareDevelopment).NewArticlePublished("article1", "author2")
 publisher.GetHashtag(tagSoftwareDevelopment).NewArticlePublished("article2", "author1") // Charlie blocked author1
 publisher.GetHashtag(tagCleanCode).NewArticlePublished("article3", "author1")

 // Charlie unsubscribes from "Clean Code" hashtag
 user3.RemoveHashtag(tagCleanCode)
 publisher.GetHashtag(tagCleanCode).NewArticlePublished("article4", "author2") // Charlie won't be notified
}

Expected Output

📢 Notifying subscribers of #Software Development about new article (ID: article1) by author2

📨 Sending notification to Alice
📧 Email sent to [email protected] for new article (ID: article1) by author2
💬 Slack notification sent to alice_slack for new article (ID: article1) by author2

📨 Sending notification to Bob
📧 Email sent to [email protected] for new article (ID: article1) by author2
💬 Slack notification sent to bob_slack for new article (ID: article1) by author2

📨 Sending notification to Charlie
📧 Email sent to [email protected] for new article (ID: article1) by author2
💬 Slack notification sent to charlie_slack for new article (ID: article1) by author2

📢 Notifying subscribers of #Software Development about new article (ID: article2) by author1

📨 Sending notification to Alice
📧 Email sent to [email protected] for new article (ID: article2) by author1
💬 Slack notification sent to alice_slack for new article (ID: article2) by author1

📨 Sending notification to Bob
📧 Email sent to [email protected] for new article (ID: article2) by author1
💬 Slack notification sent to bob_slack for new article (ID: article2) by author1
🚫 Charlie skipped notification for article (ID: article2) - blocked author: author1

📢 Notifying subscribers of #Clean Code about new article (ID: article3) by author1

📨 Sending notification to Bob
📧 Email sent to [email protected] for new article (ID: article3) by author1
💬 Slack notification sent to bob_slack for new article (ID: article3) by author1
🚫 Charlie skipped notification for article (ID: article3) - blocked author: author1

📢 Notifying subscribers of #Clean Code about new article (ID: article4) by author2

📨 Sending notification to Bob
📧 Email sent to [email protected] for new article (ID: article4) by author2
💬 Slack notification sent to bob_slack for new article (ID: article4) by author2

Boom! 🎉 Alice and Bob got their notifications instantly and Charlie also was able to block a particular author! No polling, no wasted CPU cycles. Just clean, efficient event-driven programming.

You can find this complete working code on my Github.

Key Takeaways

The Observer Pattern helps create event-driven, decoupled systems.
✅ It's used in stock updates, job alerts, social media notifications, and more.
✅ Our Hashtag Subscription System shows how users can subscribe, unsubscribe, and receive notifications in real-time.

Final Thoughts

This is just the tip of the iceberg. You can extend this pattern for WebSockets, message brokers, or even real-time dashboards! 🚀

Stay Connected!

💡 Follow me here on LinkedIn for more insights on software development and architecture.
🎥 Subscribe to my YouTube channel for in-depth tutorials.
📬 Sign up for my newsletter, The Weekly Golang Journal, for exclusive content.
✍️ Follow me on Medium for detailed articles.
👨‍💻 Join the discussion on my subreddit, r/GolangJournal, and be part of the community!