Get nth character in Swift: how not to drop performance

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

The Magic of Strings in Swift: Why "Just Get a Character" Is a Challenge

\n\n

Any 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).

\n\n

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\n

In 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\n

Why doesn't string[2] work? A quick primer on String.Index

\n\n

In 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\n
    \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
\n\n

Therefore, the standard way to get the nth character in Swift looks like this:

\n\n
let str = "Привет, Swift!"\nlet index = str.index(str.startIndex, offsetBy: 2)\nlet char = str[index] // "и"
\n\n

Does it work? Yes. Is it optimal? No.

\n\n

The Performance Problem: What offsetBy Hides

\n\n

Each 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.

\n\n

Here is an example of slow code that beginners often write:

\n\n
let 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\n

This code will run very slowly, because each call to offsetBy starts from startIndex. The complexity is effectively O(n²).

\n\n

Optimization #1: Using an Incremental Index

\n\n

The 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:).

\n\n
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\n

Now the complexity is linear O(n). But what if we need random access by index, not sequential access?

\n\n

Optimization #2: Converting to an Array of Characters (with caveats)

\n\n

If 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).

\n\n
let str = "Привет, Swift!"\nlet chars = Array(str)\nlet char = chars[2] // "и                

Blogs

Book Recommendations