GoGo example

How to trim whitespace from a string in Go

6 min read▶ Runs in an isolated hosted runtimeUpdated Aug 2026

Quick answer

Use strings.TrimSpace(s). It removes every leading and trailing whitespace rune — spaces, tabs, newlines, and the Unicode ones like U+00A0 — and returns a new string; Go strings are immutable, so s itself never changes. For anything other than whitespace, reach for strings.Trim(s, cutset).

Trimming is the one string operation almost every Go program does, usually on a line read from a file, a form field, or os.Args. The strings package gives you eight closely related functions to do it, and picking the wrong one is a genuinely common bug: TrimLeft takes a set of characters while TrimPrefix takes an exact substring, and they quietly disagree on real input. Below, every snippet prints with %q so you can see exactly which invisible characters survived. Hit Run, then edit the code and run it again.

1strings.TrimSpaceRecommended

strings.TrimSpace(s) strips whitespace off both ends and leaves the middle alone. It takes no second argument, which is the whole point — there is nothing to get wrong. This is the right answer for cleaning up a scanned line, a CSV cell, or a value from os.Getenv, and it is the function you want roughly nine times out of ten.

trimspace.go

Output

The %q verb quotes the string and escapes the invisibles, so line 1 echoes the input exactly as written — a leading \t, two spaces, the text, then a space and a \n — while line 2 is the trimmed "hello, world". Line 3 keeps every one of the three-space gaps between the words: TrimSpace works inward from each end and stops at the first non-space rune, so the middle is never reached. Line 4 reprints the original, unchanged, because TrimSpace returns a new string rather than editing one — and when there is nothing to remove it hands back the input as-is (a reslice of the same bytes, no copy), so calling it defensively is cheap.

2What counts as whitespace (and what doesn't)

TrimSpace delegates to unicode.IsSpace, which is Unicode's White_Space property — so \t \n \v \f \r and space are in, and so are U+0085, the non-breaking space U+00A0, and the ideographic space U+3000. What is not in: the zero-width space U+200B and the BOM U+FEFF. Those two are the reason a "trimmed" string pasted from a web page still fails an equality check.

whitespace.go

Output

The non-breaking space is trimmed — "\u00a0go\u00a0" -> "go" — but the zero-width space is not: "\u200bgo\u200b" comes back identical, and unicode.IsSpace confirms it with true false. strings.TrimFunc takes any func(rune) bool, so widening the definition to include U+200B and U+FEFF gets you a clean "go". The last two lines show TrimRightFunc doing one end only: with unicode.IsSpace you get "id-42-", and with the dash added, "id-42".

3TrimLeft/TrimRight vs TrimPrefix/TrimSuffix

This pair looks interchangeable and is not. TrimLeft(s, cutset) reads the second argument as an unordered set of runes and keeps eating characters off the front for as long as each one is a member. TrimPrefix(s, prefix) reads it as a literal substring and removes it exactly once. Reach for TrimLeft when you mean "strip these padding characters", and for TrimPrefix when you mean "strip this exact text".

cutset.go

Output

Here is the bug in one line: TrimLeft(url, "https://") yields "orts.example.com/" — the cutset is {h,t,p,s,:,/}, so it chews straight through the scheme and then keeps going into s and p of "sports". TrimPrefix gives the intended "sports.example.com/", and it returns the string untouched when the prefix doesn't match, which makes it safe to call unconditionally. Note also TrimRight("value;;;", ";") and TrimRight("value;;;", ";;;") both print "value": duplicates in a cutset are meaningless.

4strings.Trim with a custom cutset

strings.Trim(s, cutset) is TrimSpace with the whitespace rule replaced by your own character set. It shines on messy real-world data, where a field can be padded with spaces and wrapped in quotes and you don't know which order they arrive in — a set handles both without a loop. It always stops at the first rune that isn't a member, so anything in the middle is safe.

trim.go

Output

The scraped field becomes "Ada Lovelace" in one call. Then "go", "api/v1/users", and — the important one — "a--b", where the inner dashes survive because Trim only works inward from the ends. The last line strips digits and dashes off the right to leave "report". A cutset is interpreted as UTF-8 runes, not bytes, so multi-byte characters work fine in it.

5Which should you use?

MethodSecond argumentWhat it removesBest for
strings.TrimSpace(s)NoneWhitespace, both endsAlmost everything
strings.Trim(s, cutset)A set of runesCutset members, both endsQuotes, slashes, custom padding
strings.TrimLeft / TrimRightA set of runesCutset members, one endLeading zeros, trailing punctuation
strings.TrimPrefix / TrimSuffixAn exact substringThat substring, onceStripping "https://" or ".go"
strings.TrimFunc (+ Left/Right)A func(rune) boolWhatever you sayZero-width chars, custom classes
strings.Join(strings.Fields(s), " ")NoneEnds and inner runsNormalising messy whitespace

6Removing or collapsing inner whitespace

No Trim* function touches the middle of a string, so "remove the double spaces" is a different job. The idiom is strings.Join(strings.Fields(s), " "): Fields splits on any run of whitespace and drops the empties, so re-joining with a single space normalises the ends and the middle in one pass. Join with "" instead and every space disappears.

inner.go

Output

You get "Go makes concurrency easy", then "Gomakesconcurrencyeasy". The third line is the warning: strings.ReplaceAll(s, " ", "") only matches the literal ASCII space, so it leaves "\tGomakes\nconcurrency\t\teasy" — tabs and newlines fully intact. strings.Map with a -1 return is the general escape hatch and matches Fields. For a similar splitting trick, see reversing the words in a sentence.

Frequently asked questions

What is the difference between strings.Trim and strings.TrimSpace in Go?

strings.TrimSpace(s) takes one argument and removes whitespace from both ends, using unicode.IsSpace to decide what whitespace is. strings.Trim(s, cutset) takes a second argument — a set of runes — and removes those from both ends instead. TrimSpace(s) is effectively TrimFunc(s, unicode.IsSpace); use it unless you need to strip something that is not whitespace.

Why does strings.TrimLeft(url, "https://") remove the wrong characters?

Because the second argument to TrimLeft is a cutset, not a prefix. Go treats "https://" as the set {h, t, p, s, :, /} and keeps removing leading runes while each one is in that set, so strings.TrimLeft("https://sports.example.com/", "https://") returns "orts.example.com/". Use strings.TrimPrefix(url, "https://") when you mean an exact prefix — it removes the substring once and returns the string unchanged if it does not match.

Does strings.TrimSpace remove non-breaking spaces and zero-width spaces?

It removes non-breaking spaces but not zero-width ones. TrimSpace uses unicode.IsSpace, which follows the Unicode White_Space property: U+00A0 (no-break space), U+0085, and U+3000 (ideographic space) are all included, so they get trimmed. U+200B (zero-width space) and U+FEFF (byte-order mark) are not white space, so they survive. Use strings.TrimFunc(s, f) with your own predicate to strip those too.

How do I remove all whitespace from a Go string, including in the middle?

Use strings.Join(strings.Fields(s), ""), which splits on every run of whitespace and rejoins with nothing, or strings.Map returning -1 for any rune where unicode.IsSpace is true. Do not use strings.ReplaceAll(s, " ", "") — it matches only the literal ASCII space and leaves tabs and newlines behind.

Does trimming a string in Go modify the original?

No. Go strings are immutable, so every function in the Trim family returns a new string and leaves its input alone — you have to assign the result, as in s = strings.TrimSpace(s). Under the hood TrimSpace and Trim reslice the existing bytes rather than copying them, so trimming is cheap, but the returned value still keeps the original backing array alive.

How do I strip just the trailing newline from a line of input?

Use strings.TrimRight(line, "\r\n"), whose cutset of {\r, \n} handles both Unix and Windows endings in one call. strings.TrimSuffix(line, "\n") removes exactly one "\n" and would leave the carriage return behind on CRLF input — strings.TrimSuffix("line\r\n", "\n") returns "line\r". If you also want surrounding spaces gone, strings.TrimSpace covers all of it.