strings.Cut separates a string around the first occurrence of a delimiter and reports whether that delimiter was present. That three-result contract matters in parsers where a missing separator is different from a separator followed by an empty value.

before, after, found := strings.Cut(input, "=")

When the separator exists, before contains the text preceding its first occurrence and after contains the remainder. When it does not exist, the function returns the original string, an empty second string, and false.

Presence stays separate from content

Consider configuration fragments that accept a name and an optional textual value:

package main

import (
    "fmt"
    "strings"
)

func main() {
    for _, s := range []string{"mode=fast", "mode=", "mode"} {
        key, value, found := strings.Cut(s, "=")
        fmt.Printf("key=%q value=%q found=%v\n", key, value, found)
    }
}

The final two inputs produce different states. mode= contains the delimiter and has an empty value. mode contains no delimiter. Code that only checks whether the returned value is empty collapses those states and loses information supplied by the input.

This distinction is useful for headers, environment-style assignments, compact protocol fields, and other formats where delimiter presence carries meaning of its own.

Only the first separator is structural

Cut stops at the first matching separator. The remainder is returned intact:

key, value, found := strings.Cut("dsn=host=db.internal", "=")
// key == "dsn"
// value == "host=db.internal"
// found == true

That behavior fits formats in which the first delimiter divides a field name from an unrestricted payload. A full split can create more pieces than the parser needs and can require later reconstruction of the tail.

The separator can contain multiple bytes as well:

left, right, found := strings.Cut("alpha::beta::gamma", "::")
// left == "alpha"
// right == "beta::gamma"
// found == true

Matching is byte-oriented, consistent with the rest of the strings package. No Unicode normalization or case folding is performed.

Empty separators have defined semantics

An empty separator is considered present at the start of every string. As a result, the left side is empty and the right side is the original input:

left, right, found := strings.Cut("alpha", "")
// left == ""
// right == "alpha"
// found == true

For parsers with a configurable delimiter, validating that delimiter before calling Cut can be appropriate when an empty delimiter has no valid domain meaning. The standard-library behavior itself is deterministic; the application still owns the input contract.

Cut and SplitN express different result shapes

strings.SplitN(s, sep, 2) can also divide around at most one occurrence. Its result is a slice, so delimiter absence is represented by a one-element slice and delimiter presence by a two-element slice. Cut exposes the same parsing boundary through fixed return values and a boolean.

For code that needs exactly a prefix, suffix, and presence bit, the fixed shape tends to map directly onto the branch that follows:

name, value, ok := strings.Cut(field, ":")
if !ok {
    return fmt.Errorf("missing field separator")
}

name = strings.TrimSpace(name)
value = strings.TrimSpace(value)

Cut does not trim either side, validate either field, decode escaping, or reject repeated separators. Those concerns remain separate parsing decisions.

Returned strings are slices of the input

The operation returns substrings rather than constructing a token list. That keeps the API focused on locating one boundary. As with other substring operations in Go, retaining a small returned portion can also retain the backing data associated with a larger source string. This is relevant when a tiny parsed field is stored for a long period after processing a very large input.

strings.Clone is available when an application deliberately needs a copy with independent backing storage:

name, _, ok := strings.Cut(largeInput, "=")
if ok {
    name = strings.Clone(name)
}

Copying every result preemptively is usually unnecessary. The retention pattern and lifetime of the source determine whether a copy serves a concrete purpose.

Parsing remains responsible for format rules

strings.Cut answers one narrow question: where the first exact separator occurs. It does not establish that the surrounding text is valid for a particular format. A parser may still need length limits, character constraints, duplicate-field handling, escaping rules, or normalization after the cut.

That narrow contract is also its main advantage. When a format has one structural boundary and delimiter absence must remain observable, strings.Cut represents those semantics directly without turning the rest of the input into extra tokens.