Algorithm

Find the Index of the First Occurrence in a String

Arrays & Strings Pattern

First Occurrence in String

Given two strings haystack and needle, return the index of the first (leftmost) position at which needle appears inside haystack as a contiguous substring, or -1 if needle never appears. The matched characters must be adjacent and in order; the returned index is the starting position of that match.

CONSTRAINTS
  • 1 <= haystack.length, needle.length <= 10⁴
  • haystack and needle consist of lowercase English letters only
EXAMPLE 1
Input: haystack = "sadbutsad", needle = "sad"
Output: 0
"sad" occurs starting at index 0 and again at index 6. We return the first (leftmost) start, index 0.
EXAMPLE 2
Input: haystack = "aaabaaa", needle = "aab"
Output: 1
The block starting at index 0 is "aaa", which is not "aab". The block starting at index 1 is "aab" — a match — so the answer is 1.
EXAMPLE 3
Input: haystack = "leetcode", needle = "leeto"
Output: -1
No contiguous block of "leetcode" equals "leeto" (the 'o' never follows "leet"), so there is no occurrence and we return -1.
Must the match be a contiguous substring, or can the letters have gaps?
Contiguous — the needle's characters must appear adjacent and in order. If gaps were allowed it would be a subsequence question instead (that is the Is Subsequence problem), which a simple one-directional scan solves.
Is the comparison case-sensitive, and which characters can appear?
The inputs here are lowercase English letters only, so case never comes up. If mixed case or other characters were allowed, confirm the matching rules with the interviewer.
If needle appears multiple times, which index do I return?
The first (smallest) starting index. Because we scan left to right and return on the first match, later occurrences are never even examined.
What should I return if needle is empty?
The constraints guarantee needle has length at least 1, so it does not arise here. By common convention (e.g. Java's indexOf) an empty needle matches at the start and returns 0.

Given a big string (the haystack) and a small one (the needle), return the index of the first place the needle appears inside the haystack as a contiguous block — same letters, adjacent, in order — or -1 if it never appears. Two words in that sentence carry weight. "Contiguous" separates this from a subsequence search like Is Subsequence, where gaps were allowed; here the needle's characters must sit shoulder to shoulder. And "first" means the smallest starting index, so we search left to right and stop the instant we succeed.

Try every landing spot for the needle

The needle, if it appears at all, must start somewhere — and it cannot start so late that it hangs off the end of the haystack. So the only candidate starting positions run from 0 up to len(haystack) - len(needle). Walk those positions left to right; at each one, line the needle up against that slice of the haystack and compare character by character. The first slice that matches, return its index; if none do, return -1.

python
h_len, n_len = len(haystack), len(needle)
for i in range(h_len - n_len + 1):         # every spot the needle could fit
    if haystack[i : i + n_len] == needle:  # compare the aligned block
        return i
return -1

Now count the work. There are about H - N + 1 starting positions, and confirming a match at each compares up to N characters. In friendly input the first mismatch at each spot ends the comparison early, so it feels fast. Adversarial input erases that: haystack "aaaa…a" (H copies of 'a') with needle "aaa…ab" (N-1 a's then a b). At every starting position the first N-1 characters match and only the last one fails — so every spot pays nearly the full N comparisons, and the total climbs to about H × N. That worst case, O(H × N), is worth seeing exactly where it comes from.

The waste: discarding what a failed match just proved

Look at what that pathological run does. At position 0 it compares N-1 a's, confirms they match, hits the final mismatch — and then slides forward by exactly one and re-compares almost the same a's all over again. Every restart throws away everything the previous comparison established about characters it already read. A person searching would never do this: having just read a long run of a's, you would not re-read it from scratch one step over. The waste is re-scanning haystack characters we have already looked at.

Letting the needle say how far to jump: KMP

The Knuth-Morris-Pratt algorithm removes that waste, and its key realization is that the needle itself can tell you, in advance, how far it is safe to jump after a mismatch. Suppose we have matched some prefix of the needle and then a character fails. Those matched characters are known — they are literally the start of the needle. If that matched prefix contains a smaller piece that is both its own prefix and its suffix (in "ababc", after matching "abab" the piece "ab" is such an overlap), then that overlap is already correctly aligned at the next position, so we resume comparing from there instead of restarting — and the haystack pointer never moves backward. Precomputing those safe-jump distances from the needle alone is O(N); the haystack scan is then O(H) because each haystack character is examined a constant number of times. Total: O(H + N). Building the jump table in full is a study in itself; the load-bearing idea to carry away is that the needle's own internal repetition is what lets the search avoid re-reading the haystack.

Worked Example:haystack = "sadbutsad", needle = "but"
Candidate start positions run from 0 to 9 - 3 = 6. Checking the aligned 3-character block at each:
- i = 0: "sad" vs "but" — 's' ≠ 'b', mismatch immediately.
- i = 1: "adb" vs "but" — 'a' ≠ 'b', mismatch.
- i = 2: "dbu" vs "but" — 'd' ≠ 'b', mismatch.
- i = 3: "but" vs "but" — all three match. Return 3.
Positions 4 through 6 are never examined, because "first occurrence" lets us stop the moment we succeed.
The takeaway

For this problem's limits (strings up to 10⁴), the plain sliding compare is entirely acceptable and is the expected answer — its worst case is rare and the code is short and obviously correct. Reach for KMP only when the input can be adversarial or huge, or when the interviewer explicitly asks for linear time. The broader reflex is the durable part: whenever a brute-force scan keeps re-reading characters it has already seen after a partial success, ask what structure in the data (here, the needle's repeated prefixes) would let you skip the re-read. That one question is the seed of KMP, the Z-algorithm, and Rabin-Karp alike. Note too the sharp contrast with Is Subsequence: there the needle could spread out with gaps, so a single greedy forward scan sufficed; here the demand for a contiguous block is exactly what forces the backtracking that KMP works to eliminate.

Interactive Strategy Visualization

sliding window

Pattern Matching Scan
S0
A1
D2
B3
U4
T5
S6
A7
D8
B
U
T
📡

Scanning index 0... Comparing "SAD" with "BUT".

O(H × N) Sliding Compare
O(H + N) KMP · No Re-scan