Avoid These Common Go String Mistakes That Hurt Performance

This article examines six frequent Go string pitfalls—including misuse of rune, incorrect iteration, inefficient concatenation, unnecessary conversions, substring memory leaks, and improper trim functions—explaining their impact, demonstrating flawed code, and providing optimized examples to improve correctness and performance.

FunTester
FunTester
FunTester
Avoid These Common Go String Mistakes That Hurt Performance

Mistake 36: Not Understanding rune (#36)

The rune type represents a Unicode code point, which is a multi‑byte sequence, not a single byte. Developers often treat rune and byte as interchangeable, leading to errors when handling multi‑byte characters.

Possible impact: Using len(s) on a string returns the number of bytes, not characters, which can cause UI display issues or data truncation.

Best practice: Understand the difference between rune and byte. For character‑level operations, convert the string to a []rune slice.

Improved code:

package main

import "fmt"

func main() {
    s := "FunTester 演示"
    runes := []rune(s)
    fmt.Printf("FunTester: string length = %d characters
", len(runes))
    for i, char := range runes {
        fmt.Printf("FunTester: character %d is %c
", i, char)
    }
}

Output:

FunTester: string length = 12 characters
FunTester: character 0 is F
FunTester: character 1 is u
FunTester: character 2 is n
FunTester: character 3 is T
FunTester: character 4 is e
FunTester: character 5 is s
FunTester: character 6 is t
FunTester: character 7 is e
FunTester: character 8 is r
FunTester: character 9 is 演
FunTester: character 10 is 示

Mistake 37: Incorrect String Traversal (#37)

Using a traditional for i := 0; i < len(s); i++ loop with index access treats the string as a byte slice, ignoring that rune values may occupy multiple bytes. This results in garbled output or runtime errors for multi‑byte characters.

Possible impact: Wrong characters are printed, especially for non‑ASCII text, potentially causing crashes.

Best practice: Iterate with for i, char := range s, which automatically iterates over runes.

Improved code:

package main

import "fmt"

func main() {
    s := "FunTester 演示"
    for i, char := range s {
        fmt.Printf("FunTester: character %d is %c
", i, char)
    }
}

Output:

FunTester: character 0 is F
FunTester: character 1 is u
FunTester: character 2 is n
FunTester: character 3 is T
FunTester: character 4 is e
FunTester: character 5 is s
FunTester: character 6 is t
FunTester: character 7 is e
FunTester: character 8 is r
FunTester: character 9 is 演
FunTester: character 10 is 示

Mistake 38: Misusing trim functions (#38)

The strings.TrimLeft and strings.TrimRight functions remove any characters found in the supplied cutset from the start or end of the string, not a specific prefix or suffix. Using them to delete a concrete substring often yields unexpected results.

Possible impact: Unintended characters are stripped, corrupting the string content.

Best practice: Use strings.TrimPrefix and strings.TrimSuffix when you need to remove a known prefix or suffix.

Improved code:

package main

import (
    "fmt"
    "strings"
)

func main() {
    s := "***FunTester***"
    // Remove the leading "***"
    trimmedPrefix := strings.TrimPrefix(s, "***")
    fmt.Println("FunTester: Trimmed with TrimPrefix =", trimmedPrefix)
    // Remove the trailing "***"
    trimmedSuffix := strings.TrimSuffix(s, "***")
    fmt.Println("FunTester: Trimmed with TrimSuffix =", trimmedSuffix)
    // If you really need to strip any '*' characters, use TrimLeft/TrimRight
    trimmedAny := strings.TrimLeft(s, "*")
    trimmedAny = strings.TrimRight(trimmedAny, "*")
    fmt.Println("FunTester: Trimmed any '*' characters =", trimmedAny)
}

Output:

FunTester: Trimmed with TrimPrefix = FunTester***
FunTester: Trimmed with TrimSuffix = ***FunTester
FunTester: Trimmed any '*' characters = FunTester

Mistake 39: Unoptimized string concatenation (#39)

In Go, strings are immutable. Using the + operator inside a loop creates a new string each iteration, causing repeated memory allocations and copying, which degrades performance.

Possible impact: High CPU and memory usage, especially when concatenating large numbers of strings.

Best practice: Use strings.Builder or bytes.Buffer for efficient concatenation.

Improved code:

package main

import (
    "fmt"
    "strings"
)

func main() {
    parts := []string{"Fun", "Tester", "是", "测试", "工具"}
    var builder strings.Builder
    for _, part := range parts {
        builder.WriteString(part)
    }
    fullString := builder.String()
    fmt.Println("FunTester: concatenated string =", fullString)
}

Output:

FunTester: concatenated string = FunTester是测试工具

Mistake 40: Useless string conversion (#40)

Converting a string to []byte and back to string without a real need adds redundant code and incurs unnecessary performance overhead.

Possible impact: Longer, less readable code and potential slowdown when processing large data volumes.

Best practice: Operate directly on the appropriate type using functions from the strings or bytes packages, and only convert when truly required.

Improved code:

package main

import (
    "fmt"
    "strings"
)

func main() {
    s := "FunTester 演示"
    // Directly use strings package without conversion
    upper := strings.ToUpper(s)
    fmt.Println("FunTester: uppercase string =", upper)
    // If byte manipulation is needed, convert explicitly
    b := []byte(s)
    b = append(b, '!') // example modification
    s2 := string(b)
    fmt.Println("FunTester: modified string =", s2)
}

Output:

FunTester: uppercase string = FUNTESTER 演示
FunTester: modified string = FunTester 演示!

Mistake 41: Substring and memory leak (#41)

When a substring is obtained with s[low:high], the new string still references the original string's underlying array. If the original string is large and only a small part is needed, the whole array remains in memory, causing a leak.

Possible impact: Continuous memory growth in long‑running programs, eventually exhausting system resources.

Best practice: Copy the needed portion into a new slice (e.g., []rune or []byte) and convert back to a string, breaking the reference to the original array.

Improved code:

package main

import "fmt"

func main() {
    s := "FunTester 演示内存泄漏"
    subs := s[0:8] // get a substring
    // Copy the substring to a new string
    runes := []rune(subs)
    subsCopy := string(runes)
    fmt.Println("FunTester: substring =", subsCopy)
}

Output:

FunTester: substring = FunTester
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendperformanceGobest practicesStringsrune
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.