Be Aware of Pointer References And Cache

Created on Jun 21, 2025  /  Updated on Jun 21, 2025
Disclaimer: Views expressed in this software engineering blog are personal and do not represent my employer. Readers are encouraged to verify information independently.

Introduction

A faced a bug, that is so simple, that it took me sometime to find it. Let’s start with the problem, and explain what was happening.

Problem Description

Let’s take a look at this code ( I knew it’s not well written, but it’s a good minimal example)

type S struct {
	Field1 int
}

type X struct {
	S *S
}

type Y struct {
	sm map[string]*S
}

func main() {
	key := "k"
	s := &S{Field1: 10}
	x := X{S: s}
	y := Y{sm: map[string]*S{
		key: s,
	}}
	x.S.Field1 = 20
	fmt.Printf("Changing x reflected in y map: %d\n", y.sm[key]) // prints 20

	fmt.Println("Caching x struct...")
	c := &Cache{}
	xJSON, err := json.Marshal(x)
	if err != nil {
		panic(err)
	}
	c.Put(key, xJSON)

	y.sm[key].Field1 = -100
	fmt.Printf("Field in y map: %d\n", y.sm[key].Field1) // prints -100
	fmt.Printf("Field in x (live without saving to cache): %d\n", x.S.Field1) // prints -100

	v, ok := c.Get(key)
	if !ok {
		panic("not found")
	}
	var xFromCache *X
	err = json.Unmarshal(v, &xFromCache)
	if err != nil {
		panic(err)
	}

	fmt.Println("Reading x from cache...")
	fmt.Printf("After reading from cache: %d\n", xFromCache.S.Field1) // prints 20
}

type Entry struct {
	key   string
	value []byte
}

type Cache struct {
	mu      sync.Mutex
	entries []Entry
}

func (c *Cache) Put(key string, value []byte) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.entries = append(c.entries, Entry{
		key:   key,
		value: value,
	})
}

func (c *Cache) Get(key string) ([]byte, bool) {
	c.mu.Lock()
	defer c.mu.Unlock()
	for _, entry := range c.entries {
		if entry.key == key {
			return entry.value, true
		}
	}
	return []byte{}, false
}

The problem here that after we changed the values we didn’t save the changes to the cache. If we save the Y struct to the cache, the changes will be reflected only for the Y struct itself, but not for the X struct. so when they are stored in the cache, the changes made to the Y struct will not be reflected in the X struct.

Conclusion

As easy as it is, but it’s really important to understand the implications of pointer references and caching. Always ensure that changes are saved to the cache to avoid unexpected behavior. Specially, when the code is big, and caching behavoir is somewhat hidden after abstractions.