To get the index of an element in a Python list, call .index() on the list:
fruits = ["apple", "pear", "plum"]
fruits.index("pear") # 1
Two things to know before you rely on it. It returns only the first match, and a value that is not there raises ValueError rather than returning -1.
Both have straightforward fixes, covered below along with finding every occurrence and what to do with NumPy arrays. Output is from Python 3.12.5.
.index() stops at the first match and never mentions the second.Using list.index() to find an element
The method takes the value you are looking for and gives back its position:
fruits = ["apple", "pear", "plum", "pear"]
print("fruits.index('pear') ->", fruits.index("pear"))
print("fruits.index('plum') ->", fruits.index("plum"))
print()
print("positions count from 0, so 'apple' is 0 and 'pear' is 1.")
print("Note that 'pear' appears twice and only the FIRST is reported.")
Output:
fruits.index('pear') -> 1
fruits.index('plum') -> 2
positions count from 0, so 'apple' is 0 and 'pear' is 1.
Note that 'pear' appears twice and only the FIRST is reported.
.index() returns the position of the first match.Positions start at 0, so the first item is at index 0. That is the same numbering used by slicing and by range(len(items)).
Why index() raises ValueError instead of returning -1
A missing value is treated as a mistake, not as a result:
fruits = ["apple", "pear", "plum"]
try:
fruits.index("kiwi")
except ValueError as err:
print("fruits.index('kiwi') -> ValueError:", err)
print()
print("an empty list behaves the same way:")
try:
[].index(1)
except ValueError as err:
print("[].index(1) -> ValueError:", err)
print()
print("so a missing value is an exception, not a -1 or a None.")
Output:
fruits.index('kiwi') -> ValueError: 'kiwi' is not in list
an empty list behaves the same way:
[].index(1) -> ValueError: 1 is not in list
so a missing value is an exception, not a -1 or a None.
ValueError: 'kiwi' is not in list.This is deliberate. Returning -1 would be a valid index in Python, since negative indexes count from the end, so items[-1] would silently hand back the last item instead of signalling a miss.
Python has no indexOf method
If you arrived from JavaScript, the method you want is spelled differently and behaves differently:
print("Python lists do not have the methods you may be looking for:")
print(" hasattr(list, 'indexOf') ->", hasattr(list, "indexOf"))
print(" hasattr(list, 'find') ->", hasattr(list, "find"))
print(" hasattr(list, 'index') ->", hasattr(list, "index"))
print()
print("STRINGS do have find, and it returns -1 instead of raising:")
print(" 'hello'.find('z') ->", "hello".find("z"))
print(" 'hello'.find('ll') ->", "hello".find("ll"))
print()
print(" 'hello'.index('z') ->", end=" ")
try:
"hello".index("z")
except ValueError as err:
print("ValueError:", err)
print()
print("So: lists get .index() only. Strings get both .find() and .index().")
Output:
Python lists do not have the methods you may be looking for:
hasattr(list, 'indexOf') -> False
hasattr(list, 'find') -> False
hasattr(list, 'index') -> True
STRINGS do have find, and it returns -1 instead of raising:
'hello'.find('z') -> -1
'hello'.find('ll') -> 2
'hello'.index('z') -> ValueError: substring not found
So: lists get .index() only. Strings get both .find() and .index().
.index() only; strings also have .find().| You want | JavaScript | Python |
|---|---|---|
| Position, or a miss signal | arr.indexOf(x) returns -1 | list.index(x) raises ValueError |
| Position in a string | str.indexOf(x) returns -1 | str.find(x) returns -1 |
| Is it there at all? | arr.includes(x) | x in list |
| Every position | loop or flatMap | [i for i, v in enumerate(list) if v == x] |
str.find() is the closest match to indexOf and it does return -1. Lists simply never got an equivalent.
Getting the index without an exception
Three ways to turn a miss into a value you can test:
fruits = ["apple", "pear", "plum"]
def index_or(items, value, default=-1):
"""The equivalent of JavaScript's indexOf."""
return next((i for i, item in enumerate(items) if item == value), default)
print("index_or(fruits, 'pear') ->", index_or(fruits, "pear"))
print("index_or(fruits, 'kiwi') ->", index_or(fruits, "kiwi"), " no exception")
print("index_or(fruits, 'kiwi', None) ->", index_or(fruits, "kiwi", None))
print()
print("or handle the exception where it happens:")
try:
position = fruits.index("kiwi")
except ValueError:
position = -1
print(" position ->", position)
print()
print("checking first also works, but scans the list twice:")
position = fruits.index("pear") if "pear" in fruits else -1
print(" position ->", position)
Output:
index_or(fruits, 'pear') -> 1
index_or(fruits, 'kiwi') -> -1 no exception
index_or(fruits, 'kiwi', None) -> None
or handle the exception where it happens:
position -> -1
checking first also works, but scans the list twice:
position -> 1
The next(...) form is the closest thing to indexOf, and the default is yours to choose. Use None rather than -1 if there is any chance the result gets used as an index later.
Wrapping the call in try is equally valid and reads well when a miss is genuinely unexpected. Checking with in first works too, but walks the list twice. Checking whether a list contains a value covers that test on its own.
Find all indexes of an element in a list
.index() stops at the first match, so repeats need a comprehension:
fruits = ["apple", "pear", "plum", "pear", "pear"]
print("the list ->", fruits)
print("fruits.index('pear') ->", fruits.index("pear"), " first only")
print()
positions = [i for i, fruit in enumerate(fruits) if fruit == "pear"]
print("every position ->", positions)
print("how many ->", len(positions))
print("last one ->", positions[-1])
print()
print("the same idea with a condition instead of a value:")
numbers = [4, 9, 2, 9, 1]
print(" numbers ->", numbers)
print(" positions over 3 ->", [i for i, n in enumerate(numbers) if n > 3])
Output:
the list -> ['apple', 'pear', 'plum', 'pear', 'pear']
fruits.index('pear') -> 1 first only
every position -> [1, 3, 4]
how many -> 3
last one -> 4
the same idea with a condition instead of a value:
numbers -> [4, 9, 2, 9, 1]
positions over 3 -> [0, 1, 3]
enumerate() returns every matching position.The same pattern takes a condition instead of a value, which is how you find every position above a threshold or every string starting with a letter.
It returns an empty list when nothing matches, so there is no exception to handle. Looping with an index explains the enumerate() part.
Searching from a given position
.index() accepts start and end arguments:
fruits = ["apple", "pear", "plum", "pear"]
print("searching from a position onwards:")
print(" fruits.index('pear') ->", fruits.index("pear"))
print(" fruits.index('pear', 2) ->", fruits.index("pear", 2), " skips the first one")
print()
print("with an end position too, which is exclusive:")
print(" fruits.index('pear', 0, 2) ->", fruits.index("pear", 0, 2))
try:
fruits.index("pear", 2, 3)
except ValueError as err:
print(" fruits.index('pear', 2, 3) -> ValueError:", err)
print()
print("this is how you walk through repeats one at a time,")
print("though the comprehension above is usually clearer.")
Output:
searching from a position onwards:
fruits.index('pear') -> 1
fruits.index('pear', 2) -> 3 skips the first one
with an end position too, which is exclusive:
fruits.index('pear', 0, 2) -> 1
fruits.index('pear', 2, 3) -> ValueError: 'pear' is not in list
this is how you walk through repeats one at a time,
though the comprehension above is usually clearer.
The end position is exclusive, matching slicing. Calling it repeatedly with a moving start is the classic way to step through repeats, though the comprehension above is easier to read.
index() compares with equality, not identity
This produces results that look wrong until you see why:
mixed = [1.0, 1, True]
print("the list ->", mixed)
print("mixed.index(1) ->", mixed.index(1))
print()
print("1.0 == 1 is", 1.0 == 1, "so the float matched first.")
print(".index() compares with == , not with 'is the same object'.")
print()
print("True is even stranger:")
print(" True == 1 ->", True == 1)
print(" [True].index(1) ->", [True].index(1))
print()
print("case matters for strings:")
names = ["Ann", "bob"]
try:
names.index("ann")
except ValueError as err:
print(" names.index('ann') -> ValueError:", err)
print(" case insensitive ->",
next((i for i, n in enumerate(names) if n.lower() == "ann"), None))
Output:
the list -> [1.0, 1, True]
mixed.index(1) -> 0
1.0 == 1 is True so the float matched first.
.index() compares with == , not with 'is the same object'.
True is even stranger:
True == 1 -> True
[True].index(1) -> 0
case matters for strings:
names.index('ann') -> ValueError: 'ann' is not in list
case insensitive -> 0
.index() uses ==, so 1.0 matches 1.1.0 == 1 is true in Python, and True == 1 is as well, so a list holding a mix of those types can match somewhere you did not expect.
String comparison is case sensitive. To ignore case, filter with enumerate and compare lowercased values.
Index of the largest or smallest value
Combine .index() with max() or min():
scores = [4, 9, 2, 9, 1]
print("scores ->", scores)
print()
print("position of the highest value:")
print(" scores.index(max(scores)) ->", scores.index(max(scores)))
print("position of the lowest:")
print(" scores.index(min(scores)) ->", scores.index(min(scores)))
print()
print("the LAST highest, when there are ties:")
last = len(scores) - 1 - scores[::-1].index(max(scores))
print(" ->", last)
print()
print("and every position that ties for the highest:")
best = max(scores)
print(" ->", [i for i, s in enumerate(scores) if s == best])
Output:
scores -> [4, 9, 2, 9, 1]
position of the highest value:
scores.index(max(scores)) -> 1
position of the lowest:
scores.index(min(scores)) -> 4
the LAST highest, when there are ties:
-> 3
and every position that ties for the highest:
-> [1, 3]
Ties go to the first occurrence, as always. Reversing the list and adjusting the arithmetic gives the last one instead, and a comprehension gives every position that ties.
Finding an item in a list of dictionaries
.index() needs the whole value, which rarely helps for records:
people = [{"name": "ann", "age": 30},
{"name": "bob", "age": 25}]
print("lists of dictionaries need a condition, not a value:")
position = next((i for i, p in enumerate(people) if p["name"] == "bob"), None)
print(" first person named bob ->", position)
print()
print("lists of tuples can use .index() directly, if you have the whole tuple:")
pairs = [("a", 1), ("b", 2)]
print(" pairs.index(('b', 2)) ->", pairs.index(("b", 2)))
print()
print("nested lists work the same way:")
grid = [[1, 2], [3, 4]]
print(" grid.index([3, 4]) ->", grid.index([3, 4]))
Output:
lists of dictionaries need a condition, not a value:
first person named bob -> 1
lists of tuples can use .index() directly, if you have the whole tuple:
pairs.index(('b', 2)) -> 1
nested lists work the same way:
grid.index([3, 4]) -> 1
Searching by one field means a condition, so next(...) over enumerate is the tool again. Tuples and nested lists can use .index() directly, because you can write the value out in full.
Getting the index in a NumPy array
NumPy arrays have no .index() at all:
import numpy as np
arr = np.array([4, 9, 2, 9, 1])
print("array ->", arr)
print()
print("NumPy arrays have no .index() method. Use np.where:")
print(" np.where(arr == 9)[0] ->", np.where(arr == 9)[0])
print(" as a plain list ->", np.where(arr == 9)[0].tolist())
print()
print("the first match only:")
print(" np.where(arr == 9)[0][0] ->", np.where(arr == 9)[0][0])
print()
print("and the positions of the largest and smallest:")
print(" arr.argmax() ->", arr.argmax())
print(" arr.argmin() ->", arr.argmin())
print()
print("np.where returns every match in one pass, which beats looping.")
Output:
array -> [4 9 2 9 1]
NumPy arrays have no .index() method. Use np.where:
np.where(arr == 9)[0] -> [1 3]
as a plain list -> [1, 3]
the first match only:
np.where(arr == 9)[0][0] -> 1
and the positions of the largest and smallest:
arr.argmax() -> 1
arr.argmin() -> 4
np.where returns every match in one pass, which beats looping.
np.where() returns every matching position in one pass, so there is no first-match limitation to work around. Take [0][0] if you only want the first.
argmax() and argmin() give the positions of the extremes directly. Using np.where goes into the conditional forms.
Which method should you use?
| Situation | Use |
|---|---|
| A value you know is present | items.index(value) |
| It might be missing | next((i for i, v in enumerate(items) if v == value), -1) |
| Every occurrence | [i for i, v in enumerate(items) if v == value] |
| A condition rather than a value | next((i for i, v in enumerate(items) if v > 10), None) |
| The largest or smallest | items.index(max(items)) |
| A NumPy array | np.where(arr == value)[0] |
| A substring in a string | text.find(sub), which returns -1 |
More list guides worth reading:
- Loop over a list with its index
- Check if a list contains a value
- What for i in range means
- Split a list in Python
- Reverse a list in Python
- NumPy where
Frequently asked questions
How do I get the index of an element in a Python list?
Call my_list.index(value). It returns the position of the first match, counting from 0, and raises ValueError if the value is not present.
Does Python have an indexOf method?
No. Lists have .index(), which raises instead of returning -1. Strings have .find(), which does return -1 and is the closest equivalent.
How do I find the index without getting a ValueError?
Use next((i for i, v in enumerate(items) if v == value), -1), or wrap .index() in a try block. Both let you choose what a miss looks like.
How do I find all indexes of an element in a list?
[i for i, v in enumerate(items) if v == value]. It returns every matching position, and an empty list when there are none.
Why does index() only return the first match?
Because it stops as soon as it finds one. Use the comprehension above when a value repeats and you need every position.
How do I get the index of the maximum value?
items.index(max(items)) for a list, or arr.argmax() for a NumPy array. Both report the first position when several values tie.
Why did index() match 1 when my list holds 1.0?
It compares with ==, and 1.0 == 1 is true in Python. The behaviour is set out in the Python list methods documentation.
Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn