Strings and Conversions
Sooner or later every program has to turn numbers into text and back. Go is
strict about this: string(65) does not give you "65" (it gives you
"A", the character with code 65). Real conversions live in the strconv
package.
strconv: numbers to strings and back
package main
import (
"fmt"
"strconv"
)
func main() {
s := strconv.Itoa(42) // int → string: "42"
n, err := strconv.Atoi("42") // string → int: 42, nil
fmt.Println(s, n, err)
f, err := strconv.ParseFloat("3.14", 64) // string → float64
fmt.Println(f, err)
_, err = strconv.Atoi("not a number")
fmt.Println(err) // strconv.Atoi: parsing "not a number": invalid syntax
}
Itoa ("integer to ASCII") can't fail, so it returns just a string. The
parsing directions can fail — someone typed "forty-two" — so Atoi and
ParseFloat return (value, error), and you check the error like always.
ParseFloat's second argument is the precision to round to: use 64 for a
float64.
fmt.Sprintf: formatted strings
You already know fmt.Println. Its sibling fmt.Sprintf formats into a
string instead of printing:
label := fmt.Sprintf("%s is %d years old", "Ada", 36)
// "Ada is 36 years old"
Sprintf is the swiss-army knife — great for one-off labels and error
messages. For a plain int-to-string conversion, strconv.Itoa is the
sharper (and faster) tool.
The cost of + in a loop
Strings in Go are immutable. Every a + b allocates a brand-new string and
copies both halves into it. That's fine once; in a loop it's a trap:
s := ""
for i := 0; i < 10000; i++ {
s += "x" // copies the ENTIRE string built so far, every time
}
Each iteration copies everything accumulated so far, so building an n-character string this way costs about n²/2 copies — O(n²). At 10,000 iterations that's ~50 million character copies for a 10 KB result.
strings.Builder: accumulate, then take the string
The fix is strings.Builder, a growable buffer you write into. It amortizes
its allocations, so building the same string is O(n):
package main
import (
"fmt"
"strings"
)
func main() {
var b strings.Builder
for i := 0; i < 3; i++ {
b.WriteString("go")
}
fmt.Println(b.String()) // "gogogo"
}
The pattern is always the same three steps: declare var b strings.Builder
(the zero value is ready to use), call b.WriteString(...) as many times as
you like, and call b.String() once at the end.
One special case deserves a mention: joining a slice of strings with a
separator is so common that the library does it for you — strings.Join( []string{"a", "b", "c"}, ", ") returns "a, b, c", with no separator after
the last element. Under the hood it uses a Builder.
Putting it together
The exercise below combines both halves of this lesson: convert each int in
a slice with strconv.Itoa, and accumulate the result efficiently — either
with a Builder and some care about where the separators go, or by building
a []string and handing it to strings.Join. Both are idiomatic; pick one.
Practice
1. Join Ints
IntermediateImplement
joinInts, which renders a slice of ints as a single string with a separator between elements:func joinInts(nums []int, sep string) stringjoinInts([]int{1, 2, 3}, ", ")returns"1, 2, 3".- The separator goes between elements only — never before the first or after the last.
- A single element means no separators at all:
joinInts([]int{7}, "-")is"7". - An empty (or nil) slice returns
"". - An empty separator just concatenates:
joinInts([]int{1, 2, 3}, "")is"123".
Use
strconv.Itoafor each conversion, and accumulate withstrings.Builder(write the separator before every element except the first) — or convert everything into a[]stringand letstrings.Joinhandle the separators.package main import "fmt" // joinInts renders nums as a single string with sep between elements. // An empty slice returns "". func joinInts(nums []int, sep string) string { // TODO: implement return "" } func main() { fmt.Println(joinInts([]int{1, 2, 3}, ", ")) }Sign in to write and run your solution in the browser.
Sign in for hints and the solution.