How many times have you looked at a piece of code and thought, “This should be faster”?
Recently, I went on a journey of optimizing a process that was taking 3 seconds and managed to bring it down to 0.0002 seconds. That’s a 150,000x improvement. But the real story isn’t just the final number—it’s what I broke along the way to get there.
The Challenge: Last-In, First-Out (LIFO)
The goal was simple: Reverse a string of 34,000 characters. To make it interesting, I benchmarked each approach by running the operation 1,000 times.
Phase 1: The Pythonic Way
Initially, I was doing what most of us do: focusing on readability and standard library functions. It worked, but at scale, it crawled.
class Stack: def init(self): self.items = []
def is_empty(self):
#return len(self.items) == 0
return not self.items
def push(self, item):
self.items.append(item)
def pop(self):
return self.items.pop()
def peek(self):
return self.items[-1]
def size(self):
return len(self.items)
def __str__(self):
return str(self.items)
The Lesson:
I quickly discovered the “String Concatenation Trap.” Using += to build the result string in Python is O(n2) because strings are immutable; every addition creates a copy. By switching to a list-based accumulator and “".join(), I brought the time down significantly.
- Final Python Time: ~3.16 seconds.
def reverse_string_slow(my_string): reversed_string = "”
# Create a new stack
s = Stack()
# Iterate through my_string and push the characters onto the stack
for char in my_string:
s.push(char)
# Use a while loop with the exit condition that the stack is empty.
# Within this loop, update reversed_string with characters popped off the stack.
while not s.is_empty():
reversed_string += s.pop()
#return reversed_string
The Faster approach def reverse_string_fast(my_string): reversed_string = ""
# Create a new stack
s = Stack()
# Iterate through my_string and push the characters onto the stack
for char in my_string:
s.push(char)
# Use a while loop with the exit condition that the stack is empty.
# Within this loop, update reversed_string with characters popped off the stack.
accumulator = []
while not s.is_empty():
accumulator.append(s.pop())
return "".join(accumulator)
Phase 2: The Go Evolution (The Crash)
Moving to Go, I wanted to see how a compiled language handled the same Stack logic. I built a struct with Push and Pop methods.
The Lesson: I learned about “The Ghost Pop.” In my first attempt, I returned the top item but forgot to re-slice the underlying array.
Result: An infinite loop that allocated memory so fast it crashed my machine!
The Fix: s.items = s.items[:lastIdx] — strictly managing the slice length is the key to Go’s efficiency.
func (s *Stack) Pop() rune { if len(s.items) == 0 { return 0 } lastIdx := len(s.items) - 1 item := s.items[lastIdx] s.items = s.items[:lastIdx] return item }
The Pointer Swap version i followed to check the stats instead of Stack.
func (s *Stack) reverse() string { runes := []rune(s.str) n := len(runes) j := n - 1 for i := 0; i < n/2; i++ { runes[i], runes[j-i] = runes[j-i], runes[i] } return string(runes) }
Phase 3: The Ultimate Optimization (The Swap)
Finally, I moved away from the Stack entirely. Instead of moving data into a container, I used a Two-Pointer Swap directly on a slice of runes. This mirrored the indices from the outside-in.
Key Insights
- The "Allocation" Hidden Cost: While the Go Stack and the Go Swap were similar in speed, the Stack used 4.6x more memory and required 21x more interactions with the OS (allocations). In a high-traffic production environment, the Swap version is the clear winner for stability.
- Stateless vs. Stateful: The "Swap" method is stateless and O(1) extra space, making it a "Senior" approach to a "Junior" problem.
- The Performance Gap: Go finished in nanoseconds what Python finished in seconds. For data-heavy tasks, the difference is night and day.
Final Thoughts
Whether you are building a Neural Network or a simple string utility, understanding how your language manages memory is the difference between code that “works” and code that “scales.”
Don’t just write code—measure it.
#SoftwareEngineering #Golang #Python #Performance #Programming #DataStructures #ComputerScience
Originally published on LinkedIn
