Prefix removal often carries two pieces of information: the remaining text and whether the expected prefix was present. strings.CutPrefix represents both results in one operation instead of separating a prefix test from the removal that follows it.
That distinction matters when an unchanged string is a valid result. strings.TrimPrefix returns the input unchanged when the prefix is absent, so its return value alone cannot report presence. strings.CutPrefix returns the remainder plus a boolean that preserves that fact explicitly.
Prefix presence is part of the result
The function has a compact contract:
after, found := strings.CutPrefix(s, prefix)When s starts with prefix, after contains the bytes following that prefix and found is true. When it does not, after is the original string and found is false.
For example, a parser that accepts a fixed scheme can keep validation and extraction together:
func bearerToken(header string) (string, error) {
token, ok := strings.CutPrefix(header, "Bearer ")
if !ok {
return "", errMissingScheme
}
if token == "" {
return "", errMissingToken
}
return token, nil
}The boolean distinguishes a missing scheme from a present scheme followed by an empty value. That distinction would be lost if code inspected only the returned string from TrimPrefix.
It replaces a paired test and trim
A common form uses HasPrefix before TrimPrefix:
if strings.HasPrefix(value, prefix) {
rest := strings.TrimPrefix(value, prefix)
use(rest)
}The same condition can be represented directly:
if rest, ok := strings.CutPrefix(value, prefix); ok {
use(rest)
}The second form ties the test to the exact transformation whose result is consumed. There is no separate predicate that must stay aligned with a later removal call.
This is more than shorter syntax when the prefix is an expression. Evaluating one operation also avoids accidentally testing one prefix and trimming another after surrounding code changes.
Empty prefixes have defined behavior
An empty prefix is considered present. Calling:
rest, ok := strings.CutPrefix("alpha", "")produces "alpha" and true. This follows the normal prefix relation: every string starts with the empty string.
Code that accepts a configurable prefix should account for this if an empty configuration value is meant to be invalid. The string function reports string structure; configuration policy remains the caller’s responsibility.
The operation is byte-oriented
Go strings contain bytes, and CutPrefix matches the supplied prefix exactly. It does not perform Unicode normalization or case folding. Two visually similar strings can therefore fail to match if their byte sequences differ.
Likewise, protocol tokens with case-insensitive rules need separate handling. Converting arbitrary input to a different case before cutting can also change semantics for domains with specific comparison rules. The appropriate comparison rule should come from the format or protocol being parsed.
It removes one exact leading prefix
CutPrefix removes at most one occurrence, and only at the start of the string. Given:
rest, ok := strings.CutPrefix("////api", "//")rest is "//api" and ok is true. It does not keep stripping repeated prefixes.
That behavior differs from functions that trim a set of characters. strings.TrimLeft, for example, treats its second argument as a cutset and can remove multiple leading characters. For structured text where "//" is one meaningful token, CutPrefix expresses the boundary directly.
Returned substrings retain string semantics
The returned value is a string slice derived from the input. Callers should treat it like other substring operations in Go: it is immutable, cheap to pass around, and suitable for immediate parsing or comparison.
If a small remainder must outlive a very large source string for a long period, strings.Clone can be considered when retaining the source storage is undesirable. That is a storage-lifetime decision rather than a requirement of CutPrefix itself.
strings.CutPrefix fits code where prefix presence and prefix removal form one logical operation. Its boolean result keeps parsing state explicit, while its exact-match behavior makes the boundary visible without combining separate checks and transformations.