array.firstIndex(of: value) to get the index of the first match. It returns an Int?, so unwrap it before use. Use lastIndex(of:) for the last match, or firstIndex(where:) with the === operator when you need the exact instance of a class.Why this happens
Swift arrays have no index("text") method like Python lists. Array is a struct that conforms to Collection, and the collection protocols give you index-based access through startIndex, endIndex and indices — not a search-by-value method that returns a plain Int.
Searching by value is done by a separate API, and that API has been renamed more than once. Code written for Swift 1 or 2 calls a free function find(_:_:). Swift 3 renamed it to index(of:), and Swift 4.2 split it into firstIndex(of:) and lastIndex(of:). Paste an old snippet into a current project and you get errors such as Cannot find 'find' in scope or Value of type '[String]' has no member 'indexOf'.
The other reason lookups feel awkward is that the item may not be in the array at all. Swift makes that explicit: the search returns an optional index, and you must decide what a missing item means in your code.
How to fix it
1. Use firstIndex(of:) for values that are Equatable
This is the direct replacement for the old find call. It returns the position of the first matching element, or nil if nothing matches.
let letters = ["a", "b", "c"]
if let index = letters.firstIndex(of: "c") {
print(index) // 2
}
let missing = letters.firstIndex(of: "d") // nil
The element type must conform to Equatable, which String, Int, Double, and any struct you mark Equatable already do.
Do not force-unwrap the result just to keep the code short. A force unwrap turns a normal “not found” case into a crash, and array contents change at runtime far more often than people expect.
let index = letters.firstIndex(of: "d")! // crash if the value is absent
2. Use lastIndex(of:) when duplicates exist
With repeated values, firstIndex(of:) always gives you the earliest position. If you need the most recent one, use the matching method on the other end.
let letters = ["a", "b", "c", "a"]
let first = letters.firstIndex(of: "a") // 0
let last = letters.lastIndex(of: "a") // 3
Both return the same Int?, so the unwrapping rules are identical.
3. Compare identity with === for class instances
For reference types, firstIndex(of:) uses ==. If your class is Equatable and two different objects compare equal — same id, same stored values — you get the index of whichever one comes first, not necessarily the instance you passed in.
When you want the exact object you are holding, search with the identity operator inside a predicate closure:
final class Person {
let name: String
init(name: String) { self.name = name }
}
let john = Person(name: "John")
let sue = Person(name: "Sue")
let people = [john, sue]
let index = people.firstIndex { $0 === john } // Optional(0)
The trailing-closure form above is the same as writing people.firstIndex(where: { $0 === john }). If the object is not in the array, the result is nil.
If you actually want value equality — two Person instances with the same name should match — conform the class to Equatable and go back to firstIndex(of:).
4. Use a predicate when the type is not Equatable
firstIndex(where:) accepts any closure returning Bool, so it works with types that cannot be compared with == and with searches on a property rather than the whole value.
struct Order {
let id: Int
let total: Double
}
let orders = [Order(id: 1, total: 9.99), Order(id: 2, total: 24.50)]
let index = orders.firstIndex { $0.id == 2 } // Optional(1)
This is also the cleanest way to express “first element matching a condition” without adding conformance to a model just to search a list.
5. Port snippets that still use the old names
If you are adapting code from an older tutorial, translate the call instead of guessing at a new signature:
| Old call | Current call |
|---|---|
find(arr, value) |
arr.firstIndex(of: value) |
arr.indexOf(value) (Swift 2) |
arr.firstIndex(of: value) |
arr.index(of: value) (Swift 3 / 4) |
arr.firstIndex(of: value) |
All of the left-hand forms produce compile errors on current Swift; the right-hand column is the supported API.
6. Handle the non-Int index of String
firstIndex(of:) exists on every collection, including String, but a string’s index is a String.Index, not an Int.
let word = "hello"
let position = word.firstIndex(of: "l") // Optional(String.Index)
if let position {
let offset = word.distance(from: word.startIndex, to: position) // 2
}
If you need a plain offset, convert it with distance(from:to:). Converting with Int(position) does not compile.
7. Get every matching index
When duplicates matter and you need all of them, filter the array’s indices:
let letters = ["a", "b", "a", "c"]
let allA = letters.indices.filter { letters[$0] == "a" } // [0, 2]
For very large arrays searched repeatedly in a loop, build a lookup once instead of scanning each time:
var indexByValue: [String: Int] = [:]
for (offset, value) in letters.enumerated() where indexByValue[value] == nil {
indexByValue[value] = offset
}
That turns an O(n) scan per lookup into a single dictionary read.
How to prevent it
- Treat every search result as optional and unwrap it with
if let,guard let, or??. - Choose
firstIndex(of:)only when the element type isEquatable; otherwise usefirstIndex(where:). - Decide explicitly whether class instances should be matched by identity (
===) or equality (==), and keep that choice consistent across the codebase. - Never force-unwrap an index that came from a search over runtime data.
- Re-check old snippets against current Swift naming before copying them into a project.
- Use
indices.filteror a prebuilt dictionary when you need all matches or repeated lookups.
FAQ
Why does firstIndex(of:) return an optional?
Because the value may not exist in the array. The type is Int?, and nil means no element matched. There is no valid sentinel index to return instead, so Swift makes the absence part of the type.
What is the difference between == and === when searching an array?
== compares values and requires Equatable. === compares object identity and only works with class instances. If two separate objects hold identical data, == finds the first equal one while === finds only the exact instance.
How do I find the index of an item in an array of structs?
Mark the struct Equatable and use firstIndex(of:), or skip the conformance and compare a property with firstIndex(where:), for example items.firstIndex { $0.id == targetID }.
Parts of this guide are adapted from content licensed under CC BY-SA 4.0; this article is shared under the same license.
