Start learning free

LESSON 57 OF 415 · STRINGS I

Accessing Characters in Swift: String Indexes

Lesson Read the full explanation

Each character in a string has a numbered position known as its index. We access a character by referring to its index number.

Strings use zero-based indexing, so the first character in a string has an index of 0, the second character has an index of 1, etc.

We're using the chars[index] subscript with the index to access the character:

let message = "Hello world!"
let chars = Array(message)

var c = chars[0]
print(c) // Output: H

c = chars[1]
print(c) // Output: e

print(chars[6]) // Output: w

Whitespaces have indices:

Note that every character inside the quotes (including whitespaces) is part of the string and has its own index. For example, in string "Hello world!", the whitespace is placed on index 5.

You can see that clearly when you run this code:

let message = "Hello world!"
let chars = Array(message)

print("\(chars[4])\(chars[5])\(chars[6])")

the output is:

o w

Assignment
Follow the Coding Tutorial and let's access some characters!


Hint
Look at the examples above if you get stuck.


STEP 1 OF 2
    Ctrl + Enter to check

    SWIFT · STRINGS I

    Accessing Characters in Swift, explained

    Positions in a string

    Every character in a string has a position, counted from zero. In "Hello world!", H is at 0, e at 1, and the space is at 5, because spaces are characters too. The w is at 6.

    Many languages let you write text[6]. Swift doesn't. A Swift String can't be subscripted with an Int, and this line:

    let fruit = "Avocado"
    print(fruit[3])
    

    fails to compile with cannot subscript String with an Int, use a String.Index instead. The reason is that a Swift character is a user-perceived character, which can be made of several Unicode pieces and so takes different amounts of storage. Finding the 7th character means walking the string from the start, which isn't a constant-time lookup, so Swift makes you ask for it explicitly.

    Option 1: convert to an Array

    An Array of characters can be indexed with integers:

    let message = "Hello world!"
    let chars = Array(message)
    
    print(chars[0]) // H
    print(chars[1]) // e
    print(chars[6]) // w
    

    The array holds Character values, one per element. It is a copy, so it costs time and memory proportional to the string's length, but afterwards each lookup is fast. Printing three neighbours shows that the space has its own index:

    print("\(chars[4])\(chars[5])\(chars[6])")
    

    This prints o w.

    Option 2: build a String.Index

    The method index(_:offsetBy:) moves from a starting position by a number of characters and returns a String.Index, the type a string's subscript accepts:

    let fruit = "Avocado"
    let i = fruit.index(fruit.startIndex, offsetBy: 3)
    print(fruit[i]) // c
    

    fruit.startIndex is the position of the first character. An offset of 0 gives the first character, and an offset of fruit.count - 1 gives the last. The result is a Character, so printing it shows just the letter.

    Handy shortcuts

    • fruit.first and fruit.last are optionals, because an empty string has neither. fruit.first! is A. Use if let or ?? for strings that may be empty.
    • fruit[fruit.startIndex] is the first character with no offset.
    • fruit.index(before: fruit.endIndex) is the last one, because endIndex points just past the end.
    • fruit.index(after: fruit.startIndex) is the second character.

    Out of range crashes

    The offset must stay within the string. Using fruit[fruit.index(fruit.startIndex, offsetBy: 7)] on "Avocado", which has 7 characters, points past the end and the program stops with Fatal error: String index is out of bounds. The valid offsets run from 0 to count - 1. An array has the same limit: chars[7] traps with an index-out-of-range error.

    count counts characters, not bytes. In "héllo", count is 5 even though the UTF-8 form takes 6 bytes, and offset 1 gives é.

    The exercise

    The starting code is let fruit = "Avocado", and both steps use the index method:

    1. The first character. An offset of 0 from startIndex, then print it.
    2. The last character. "Avocado" has 7 characters, so the last one is at offset 6.
    let fruit = "Avocado"
    
    print(fruit[fruit.index(fruit.startIndex, offsetBy: 0)])
    print(fruit[fruit.index(fruit.startIndex, offsetBy: 6)])
    

    The output is two lines, A and then o.

    Common mistakes

    • Writing fruit[0]. It doesn't compile.
    • Counting from 1. The first character is at offset 0, so the last of 7 is at 6, not 7.
    • Using an index from one string on another. A String.Index belongs to the string that produced it.
    • Looping with index(_:offsetBy:) for every position. Each call walks from the start, so the loop gets slow on long strings. Use for c in fruit or convert to an array.

    Key takeaways

    • Positions start at 0 and spaces count.
    • Swift strings can't be indexed with integers; use Array(text) or a String.Index.
    • text.index(text.startIndex, offsetBy: n) builds the index for position n.
    • An index past the end crashes the program.
    Keep going with SwiftAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free