How to Use `strings.CutPrefix` and `strings.CutSuffix` for Efficient String Manipulation in Go

strings.CutPrefix and strings.CutSuffix combine a prefix or suffix check with trimming into a single atomic operation, returning the remaining string and a boolean indicating whether the cut occurred.

Introduced in Go 1.20, these functions eliminate the redundancy of calling HasPrefix followed by TrimPrefix (or their suffix equivalents) when you need both the match status and the trimmed result. The JetBrains/go-modern-guidelines repository explicitly recommends these helpers to improve code clarity and avoid subtle bugs caused by mismatched check-and-trim operations.

Function Signatures and Return Values

Both functions follow a consistent pattern, returning two values that allow immediate conditional handling:

func CutPrefix(s, prefix string) (rest string, ok bool)
func CutSuffix(s, suffix string) (rest string, ok bool)
  • ok: Set to true only when s starts with prefix (or ends with suffix).
  • rest: Contains the substring with the matching prefix or suffix removed. If ok is false, rest equals the original string s.

This design pattern enables concise error handling and parsing logic without requiring separate validation calls.

Why Prefer CutPrefix Over Legacy Patterns

The traditional approach requires two separate operations against the string:

// Legacy pattern: two scans of the string
if strings.HasPrefix(s, "pre:") {
    result = strings.TrimPrefix(s, "pre:")
    // handle result
}

strings.CutPrefix performs the match and trim in a single pass:

// Modern pattern: single scan with clear intent
if result, ok := strings.CutPrefix(s, "pre:"); ok {
    // handle result
}

This consolidation provides three concrete benefits:

  • Eliminates redundant work: The underlying implementation scans the string only once rather than twice.
  • Prevents logic errors: You cannot accidentally trim a string without verifying the prefix exists, a common bug when HasPrefix and TrimPrefix drift apart during refactoring.
  • Improves readability: The code explicitly states "remove the prefix if present, and tell me whether it matched."

Guidelines from JetBrains/go-modern-guidelines

The JetBrains/go-modern-guidelines repository codifies this best practice in internal/guidelines/guidelines.json at lines 1228-1230, explicitly stating: "Use strings.CutPrefix or strings.CutSuffix when you need both the trimmed result and whether it matched."

The guideline is enforced by tests in internal/guidelines/guidelines_test.go at line 41, which flags violations of the strings_bytes_cut_last rule. This ensures that contributions to the repository follow the modern pattern rather than legacy check-then-trim sequences.

Real-World Usage in Version Parsing

The repository demonstrates practical application in internal/goversion/goversion.go (lines 197-201), where the version parser extracts major and minor components. While the file utilizes strings.Cut (another Go 1.20 helper) for initial splitting, the logic structure demonstrates the same philosophy applied throughout: perform one operation to both test and extract content, rather than testing first and extracting second.

This pattern appears when processing semantic version strings where determining the presence of a prefix (like a build marker) and obtaining the remainder must happen atomically to avoid race conditions or double-processing in tight loops.

Practical Implementation Examples

Conditional Header Parsing

Extract error messages from tagged log lines without redundant checks:

lines := []string{
    "error: failed to start",
    "info: all good",
    "error: out of memory",
}

for _, line := range lines {
    if msg, ok := strings.CutPrefix(line, "error: "); ok {
        fmt.Printf("Critical: %s\n", strings.TrimSpace(msg))
    }
}

Safe File Extension Handling

Remove known extensions while confirming the file type:

filename := "report.pdf"
if name, ok := strings.CutSuffix(filename, ".pdf"); ok {
    fmt.Printf("Processing PDF: %s\n", name)
} else {
    fmt.Println("Unsupported file format")
}

Protocol Stripping

Clean URLs by removing schemes only when present:

url := "https://example.com"
if host, ok := strings.CutPrefix(url, "https://"); ok {
    // host now contains "example.com", guaranteed to have had the prefix
    handleSecureHost(host)
}

Summary

  • strings.CutPrefix and strings.CutSuffix were added in Go 1.20 to combine matching and trimming into a single operation.
  • Both functions return (rest string, ok bool), allowing immediate conditional logic without separate HasPrefix/HasSuffix calls.
  • Using these functions eliminates redundant string scans and prevents bugs where validation and trimming logic become decoupled.
  • The JetBrains/go-modern-guidelines repository enforces this pattern in internal/guidelines/guidelines.json and validates it through internal/guidelines/guidelines_test.go.
  • Real-world usage in internal/goversion/goversion.go demonstrates the performance and clarity benefits in production parsing code.

Frequently Asked Questions

When should I use strings.CutPrefix instead of strings.TrimPrefix?

Use strings.CutPrefix when your logic requires knowing whether the prefix existed before processing the remainder. If you only need to remove a prefix unconditionally (and do not care if it was present), strings.TrimPrefix remains appropriate. The JetBrains guidelines specifically recommend CutPrefix when you need both the trimmed result and the match status.

What Go version introduced CutPrefix and CutSuffix?

These functions were introduced in Go 1.20. Codebases targeting earlier versions must continue using the strings.HasPrefix and strings.TrimPrefix combination or implement custom single-pass logic.

Are these functions more performant than separate HasPrefix and TrimPrefix calls?

Yes. According to the implementation details referenced in the JetBrains guidelines, CutPrefix and CutSuffix perform the match only once, whereas the legacy pattern requires scanning the string twice—once for the check and once for the trim. While the performance gain may be negligible for small strings, it becomes measurable when processing high-volume text or large datasets in hot paths.

How does the JetBrains repository enforce these string manipulation guidelines?

The repository enforces the pattern through a combination of documentation in internal/guidelines/guidelines.json (lines 1228-1230) and automated testing in internal/guidelines/guidelines_test.go (line 41). The test suite includes the strings_bytes_cut_last rule, which flags code that uses separate HasPrefix/TrimPrefix calls instead of the modern Cut alternatives.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →