Skip to content

28. Find the Index of the First Occurrence in a String (Easy)

Problem

Given two strings haystack and needle, return the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack.

Example

  • haystack = "sadbutsad", needle = "sad"0
  • haystack = "leetcode", needle = "leeto"-1
  • haystack = "hello", needle = "ll"2

LeetCode 28 · Link · Easy

Try it yourself

idle

Starter code: this editor begins with intentional TODOs. Fill the function, run the embedded tests, then compare your solution with the worked approaches below.

Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Approach 1: Brute force sliding window

Slide a window of length len(needle) one character at a time over haystack. At each position, compare the window against needle. Return the first position where they match.

def str_str(haystack: str, needle: str) -> int:
n, m = len(haystack), len(needle) # L1: O(1)
for i in range(n - m + 1): # L2: loop, n-m+1 iterations
if haystack[i:i + m] == needle: # L3: slice + compare, O(m) each
return i # L4: O(1) return
return -1 # L5: O(1)

Where the time goes, line by line

Variables: n = len(haystack), m = len(needle).

LinePer-call costTimes executedContribution
L1 (lengths)O(1)O(1)1O(1)O(1)
L2 (loop)O(1)O(1)n - m + 1O(n)O(n)
L3 (slice + compare)O(m)O(m)up to n - m + 1O(nm)O(n * m)
L4 (return)O(1)O(1)1O(1)O(1)
L5 (return -1)O(1)O(1)1O(1)O(1)

The slice haystack[i:i+m] allocates a new string of length m and the == comparison scans up to m characters. Done at each of the ~n positions, that is O(nm)O(n * m) total.

Complexity

  • Time: O(nm)O(n * m), driven by L3 (window comparison at every position).
  • Space: O(m)O(m) for the slice allocated at each step.

Approach 2: Built-in (interview-acceptable)

Python’s str.find() and JavaScript’s String.prototype.indexOf() use optimized algorithms under the hood. In an interview, returning the built-in result is explicitly acceptable for this problem.

def str_str(haystack: str, needle: str) -> int:
return haystack.find(needle)

This is O(n)O(n) in the average case and O(nm)O(n * m) in the worst case, but the constant factor is far lower than a hand-rolled loop because the inner comparison runs in native code.

Try this approach:

idle
Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Note on KMP

The Knuth-Morris-Pratt algorithm solves this in O(n+m)O(n + m) time and O(m)O(m) space by precomputing a failure function over needle that allows the search to skip redundant comparisons. See KMP for the full walkthrough.

Summary

ApproachTimeSpace
Brute sliding windowO(nm)O(n * m)O(m)O(m)
Built-inO(n)O(n) avgO(1)O(1)
KMPO(n+m)O(n + m)O(m)O(m)

The brute approach is the clearest to reason about and fine for small inputs. Use the built-in in interviews unless the interviewer specifically asks for the algorithm. KMP is the canonical O(n)O(n) solution.

Test cases

# Quick smoke tests, paste into a REPL or save as test_028.py and run.
# Uses the brute sliding window implementation.
def str_str(haystack: str, needle: str) -> int:
n, m = len(haystack), len(needle)
for i in range(n - m + 1):
if haystack[i:i + m] == needle:
return i
return -1
def _run_tests():
assert str_str("sadbutsad", "sad") == 0
assert str_str("leetcode", "leeto") == -1
assert str_str("hello", "ll") == 2
assert str_str("a", "a") == 0
assert str_str("mississippi", "issip") == 4
assert str_str("abc", "") == 0 # empty needle
assert str_str("", "a") == -1 # empty haystack
print("all tests pass")
if __name__ == "__main__":
_run_tests()
  • Array Scans, the linear pass habit of carrying just enough state while reading each item once.
  • Two Pointers, the two index invariant that shrinks or coordinates positions without nested loops.