find() vs index() vs `in` — which one?

They do the same search and differ only in how they report a miss. find returns -1, index raises ValueError, and in returns a bool. Pick by whether a miss is expected (use find or in) or a bug (use index).

Overview

The difference

Both locate a substring. They differ only in what happens when it is absent.

1Python
Output

find returns −1; index raises ValueError. That is the whole distinction, and the interesting question is which to use when.

 findindex
Not foundReturns −1Raises ValueError
Available on strYesYes
Available on listNoYes
Reverse variantrfindrindex
Best whenAbsence is expectedAbsence is a bug

The list row surprises people: lists have index but no find. So [1, 2, 3].find(4) is an AttributeError, and testing membership on a list means 4 in lst first, or catching ValueError.

StringsConceptualEasy

Step through it

What to watch

  • All three scan the same way — the difference is only at the end.
  • -1 is a valid index in Python, which is why find has a sharp edge.
  • if s.find(x): is a real bug — index 0 is falsy.

Say this out loud

"Same search, different failure. find gives -1, index raises, `in` gives a bool. If missing is normal I use find; if missing means something upstream is broken I use index so it fails loudly."

find() vs index() vs `in` — which one?

What is the difference between str.find and str.index, and when would you use each?

Run it

The three calls on a hit and on a miss, then the two bugs find invites — including the falsy-zero one, shown giving a confidently wrong answer.

2Python
Output

Which one to use

The rule follows from how the two report failure:

Use find when absence is a normal outcome you plan to branch on.

3Python
Output

Use index when absence means something is wrong and should not be silently absorbed.

4Python
Output

The failure mode of choosing wrongly is specific and worth naming: −1 is a valid index in Python. Forgetting the check does not crash — it silently reads from the end of the string.

5Python
Output

A missing check with index raises immediately, at the line that caused it. With find, the wrong answer propagates. That is why index is the safer default when a match is genuinely expected, and it is the opposite of most people's instinct.

The optional start and end arguments

Both accept a search region, and using it avoids slicing:

6Python
Output

s.find(sub, i) searches in place; s[i:].find(sub) copies the remainder first and returns an offset relative to the slice, which then needs + i to be meaningful. The in-place form is both faster and less error-prone — and iterating occurrences is the case where it matters:

7Python
Output

Advancing by 1 finds overlapping matches; advancing by len(sub) finds only non-overlapping ones. "aaa".find("aa") occurring at both 0 and 1 is the case that distinguishes them, and which behaviour is wanted should be stated rather than assumed.

The same search, three reports

find returns the index of the first occurrence, or -1. index returns the same index, or raises ValueError. in answers only yes or no, and reads better when that is all you need.

All three are the same O(n·m) scan underneath, so this is not a performance choice. There are rfind and rindex for the last occurrence, and all of them take optional start and end bounds — which is how you find the second occurrence without slicing.

The trap in find's return value

-1 is a perfectly valid index in Python, so a find result used without checking silently indexes from the end. Worse:

if s.find("a"): is False when the match is at index 0 — the one case you were most likely to want. The check has to be if s.find("a") != -1:, which is exactly why in exists.

How to choose

Use in when you only need to know whether it is there. Use find when a miss is a normal outcome you will branch on. Use index when a miss means something upstream is already broken and you would rather have a traceback at the real cause than a -1 propagating three functions away.

That last point is the answer interviewers are listening for: it is a question about error handling wearing a string-methods costume.

The other ways to ask the same question

QuestionBest tool
Is it present?sub in s — clearest and fastest
Where is it?find or index
Does it start with this?startswith — also accepts a tuple
Does it end with this?endswith
How many times?count
All positions?Loop with find(sub, pos), or re.finditer
Split around it?partition or split

in rather than find(...) != -1 when only presence matters. It reads better and avoids the comparison entirely.

str.partition is the underused one. It splits at the first occurrence and returns three parts, so no index arithmetic is needed:

8Python
Output

Checking if sep: is a clean way to distinguish found from not-found, and the split is done in the same call. rpartition splits at the last occurrence, which is how you separate a file extension.

What the complexity actually is

find is not a naive scan. CPython uses a hybrid of Crochemore-Perrin ("two-way") and Boyer-Moore-Horspool, with a simpler loop for short needles.

CaseCost
Typical text~O(n), often sublinear via skipping
Worst caseO(n · m)
Single characterO(n), heavily optimised
Not foundFull scan — O(n)

The practical implication is that a hand-written Python loop will not beat it. Even implementing KMP in pure Python is slower than calling find, because the C implementation's constant factor is far smaller. Write the loop in an interview to demonstrate understanding; call find in real code.

Questions people ask

Why does list have index but not find? A historical asymmetry. Use in first, or catch ValueError.

Is in faster than find? Effectively the same algorithm; in avoids constructing the index and is marginally quicker.

Do bytes have both? Yes, with the same semantics — and the argument must be bytes, not str.

What does find("") return? 0. The empty string is found at every position, including in an empty string.

How do I find all overlapping occurrences? Loop with find(sub, pos) and pos += 1. re.finditer skips overlaps unless you use a lookahead pattern.

Which is more Pythonic? "Ask for forgiveness, not permission" favours index with a try; branching on a value favours find. Both are idiomatic — matching the choice to whether absence is exceptional is the point.

Recap in one screen

  • find returns −1 when absent; index raises ValueError. Nothing else differs.
  • −1 is a valid Python index, so an unchecked find silently reads the last character instead of failing.
  • Use index when a match is required and find when absence is an expected branch.
  • Pass start and end rather than slicing — slicing copies and shifts the returned offset.
  • Prefer in for presence, startswith/endswith for edges, and partition to split without index arithmetic.

How the code works

The three calls on a hit and on a miss, then the two bugs find invites — including the falsy-zero one, shown giving a confidently wrong answer.

How the code works

  1. s.find("zzz") -> -1A sentinel, not an error. Convenient when a miss is expected, and dangerous the moment the result is used without being checked.
  2. s[i] with i = -1Silently the last character. Python's negative indexing means a forgotten check does not crash — it returns something plausible, which is worse.
  3. if s.find(needle):False when the match is at index 0. The table shows it getting the first case wrong; the test has to be != -1.
  4. text.find("at", at + 1)The start bound walks through every occurrence without slicing, which would copy the remainder on each step.

Change one thing

  • Swap find for index in the loop and let it run off the end. The ValueError is the loop condition you forgot to write.
  • Use rfind to walk backwards. Same bounds, same sentinel, opposite direction.

Where this runs

Real CPython, compiled to WebAssembly and running on your own machine — nothing is uploaded. The first run takes a few seconds while the interpreter downloads; after that it is immediate. Need more room, or want to paste your own attempt? Use the Python compiler.

Check yourself

0 of 3

Answer without scrolling back up.

  1. s.index('zzz') when 'zzz' is absent:

  2. Why is `if s.find(x):` a bug?

  3. To find the second occurrence of a substring, the cheapest approach is:

Cheat sheet

find() vs index() vs `in` — which one?

They do the same search and differ only in how they report a miss. find returns -1, index raises ValueError, and in returns a bool. Pick by whether a miss is expected (use find or in) or a bug (use index).

INTERVIEW · vizlearn.in/interview/find-versus-index-on-strings.html

About the author

Ashish Jangra builds and maintains VizLearn. Every module here is written and the visualisation behind it hand-built, so the numbers in a readout come from the same code that draws the picture. Corrections are genuinely welcome and get priority over everything else — if a page states something wrong, or an animation misrepresents what the algorithm does, get in touch.