Swift
String.Index
Swift Programming
String Manipulation
Swift Development

How does String.Index work in Swift

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Swift does not allow direct integer indexing into String because strings are Unicode-correct collections of extended grapheme clusters. A user-visible character may occupy multiple Unicode scalars or multiple UTF encodings, so an integer offset is not a safe universal index.

String.Index exists to represent a valid position in a particular string. Once you accept that Swift strings are character-aware rather than byte-addressed, the indexing APIs make much more sense.

Why String.Index Exists

In many languages, people write code like text[3] and assume the fourth visible character lives there. Swift intentionally rejects that assumption.

A string such as Cafe\u{301} may render as Café, but the final visible character can be composed from more than one scalar. Emoji sequences can be even more complex. Swift protects you from slicing through those boundaries by requiring a String.Index.

swift
let text = "Cafe\u{301} 🚀"
print(text.count)

count reports user-perceived characters, not bytes or UTF-16 units. That is the design pressure behind String.Index.

Basic Index Navigation

You start from known anchors such as startIndex and endIndex, then move with the provided index methods.

swift
1let s = "Kubernetes"
2let i = s.index(s.startIndex, offsetBy: 3)
3print(s[i])
4
5let last = s.index(before: s.endIndex)
6print(s[last])

This gives you valid positions relative to that exact string value.

If the offset may be out of range, use limitedBy.

swift
1if let safe = s.index(s.startIndex, offsetBy: 50, limitedBy: s.endIndex) {
2    print(s[safe])
3} else {
4    print("out of range")
5}

That is much safer than forcing an index calculation and crashing on variable-length input.

Slicing With Ranges

Once you have indices, use them to form a range and slice the string.

swift
1let message = "Swift String Index"
2let start = message.index(message.startIndex, offsetBy: 6)
3let end = message.index(start, offsetBy: 6)
4let part = message[start..<end]
5print(part)

The result is a Substring, not a new String. Convert it when you need an owned string value.

swift
let stable = String(part)
print(stable)

That matters because a Substring can keep the original string’s storage alive longer than you intended.

Converting Integer Offsets Carefully

Sometimes another part of the program gives you integer offsets. The safest pattern is to wrap the conversion in helper methods.

swift
1extension String {
2    func indexAt(_ offset: Int) -> String.Index? {
3        guard offset >= 0 else { return nil }
4        return index(startIndex, offsetBy: offset, limitedBy: endIndex)
5    }
6
7    func characterAt(_ offset: Int) -> Character? {
8        guard let idx = indexAt(offset), idx < endIndex else { return nil }
9        return self[idx]
10    }
11}
12
13let value = "naïve"
14print(value.characterAt(2) ?? "-")

This keeps bounds checks in one place and avoids scattering fragile indexing code across the project.

Performance and Other Views

Index movement in String can be linear because Swift respects grapheme boundaries. Repeatedly walking from startIndex in a loop can become expensive.

If your logic is truly byte-oriented or protocol-oriented, use a different view such as utf8.

swift
1let packet = "GET /health"
2for byte in packet.utf8 {
3    print(byte, terminator: " ")
4}
5print()

That is appropriate only when the algorithm genuinely cares about bytes. For user text, stay with native string indexing.

Foundation Interop

When interoperating with Cocoa APIs, you may see NSRange, which is based on UTF-16 positions. That does not map directly to String.Index.

swift
1import Foundation
2
3let text = "hello 👋"
4let ns = text as NSString
5let range = ns.range(of: "👋")
6print(range.location, range.length)

If you move between Swift-native strings and Foundation APIs, use explicit conversion helpers and test with emoji and combining marks. Offsets that look correct in UTF-16 are not automatically valid character boundaries in Swift.

Common Pitfalls

A common mistake is assuming one integer offset equals one visible character. That is exactly what Swift is trying to prevent.

Another issue is computing indices once and reusing them after mutating the string. Indices belong to a specific string state, so recalculate them after edits.

Developers also sometimes optimize prematurely by dropping into utf8 or utf16 views when the problem is really about characters. That trades correctness for an optimization that may not matter.

Finally, remember that Substring is not the same thing as String. Hold onto long-lived substrings only when you mean to.

Summary

  • 'String.Index is Swift’s Unicode-safe way to represent positions in a string.'
  • Start from startIndex or endIndex and move with index APIs.
  • Use limitedBy when offsets might exceed the valid range.
  • Convert Substring to String when you need a standalone value.
  • Choose utf8 or utf16 views only when the algorithm truly works at that encoding level.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.