Start learning free

LESSON 170 OF 338 · STRINGS

Strings in Go: Bytes, Runes, Slicing and Concatenation

Lesson Read the full explanation

The Go string is a value that represents a sequence of characters, each identified by one index.

Creation:

To create strings in Go, you use double quotes like this:

greeting := "Hello"

Accessing characters:

To access the bytes in a string, you use the [] operator

The following example returns the first byte of a string with the index zero:

str := "Hello"
ch := str[0]
// ch is 'H'

Getting the length:

We can get the length of a string using the len() function.

For example, to access the last byte of the string, you use the len() - 1 index:

str := "Hello"
ch := str[len(str) - 1]
// ch is 'o'

Concatenating strings via + operator:

To concatenate two or more strings, you use the + operator:

name := "John"
str := "Hello " + name

fmt.Println(str) // prints "Hello John"

If you want to assemble a string piece by piece, you can use the += operator:

greet := "Welcome"
name := "John"
greet += " to AlgoCademy, "
greet += name

fmt.Println(greet) // prints "Welcome to AlgoCademy, John"

String slicing

We often want to get a substring of a string. For this, we use Go's slice notation.

str[startIndex:endIndex] returns the substring starting from startIndex up to (but not including) endIndex:

str := "JavaScript"
substr := str[2:6]

fmt.Println(substr) // prints "vaSc"

The startIndex is a zero-based index at which the slice starts extraction.

The endIndex specifies the position (exclusive) up to which the slice extracts.

If you omit the endIndex, the slice extracts to the end of the string:

str := "JavaScript"
substr := str[4:]

fmt.Println(substr) // prints "Script"

Iterating through the characters:

We can iterate through the characters of a string using a for loop and the range keyword like this:

myStr := "Andy"
for _, c := range myStr {
    fmt.Println(string(c))
}

// This will print the characters 'A', 'n', 'd' and 'y' on different lines

We can also iterate through the elements of a string using indices and a for loop like this:

myStr := "Andy"
for i := 0; i < len(myStr); i++ {
    fmt.Println(string(myStr[i]))
}

// This will print the characters 'A', 'n', 'd' and 'y' on different lines

Both ways take O(n) time, where n is the length of the string we iterate on.

Modifying characters with runes:

In Go, strings are immutable — you cannot change a character with str[i] = .... To edit individual characters, first convert the string into a []rune. A rune is Go's type for a single character (a Unicode code point), so []rune is a modifiable slice of the string's characters:

str := "cat"
chars := []rune(str)   // convert the string into a slice of runes (characters)
chars[0] = 'b'         // now individual characters can be changed
str = string(chars)    // convert the runes back into a string

fmt.Println(str) // prints "bat"

Assignment
Follow the Coding Tutorial and let's play with some strings.


Hint
Look at the examples above if you get stuck.


STEP 1 OF 7
    Ctrl + Enter to check

    GO · STRINGS

    Strings in Go, explained

    What a Go string is

    A Go string is an immutable sequence of bytes. Text is stored as UTF-8, so a character such as h takes one byte, while é takes two. You write string values in double quotes, and := declares the variable and infers its type:

    greeting := "Hello"
    

    Two facts shape everything else in this lesson. First, indexing and len work on bytes, not on characters. Second, a string can never be changed in place. Every operation that looks like an edit builds a new string.

    Length, indexing and the byte versus character question

    len(str) returns the number of bytes. str[i] returns the byte at index i, whose type is byte (an alias for uint8), so it is a number, not text:

    str := "Hello"
    fmt.Println(str[0], string(str[0]), len(str))
    last := str[len(str)-1]
    fmt.Println(string(last))
    
    72 H 5
    o
    

    Printing the byte directly shows 72, the code of H. Wrapping it in string(...) turns it back into text. The last element is at len(str)-1, because indices start at 0.

    For ASCII text, bytes and characters are the same thing. For anything else they differ:

    t := "héllo"
    fmt.Println(len(t), len([]rune(t)))
    
    6 5
    

    The string holds 6 bytes but 5 characters. A rune is Go's type for one character (a Unicode code point), and converting with []rune(t) gives one element per character.

    Concatenation

    The + operator joins two strings into a new one. The += operator builds the result piece by piece:

    name := "John"
    greet := "Welcome"
    greet += " to the course, "
    greet += name
    fmt.Println(greet)
    

    Each += creates a new string from the old one plus the right side and stores it back in the variable. That is cheap for a handful of pieces. In a loop that runs many times, the copying adds up, which is where strings.Builder helps.

    Slicing

    str[start:end] returns the part from start up to, but not including, end. If you omit end, the slice runs to the end of the string:

    lang := "Javascript"
    fmt.Println(lang[2:6])
    fmt.Println(lang[4:])
    
    vasc
    script
    

    Slice indices are byte positions too. Cutting through the middle of a multi-byte character produces broken text, so for non-ASCII strings convert to []rune first and slice that.

    Looping over a string

    There are two ways to visit every element, and they do different things on non-ASCII text:

    myStr := "Andy"
    for _, c := range myStr {
        fmt.Println(string(c))
    }
    for i := 0; i < len(myStr); i++ {
        fmt.Println(string(myStr[i]))
    }
    

    Both print A, n, d, y on separate lines and take O(n) time. The range loop decodes UTF-8 and yields runes, with the index of each rune's first byte. The indexed loop yields single bytes. For "héllo", range gives the indices 0, 1, 3, 4, 5, because é occupies bytes 1 and 2.

    Changing a character: convert to runes

    Because strings are immutable, str[i] = 'b' does not compile. The way around it is to copy the characters into a []rune, edit the slice, and convert back:

    str := "cat"
    chars := []rune(str)
    chars[0] = 'b'
    str = string(chars)
    fmt.Println(str)
    
    bat
    

    Single quotes make a rune ('b'), double quotes make a string ("b"). Mixing them up is a compile error.

    The exercise, step by step

    The exercise turns "Java" into "TypeScript", using each tool above once.

    1. Create lang holding "Java".
    2. Append "script" with +=, giving "Javascript".
    3. Take lang[4:], the part from index 4 on, which is "script", and store it in substr.
    4. Join "Type" and substr with + into newLang, which is "Typescript".
    5. Copy newLang into charArray with []rune(newLang), so it can be modified.
    6. Set charArray[4] to 'S'. Index 4 is the fifth character, the lowercase s.
    7. Convert back with string(charArray) and assign it to newLang.

    The complete solution:

    lang := "Java"
    lang += "script"
    substr := lang[4:]
    newLang := "Type" + substr
    charArray := []rune(newLang)
    charArray[4] = 'S'
    newLang = string(charArray)
    

    After the last line, newLang is "TypeScript". The variable is reused in step 7 with =, not :=, because it already exists.

    Common mistakes

    • Expecting str[0] to be a string. It is a byte, so printing it shows a number unless you convert it.
    • Assuming len counts characters. It counts bytes.
    • Assigning to str[i]. Strings are immutable; use a []rune.
    • Declaring a variable you never use. Go rejects unused variables at compile time.
    • Indexing past the end. str[len(str)] panics at run time.

    Key takeaways

    • A string is an immutable sequence of UTF-8 bytes; len and indexing work on bytes.
    • range over a string yields runes; []rune(str) gives an editable slice of characters.
    • + and += build new strings; str[a:b] slices by byte positions with the end excluded.
    • Single quotes are runes, double quotes are strings.
    Keep going with GoAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free