Swift Array Contains: Check if an Element Exists

Swift Array Contains: Check if an Element Exists

Written by

in

Quick answer: Swift has no include or has method — call contains(_:) on the array, which returns a Bool. It requires the element type to conform to Equatable; for anything else use contains(where:) with a predicate.

Why this happens

Swift has no global contains function and no include, has or contain method. Membership testing is a method on Sequence, declared roughly like this:

extension Sequence where Element: Equatable {
    func contains(_ element: Element) -> Bool
}

Because the method sits behind an Equatable constraint, Xcode will not offer it in autocomplete when the array’s element type does not conform to Equatable — a struct without a conformance, a plain class, and so on. Typing contain and seeing nothing is usually that, not a missing API.

Older Swift releases used a free function, contains(elements, 5), and find for index lookup. Plenty of tutorials still show that form, which is why searching for the answer turns up code that no longer compiles.

How to fix it

1. Call contains(_:) on the array

let elements = [1, 2, 3, 4, 5]

if elements.contains(5) {
    print("yes")
}

This works on any sequence whose Element is Equatable: arrays, sets, ranges, and even dictionary.keys. The result is a plain Bool, so it drops straight into an if, a guard, or a ternary.

2. Use contains(where:) when there is no Equatable conformance

struct Track {
    let id: Int
    let title: String
}

let tracks = [
    Track(id: 1, title: "Intro"),
    Track(id: 2, title: "Outro")
]

let hasTrackTwo = tracks.contains { $0.id == 2 }

contains(where:) takes a (Element) throws -> Bool predicate and does not require Equatable. It also stops at the first match, so it is cheaper than filter { ... }.isEmpty, which builds a whole new array just to check whether it is empty.

3. Conform your type to Equatable

If you want to pass a value — tracks.contains(track) — the element type must be Equatable.

struct Track: Equatable {
    let id: Int
    let title: String
}

let target = Track(id: 2, title: "Outro")
if tracks.contains(target) {
    print("found")
}

The compiler synthesizes == for structs and for enums whose associated values are all Equatable. Classes get no synthesis: write static func == (lhs:rhs:) yourself. If you skip the conformance, the compiler reports that referencing contains on Sequence requires your type to conform to Equatable.

4. NSObject subclasses: override isEqual:

For NSObject subclasses, == forwards to isEqual:, so contains uses whatever isEqual: you implement. Override hash as well if the same objects go into a Set or act as dictionary keys.

final class Track: NSObject {
    let id: Int

    init(id: Int) {
        self.id = id
    }

    override func isEqual(_ object: Any?) -> Bool {
        guard let other = object as? Track else { return false }
        return id == other.id
    }

    override var hash: Int { id }
}

Without the isEqual: override, contains falls back to identity comparison and two separate instances holding the same data will not match.

5. Find and remove the element

contains only answers yes or no. To remove the first match, locate its index first:

var itemList = ["a", "b", "c"]

if let index = itemList.firstIndex(of: "b") {
    itemList.remove(at: index)
}

firstIndex(of:) requires Equatable; firstIndex(where:) takes a predicate instead. To drop every match in one pass, use removeAll(where:):

itemList.removeAll { $0 == "b" }

6. Use a Set for repeated membership tests

contains on an Array is O(n): it walks the elements until it finds a match. If you check membership inside a loop, convert the collection once to a Set (which requires Hashable) and get average O(1) lookups.

let allowedStatuses: Set<String> = ["draft", "published"]

for status in incomingStatuses where allowedStatuses.contains(status) {
    // handle allowed status
}

7. Case-insensitive string checks

contains(_:) on an array of strings is case-sensitive. Use contains(where:) with caseInsensitiveCompare when case should not matter:

let names = ["Alice", "Bob"]
let hasAlice = names.contains { $0.caseInsensitiveCompare("alice") == .orderedSame }

How to prevent it

  • Remember contains is a Sequence method, not a global function; contains(array, value) is Swift 1 era code.
  • Conform custom value types to Equatable so the plain contains(_:) overload is available.
  • Prefer contains(where:) over filter { }.isEmpty or first(where:) != nil when you only need a Bool.
  • Switch to a Set when the same collection is queried many times.
  • For NSObject subclasses, override isEqual: and hash together.

FAQ

Does Swift have an include or has method for arrays?

No. The method is contains(_:), inherited from Sequence when the element type is Equatable. include, has and contain do not exist in the standard library.

What is the difference between contains(_:) and contains(where:)?

contains(_:) compares each element with == and needs Equatable. contains(where:) runs your closure against each element and works with any element type, which is what you use for matching on a property or for a case-insensitive string comparison.

Why does contains not compile for my custom struct?

The struct probably does not conform to Equatable, so the Element: Equatable overload is unavailable. Add : Equatable to the declaration — the compiler synthesizes == when all stored properties are Equatable — or switch to contains(where:).

Sources & credits

Parts of this guide are adapted from content licensed under CC BY-SA 3.0; this article is shared under the same license.