{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreidzvgobevuhotw274fueswlptp2k4kzxhkzdrh3c2ju7trz7h5sxi",
"uri": "at://did:plc:fdfg7uwpthbb4zdam6e2xtvn/app.bsky.feed.post/3mnps77ztdmig"
},
"description": "Asked to implement a stack in Go. Here's a generic, thread-safe, interface-driven implementation with proper error handling. package main import ( \"errors\" \"fmt\" \"sync\" ) // ErrStackEmpty is returned when attempting to pop or peek an empty stack. var ErrStackEmpty = errors.New(\"stack: operation on empty stack\") // Stacker defines the contract for any stack-like data...",
"path": "/a-stack-data-structure-2/",
"publishedAt": "2026-06-07T18:26:30.000Z",
"site": "at://did:plc:fdfg7uwpthbb4zdam6e2xtvn/site.standard.publication/3mmcvowa3clzx",
"tags": [
"Go",
"AI Code Review"
],
"textContent": "Asked to implement a stack in Go. Here's a generic, thread-safe, interface-driven implementation with proper error handling. package main import ( \"errors\" \"fmt\" \"sync\" ) // ErrStackEmpty is returned when attempting to pop or peek an empty stack. var ErrStackEmpty = errors.New(\"stack: operation on empty stack\") // Stacker defines the contract for any stack-like data structure. type Stacker[T any] interface { Push(item T) Pop() (T, error) Peek() (T, error) Size() int IsEmpty() bool } // Stack is a generic, thread-safe LIFO data structure. type Stack[T any] struct { mu sync.RWMutex items []T } // NewStack constructs a new empty Stack. func NewStack[T any]() *Stack[T] { return &Stack[T]{ items: make([]T, 0), } } // Push adds an item to the top of the stack. func (s *Stack[T]) Push(item T) { s.mu.Lock() defer s.mu.Unlock() s.items = append(s.items, item) } // Pop removes and returns the top item of the stack. func (s *Stack[T]) Pop() (T, error) { s.mu.Lock() defer s.mu.Unlock() var zero T if len(s.items) == 0 { return zero, ErrStackEmpty } n := len(s.items) - 1 item := s.items[n] s.items = s.items[:n] return item, nil } // Peek returns the top item without removing it. func (s *Stack[T]) Peek() (T, error) { s.mu.RLock() defer s.mu.RUnlock() var zero T if len(s.items) == 0 { return zero, ErrStackEmpty } return s.items[len(s.items)-1], nil } // Size returns the number of items in the stack. func (s *Stack[T]) Size() int { s.mu.RLock() defer s.mu.RUnlock() return len(s.items) } // IsEmpty reports whether the stack contains no items. func (s *Stack[T]) IsEmpty() bool { return s.Size() == 0 } func main() { var s Stacker[int] = NewStack[int]() s.Push(1) s.Push(2) s.Push(3) fmt.Println(\"Size:\", s.Size()) if top, err := s.Peek(); err == nil { fmt.Println(\"Top:\", top) } for !s.IsEmpty() { val, _ := s.Pop() fmt.Println(\"Popped:\", val) } } Code Review 1. Lines 13-19. Do we actually need a Stacker interface here? There's exactly one implementation and nothing accepts it as a parameter. Classic 'just in case' interface. 2. Lines 22-24. sync.RWMutex on a stack. Was concurrent access a requirement? Because nobody mentioned it in the ticket, and now every Push pays for a lock it doesn't need. 3. Lines 28-31. make([]T, 0) is equivalent to leaving it nil for append purposes. Not wrong, just ceremonial. 4. Lines 73-76. IsEmpty calls Size(), which acquires the RLock, then IsEmpty returns. Fine, but if a caller does IsEmpty() then Pop() they still race. The locking gives a false sense of safety. 5. Line 78. Assigning NewStack[int]() to a Stacker[int] variable in main just to prove the interface exists. We see what you did there. 6. Lines 9-10. The error message 'stack: operation on empty stack' is fine, but we only ever return it from two places that already have obvious context. A sentinel error works, just noting the prefix is doing nothing for us. 7. Lines 33-37. Every method has a comment that restates the method name. 'Push adds an item to the top of the stack.' Thank you, I was worried Push might subtract.",
"title": "A Stack Data Structure"
}