Check if an Array Contains a Value in Python (in operator)

Checking whether a Python array contains a value is done with the in operator:

fruits = ["apple", "pear", "plum"]

"pear" in fruits        # True
"kiwi" not in fruits    # True

Note the vocabulary. What most people call an array, Python calls a list, and in works on both that and a NumPy array. There is no includes() or contains() method.

Below: conditions rather than exact values, checking several at once, NumPy arrays, and why converting to a set can be worth it. Output is from Python 3.12.5, NumPy 2.5.3.

Diagram showing the Python in operator used on a list a string a dictionary and a NumPy array
One operator, four containers, and one case where it raises instead.

Check if a list or array contains a value

in returns True or False and reads like English:

fruits = ["apple", "pear", "plum"]

print('"pear" in fruits     ->', "pear" in fruits)
print('"kiwi" in fruits     ->', "kiwi" in fruits)
print('"kiwi" not in fruits ->', "kiwi" not in fruits)
print()

if "pear" in fruits:
    print("found it")

print()
print("`in` is an operator, not a method, so there are no brackets")
print("and nothing to import.")

Output:

"pear" in fruits     -> True
"kiwi" in fruits     -> False
"kiwi" not in fruits -> True

found it

`in` is an operator, not a method, so there are no brackets
and nothing to import.
Command Prompt showing the Python in operator checking whether a list contains a value
in and not in answer the question directly.

It is an operator rather than a method, so there are no brackets and nothing to import. not in is a single operator too, which reads better than not (x in y).

Python has no includes() or contains() method

If you are coming from another language, the method you want does not exist:

fruits = ["apple", "pear"]

print("Python lists have no includes() or contains() method:")
print("  hasattr(list, 'includes') ->", hasattr(list, "includes"))
print("  hasattr(list, 'contains') ->", hasattr(list, "contains"))
print()

try:
    fruits.includes("pear")
except AttributeError as err:
    print("  fruits.includes('pear') -> AttributeError:", err)

print()
print("the operator replaces all of them:")
print("  JavaScript  arr.includes(x)   ->  Python  x in arr")
print("  Java        list.contains(x)  ->  Python  x in list")

Output:

Python lists have no includes() or contains() method:
  hasattr(list, 'includes') -> False
  hasattr(list, 'contains') -> False

  fruits.includes('pear') -> AttributeError: 'list' object has no attribute 'includes'

the operator replaces all of them:
  JavaScript  arr.includes(x)   ->  Python  x in arr
  Java        list.contains(x)  ->  Python  x in list
Command Prompt showing that a Python list has no includes method and raises AttributeError
Python replaces those methods with a single operator.
LanguageMethodPython
JavaScriptarr.includes(x)x in arr
Javalist.contains(x)x in list
C#list.Contains(x)x in list
PHPin_array(x, arr)x in arr

One operator covers every container, which is why Python never added the methods.

The in operator on lists, strings, sets and dictionaries

The same syntax, with one behaviour worth memorising:

print("the same operator works on every container:")
print('  "pear" in ["apple", "pear"]  ->', "pear" in ["apple", "pear"])
print('  "pear" in ("apple", "pear")  ->', "pear" in ("apple", "pear"))
print('  "pear" in {"apple", "pear"}  ->', "pear" in {"apple", "pear"})
print('  "ea"   in "pear"             ->', "ea" in "pear", " substring")
print()

scores = {"ann": 90, "bob": 85}
print("on a dictionary it checks the KEYS, not the values:")
print('  "ann" in scores        ->', "ann" in scores)
print("  90 in scores           ->", 90 in scores, " <- a value, so False")
print("  90 in scores.values()  ->", 90 in scores.values())
print()
print("that surprises people, so say .values() when you mean values.")

Output:

the same operator works on every container:
  "pear" in ["apple", "pear"]  -> True
  "pear" in ("apple", "pear")  -> True
  "pear" in {"apple", "pear"}  -> True
  "ea"   in "pear"             -> True  substring

on a dictionary it checks the KEYS, not the values:
  "ann" in scores        -> True
  90 in scores           -> False  <- a value, so False
  90 in scores.values()  -> True

that surprises people, so say .values() when you mean values.
Command Prompt showing the Python in operator on a list tuple set string and dictionary
On a dictionary, in checks the keys.

90 in scores is False even when 90 is one of the values, because a dictionary searches its keys. Add .values() when you mean the other half.

On a string, in tests for a substring rather than a single character, which is the usual way to check text. Iterating a dictionary covers the keys and values distinction in more depth.

Checking for a condition rather than an exact value

any() and all() extend the idea:

fruits = ["apple", "pear", "plum"]

print("an exact value:")
print('  "pear" in fruits ->', "pear" in fruits)
print()

print("anything matching a condition:")
print("  any(f.startswith('p') for f in fruits) ->",
      any(f.startswith("p") for f in fruits))
print("  which ones ->", [f for f in fruits if f.startswith("p")])
print()

print("every item matching:")
print("  all(len(f) > 3 for f in fruits) ->", all(len(f) > 3 for f in fruits))
print()

print("case insensitive:")
print('  "PEAR" in fruits ->', "PEAR" in fruits)
print('  lowercased       ->', "pear" in [f.lower() for f in fruits])
print()
print("`in` compares exactly, so casing and whitespace both count.")

Output:

an exact value:
  "pear" in fruits -> True

anything matching a condition:
  any(f.startswith('p') for f in fruits) -> True
  which ones -> ['pear', 'plum']

every item matching:
  all(len(f) > 3 for f in fruits) -> True

case insensitive:
  "PEAR" in fruits -> False
  lowercased       -> True

`in` compares exactly, so casing and whitespace both count.

any(...) stops at the first match, so it costs nothing extra on a large list. Swap it for a comprehension when you want the matching items rather than a yes or no.

Comparison is exact, so "PEAR" does not match "pear". Lowercase both sides when case should not matter.

Checking for several values at once

Combine in with any, all or set operations:

fruits = ["apple", "pear", "plum"]
wanted = ["pear", "kiwi"]

print("are they ALL there?")
print("  all(w in fruits for w in wanted) ->", all(w in fruits for w in wanted))
print()
print("is ANY of them there?")
print("  any(w in fruits for w in wanted) ->", any(w in fruits for w in wanted))
print()
print("which ones are missing?")
print("  ->", [w for w in wanted if w not in fruits])
print()
print("with sets, which is faster and reads well:")
print("  set(wanted) & set(fruits) ->", set(wanted) & set(fruits))
print("  set(wanted) - set(fruits) ->", set(wanted) - set(fruits), " the missing ones")

Output:

are they ALL there?
  all(w in fruits for w in wanted) -> False

is ANY of them there?
  any(w in fruits for w in wanted) -> True

which ones are missing?
  -> ['kiwi']

with sets, which is faster and reads well:
  set(wanted) & set(fruits) -> {'pear'}
  set(wanted) - set(fruits) -> {'kiwi'}  the missing ones

Set operations read best when both sides are collections. & gives what they share and - gives what is missing, in one expression each.

Why converting to a set can be much faster

This matters as soon as you check repeatedly:

import timeit

setup = "data = list(range(1_000_000)); lookup = set(data); target = 999_999"

list_time = timeit.timeit("target in data", setup, number=100)
set_time = timeit.timeit("target in lookup", setup, number=100)

print("100 lookups in a collection of 1,000,000 items:")
print(f"  list  {list_time:.4f}s")
print(f"  set   {set_time:.6f}s")
print(f"  the set is roughly {list_time / set_time:,.0f} times quicker")
print()
print("a list is checked item by item, so a miss reads all million.")
print("a set jumps straight to where the value would be.")
print()
print("converting once pays for itself as soon as you check repeatedly:")
print("  lookup = set(data)   # once")
print("  target in lookup     # many times")

Output:

100 lookups in a collection of 1,000,000 items:
  list  0.7076s
  set   0.000007s
  the set is roughly 101,081 times quicker

a list is checked item by item, so a miss reads all million.
a set jumps straight to where the value would be.

converting once pays for itself as soon as you check repeatedly:
  lookup = set(data)   # once
  target in lookup     # many times
Command Prompt timing membership tests against a Python list and a set of one million items
The same lookups against a list and against a set.

A list is searched item by item, so a value that is absent means reading every one. A set jumps straight to where the value would be, and the gap on a million items runs to many thousands of times.

Building the set costs one pass, so it pays for itself the moment you check more than a handful of times. Keep the list when order matters and build a set alongside it for the lookups.

A set also drops duplicates, so use it for membership rather than as a replacement for the list. Splitting a list is useful when the data is too large to hold at once.

Check if a NumPy array contains a value

in works, but NumPy gives you more:

import numpy as np

arr = np.array([1, 2, 3, 4])

print("the operator works on a NumPy array too:")
print("  3 in arr ->", 3 in arr)
print()
print("but the array-aware versions do more:")
print("  np.any(arr == 3)      ->", np.any(arr == 3))
print("  np.isin(arr, [2, 5])  ->", np.isin(arr, [2, 5]), " one answer per element")
print("  np.isin(arr, [2, 5]).any() ->", np.isin(arr, [2, 5]).any())
print()

print("where it is, not just whether:")
print("  np.where(arr == 3)[0] ->", np.where(arr == 3)[0])
print()

two_d = np.array([[1, 2], [3, 4]])
print("on a 2D array, `in` searches every element:")
print("  3 in two_d ->", 3 in two_d)
print("to look for a whole ROW:")
print("  [1, 2] in two_d.tolist() ->", [1, 2] in two_d.tolist())

Output:

the operator works on a NumPy array too:
  3 in arr -> True

but the array-aware versions do more:
  np.any(arr == 3)      -> True
  np.isin(arr, [2, 5])  -> [False  True False False]  one answer per element
  np.isin(arr, [2, 5]).any() -> True

where it is, not just whether:
  np.where(arr == 3)[0] -> [2]

on a 2D array, `in` searches every element:
  3 in two_d -> True
to look for a whole ROW:
  [1, 2] in two_d.tolist() -> True

np.isin() answers per element, which is what you want for filtering. Adding .any() collapses that back to a single True or False.

On a 2D array, in looks at every element regardless of rows. To search for a whole row, compare against .tolist(). np.where gives the positions rather than a yes or no.

ValueError: the truth value of an array is ambiguous

The error that follows people around NumPy:

import numpy as np

arr = np.array([1, 2, 3])

print("this is the error everyone meets with NumPy:")
try:
    if arr == 2:
        print("never reached")
except ValueError as err:
    print("  if arr == 2 -> ValueError:", err)

print()
print("arr == 2 is not True or False, it is an array:")
print("  arr == 2 ->", arr == 2)
print()
print("so say which you mean:")
print("  (arr == 2).any() ->", (arr == 2).any(), " is it anywhere?")
print("  (arr == 2).all() ->", (arr == 2).all(), " is it everywhere?")

Output:

this is the error everyone meets with NumPy:
  if arr == 2 -> ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

arr == 2 is not True or False, it is an array:
  arr == 2 -> [False  True False]

so say which you mean:
  (arr == 2).any() -> True  is it anywhere?
  (arr == 2).all() -> False  is it everywhere?
Command Prompt showing the NumPy ValueError that the truth value of an array with more than one element is ambiguous
arr == 2 is an array of booleans, not a single True or False.

arr == 2 compares every element and returns an array of results. An if needs one answer, and NumPy refuses to guess whether you meant any of them or all of them.

.any() and .all() say which, and the error message names them both.

Gotchas worth knowing

Four results that look wrong until explained:

print("floats do not always compare the way they look:")
print("  0.1 + 0.2 in [0.3] ->", 0.1 + 0.2 in [0.3])
print("  because 0.1 + 0.2 is", repr(0.1 + 0.2))
print("  use a tolerance instead:")
print("    any(abs(0.1 + 0.2 - x) < 1e-9 for x in [0.3]) ->",
      any(abs(0.1 + 0.2 - x) < 1e-9 for x in [0.3]))
print()

print("an empty string is inside every string:")
print('  "" in "abc" ->', "" in "abc")
print()

print("nested lists compare as whole items:")
print("  [1, 2] in [[1, 2], [3, 4]] ->", [1, 2] in [[1, 2], [3, 4]])
print("  1 in [[1, 2], [3, 4]]      ->", 1 in [[1, 2], [3, 4]], " <- not found at the top level")
print()

print("True and 1 are equal, so they match each other:")
print("  True in [1, 2] ->", True in [1, 2])

Output:

floats do not always compare the way they look:
  0.1 + 0.2 in [0.3] -> False
  because 0.1 + 0.2 is 0.30000000000000004
  use a tolerance instead:
    any(abs(0.1 + 0.2 - x) < 1e-9 for x in [0.3]) -> True

an empty string is inside every string:
  "" in "abc" -> True

nested lists compare as whole items:
  [1, 2] in [[1, 2], [3, 4]] -> True
  1 in [[1, 2], [3, 4]]      -> False  <- not found at the top level

True and 1 are equal, so they match each other:
  True in [1, 2] -> True

Floating point is the sharpest one. 0.1 + 0.2 is not exactly 0.3, so an exact membership test fails. Compare with a tolerance instead.

The empty string is contained in every string, nested lists match as whole items rather than by their contents, and True matches 1 because they are equal in Python.

Quick reference for contains checks

What you haveUse
A list or tuplevalue in items
A stringsubstring in text
A dictionary, by keykey in d
A dictionary, by valuevalue in d.values()
Repeated lookupsvalue in set(items)
A condition, not a valueany(f(x) for x in items)
A NumPy arraynp.any(arr == value) or np.isin()
Several valuesset(wanted) & set(items)

Guides that pair well with this one:

Frequently asked questions

How do I check if an array contains a value in Python?

Use the in operator: value in my_list. It returns True or False and works on lists, tuples, sets, strings and NumPy arrays.

Does Python have an includes() or contains() method?

No. The in operator replaces both. arr.includes(x) in JavaScript is x in arr in Python.

Why does ‘in’ return False for a dictionary value?

Because in searches a dictionary’s keys. Use value in d.values() to check the values instead.

Is a set faster than a list for checking membership?

Much faster on anything large. A list is searched item by item; a set goes straight to the value. Converting costs one pass, so it pays off as soon as you check repeatedly.

How do I check if a NumPy array contains a value?

value in arr works, or np.any(arr == value). Use np.isin(arr, values) to test many values at once.

Why do I get ‘truth value of an array is ambiguous’?

Because arr == value returns an array of booleans, and if needs one answer. Use (arr == value).any() or .all().

How do I check if a list contains a value ignoring case?

Compare lowercased values: "pear" in [f.lower() for f in fruits]. The operator itself is described in the Python membership test documentation.