strings.CutSuffix removes one exact trailing string and reports whether that suffix was present. The boolean result is the key distinction from operations that only return transformed text: callers can keep suffix recognition separate from the remaining content.
base, found := strings.CutSuffix(name, ".json")If name ends in .json, base contains the preceding text and found is true. Otherwise, base is the original string and found is false. The operation does not scan for a matching fragment in the middle and does not repeatedly strip the suffix.
Presence is part of the result
A parser often needs to distinguish a recognized ending from text that merely happens to remain unchanged after an operation. Consider a dispatcher that accepts only names ending in .json:
package main
import (
"fmt"
"strings"
)
func configName(path string) (string, bool) {
return strings.CutSuffix(path, ".json")
}
func main() {
for _, path := range []string{"service.json", "service.yaml", ".json"} {
name, ok := configName(path)
fmt.Printf("%q %q %v\n", path, name, ok)
}
}The three inputs produce distinct states. service.json yields service with a successful match. service.yaml stays unchanged and reports no match. .json yields an empty base while still reporting a match.
That last case is significant. Testing only the returned string cannot tell whether an empty result came from a valid suffix match. The boolean carries that information directly.
CutSuffix removes one exact ending
The suffix is matched as a string, not as a set of characters. Removing .gz from archive.tar.gz returns archive.tar; it does not remove any additional extension.
base, ok := strings.CutSuffix("archive.tar.gz", ".gz")
fmt.Println(base, ok) // archive.tar trueA second call can express a second removal when the format permits nested endings:
base, ok := strings.CutSuffix("archive.tar.gz", ".gz")
if ok {
base, _ = strings.CutSuffix(base, ".tar")
}
fmt.Println(base) // archiveKeeping those operations explicit avoids treating suffix removal as general trimming. Functions that operate on character sets have different semantics and can remove more trailing data than a file-format parser intends.
Empty suffixes always match
An empty suffix is a valid suffix for every string. strings.CutSuffix(s, "") returns s, true.
base, ok := strings.CutSuffix("report", "")
fmt.Println(base, ok) // report trueThis can matter when the suffix is supplied by configuration or another caller. If an empty suffix has no useful meaning in the surrounding protocol, validate that condition before calling CutSuffix. The standard-library function treats the empty suffix according to string-prefix and string-suffix semantics rather than imposing application policy.
CutSuffix and TrimSuffix carry different signals
strings.TrimSuffix also removes one exact suffix, but it returns only the resulting string. That is sufficient when the caller does not care whether removal occurred.
name := strings.TrimSuffix(input, ".json")strings.CutSuffix fits code that branches on recognition:
name, ok := strings.CutSuffix(input, ".json")
if !ok {
return fmt.Errorf("unsupported config name %q", input)
}The difference is not the text-removal rule. Both target one exact ending. The difference is whether suffix presence remains available as an explicit value instead of requiring a separate check.
A common older shape performs that check and removal independently:
if strings.HasSuffix(input, ".json") {
name := strings.TrimSuffix(input, ".json")
use(name)
}The same condition and transformation can be represented by one operation:
if name, ok := strings.CutSuffix(input, ".json"); ok {
use(name)
}This keeps the tested suffix and the removed suffix in the same expression, which also prevents those two values from drifting apart during later edits.
Matching is byte-exact
CutSuffix follows Go string semantics. It does not perform case folding, Unicode normalization, path cleaning, or extension parsing. A suffix of .JSON does not match config.json, and canonically equivalent Unicode text with different byte sequences is not made equivalent by the function.
Those transformations belong outside the suffix operation when a protocol calls for them. For example, case-insensitive file conventions require an explicit case policy rather than an assumption that suffix matching supplies one.
The same boundary applies to filesystem paths. CutSuffix can remove a textual extension, but it does not identify path components or validate filenames. Code dealing with path structure should keep path parsing and suffix recognition as separate concerns.
A small operation with a precise contract
strings.CutSuffix is most useful when an exact ending acts as a marker: a file extension, protocol token, generated-name ending, or other suffix whose presence changes interpretation. It returns the remaining text without hiding whether the marker existed.
That explicit presence bit is the main reason to choose it over a transformation-only call. When suffix recognition is part of the surrounding state machine, preserving that signal keeps the branch tied directly to the string operation that produced it.