Start learning free

LESSON 58 OF 415 · STRINGS I

String Slicing in Swift: prefix, dropFirst and Ranges

Lesson Read the full explanation

Not only we can select a single character from a string, but we can also select a sequence of consecutive characters (e.g. substring) from a string.

To achieve this, we use index-based subscripting like this:

let start = string.index(string.startIndex, offsetBy: startOffset)
let end = string.index(string.startIndex, offsetBy: endOffset)
String(string[start..<end])

This is called slicing a string. It returns the substring from startIndex to endIndex.

Here is an example:

let language = "JavaScript"
let start = language.index(language.startIndex, offsetBy: 2)
let end = language.index(language.startIndex, offsetBy: 6)
let substring = String(language[start..<end])

print(substring) // Output: vaSc
print(language) // Output: JavaScript

The startIndex is a zero-based index at which we start extraction and the endIndex is also zero-based index before which we end the extraction.

The substring will not include the character at endIndex. You can see in our example that the character at index 2 (v) was included while the character at index 6 (r) was excluded.

Also notice that language preserved its value. The slicing does not affect the original string. It just creates a brand new one representing the sliced substring.


If you want to extract from an index to the end of the string, use an open-ended range:

let language = "JavaScript"
let start = language.index(language.startIndex, offsetBy: 4)
let substring = String(language[start...])

print(substring) // Output: Script

Assignment
Follow the Coding Tutorial and let's slice some strings!


Hint
Look at the examples above if you get stuck.


STEP 1 OF 3
    Ctrl + Enter to check

    SWIFT · STRINGS I

    String Slicing in Swift, explained

    What slicing is

    Slicing means taking a run of consecutive characters out of a string. Swift offers two ways to do it: ranges of String.Index values, and the helper methods prefix, suffix, dropFirst and dropLast. Neither changes the original string.

    Slicing with indices

    A Swift string can't be subscripted with an integer like language[2]. Positions are String.Index values, and you build one by moving from startIndex by a number of characters. Two indices form a half-open range start..<end: the character at start is included and the one at end is not.

    let language = "JavaScript"
    let start = language.index(language.startIndex, offsetBy: 2)
    let end = language.index(language.startIndex, offsetBy: 6)
    let substring = String(language[start..<end])
    
    print(substring)
    print(language)
    
    vaSc
    JavaScript
    

    The character at offset 2 (v) is in the result and the character at offset 6 (r) is not. The original language keeps its value.

    To slice from an offset to the end, use an open-ended range, language[start...]. To slice from the beginning, use language[..<end].

    let language = "JavaScript"
    let start = language.index(language.startIndex, offsetBy: 4)
    print(String(language[start...]))
    
    Script
    

    Substring and String

    Subscripting a string with a range doesn't give a String. It gives a Substring, a view onto the original string's storage. That is cheap, because nothing is copied, but a Substring is a different type, so you can't pass it where a String is expected. Wrapping it in String(...) copies the characters into a real String that you can keep and use anywhere.

    let language = "JavaScript"
    let start = language.index(language.startIndex, offsetBy: 2)
    let end = language.index(language.startIndex, offsetBy: 6)
    print(type(of: language[start..<end]))
    print(type(of: String(language[start..<end])))
    
    Substring
    String
    

    Slicing with prefix, dropFirst and suffix

    For most slices you don't need indices at all:

    • prefix(n) keeps the first n characters.
    • suffix(n) keeps the last n characters.
    • dropFirst(n) skips the first n characters and keeps the rest.
    • dropLast(n) skips the last n characters and keeps the rest.

    All four return a Substring. They can be chained: dropFirst(7).prefix(5) first skips 7 characters, then keeps the next 5. Unlike the index methods, they don't crash when n is too large. They just return as much as the string has:

    let message = "Coding feels great"
    print(message.prefix(100))
    print(message.dropFirst(100).isEmpty)
    
    Coding feels great
    true
    

    Moving an index past the end with index(_:offsetBy:) does crash the program, so use these helpers when the length isn't certain.

    The exercise

    The starter code has let message = "Coding feels great" and a final print that uses three constants you have to create. The characters are numbered from 0, so "Coding" takes positions 0 to 5, a space is at 6, "feels" takes 7 to 11, a space is at 12, and "great" takes 13 to 17.

    1. firstWord holds the first 6 characters, prefix(6), converted with String(...).
    2. middleWord holds the characters at positions 7 to 11. Skip 7 with dropFirst(7), then keep 5 with prefix(5).
    3. lastWord holds everything from position 13 on, so dropFirst(13) is enough. suffix(5) would give the same result, counting from the end.
    let message = "Coding feels great"
    
    let firstWord = String(message.prefix(6))
    let middleWord = String(message.dropFirst(7).prefix(5))
    let lastWord = String(message.dropFirst(13))
    print(firstWord + "\n" + middleWord + "\n" + lastWord)
    
    Coding
    feels
    great
    

    The conversion to String matters here: the + operator in the last line joins strings, so the pieces should be String values.

    Common mistakes

    • Counting the end index as included. In start..<end the end is excluded, so a slice from offset 2 to offset 6 has 4 characters.
    • Off-by-one with dropFirst. dropFirst(7) removes 7 characters, so the next one is at position 7, the eighth character. Counting "8th through 12th" in human terms means starting at position 7.
    • Offsetting past the end. index(_:offsetBy:) with a number larger than the string's length is a fatal error.
    • Keeping a Substring around. It shares the original string's storage, which stays alive for as long as the Substring does. Convert it with String(...) when you want to store it.

    Key takeaways

    • Slice with string[start..<end], where the indices come from index(_:offsetBy:), or use prefix, suffix, dropFirst and dropLast.
    • The end of a half-open range is excluded.
    • Slices are Substring values. Wrap them in String(...) to get a String.
    • Slicing never changes the original string.
    Keep going with SwiftAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free