The Magic of Strings in Swift: Why "Just Get a Character" Is a Challenge
\n\nAny developer who has switched to Swift from Python, JavaScript, or C++ eventually hits a wall. It seems like a simple task: "Give me the third character of a string." In Python it's str[2]. In JavaScript it's str[2]. In Swift... it's more complicated. And if you've ever searched for the answer to the question "get nth character of a string in Swift", you know that Stack Overflow is literally swarming with this question (thousands of views, hundreds of upvotes).
Why? Because Swift treats strings not as an array of bytes, but as a complex, Unicode-compliant sequence of graphemes. And if you approach the task head-on — performance can drop, and the code can become unreadable.
\n\nIn this article, we won't just give the answer. We will break down optimizing the retrieval of the nth character in Swift, look at the performance of different approaches, and choose the best one. Let's go!
\n\nWhy doesn't string[2] work? A quick primer on String.Index
\n\nIn Swift, String is not a collection of Character with random access. It is a collection with access via indices, which are not integers. Why is that?
- \n
- Unicode and graphemes. One "character" can consist of several Unicode scalars (for example, the emoji "🇷🇺" or "é" with an accent). If the index were simply an Int, then
str[1]could return half an emoji. \n - Performance. Storing an index as an Int for a Unicode string is a compromise. Swift uses O(1) access to the start and end, but for an arbitrary index, you need to traverse the string. \n
Therefore, the standard way to get the nth character in Swift looks like this:
\n\nlet str = "Привет, Swift!"\nlet index = str.index(str.startIndex, offsetBy: 2)\nlet char = str[index] // "и"\n\nDoes it work? Yes. Is it optimal? No.
\n\nThe Performance Problem: What offsetBy Hides
\n\nEach call to str.index(startIndex, offsetBy: n) is an O(n) operation. Imagine you have a string of 10,000 characters, and you want to get the character at index 9999. Swift will traverse all 9999 characters to find the right one. Do this in a loop 100 times? You get quadratic complexity.
Here is an example of slow code that beginners often write:
\n\nlet longString = "A" + String(repeating: "b", count: 10_000) + "C"\nfor i in 0..<longString.count {\n let index = longString.index(longString.startIndex, offsetBy: i)\n let char = longString[index]\n // Do something with char\n}\n\nThis code will run very slowly, because each call to offsetBy starts from startIndex. The complexity is effectively O(n²).
Optimization #1: Using an Incremental Index
\n\nThe simplest way to speed up get nth character Swift is to not recalculate the index from scratch each time. Use index(after:) or formIndex(after:).
let longString = "A" + String(repeating: "b", count: 10_000) + "C"\nvar index = longString.startIndex\nfor _ in 0..<longString.count {\n let char = longString[index]\n // Do something with char\n index = longString.index(after: index) // O(1)!\n}\n\nNow the complexity is linear O(n). But what if we need random access by index, not sequential access?
\n\nOptimization #2: Converting to an Array of Characters (with caveats)
\n\nIf you need to frequently access characters by an arbitrary index (nth character), and the string is not huge, the easiest way is to convert it to [Character]. Then access will be O(1).
let str = "Привет, Swift!"\nlet chars = Array(str)\nlet char = chars[2] // "и