# Text.IndexOf

> The C# string.IndexOf: the index of the FIRST ordinal occurrence of a substring, or -1 if absent. The 3-arg overload resumes the forward search at startIndex, which is how you walk a string one match at a time. Runs in memory only — there is no SQL push-down form.

<!-- id: function-text-indexof · area: function · stability: stable · html: https://osysharp.com/reference/function/text-indexof/ -->

## Summary        {#summary}
`Text.IndexOf(s, sub)` returns the zero-based index of the **first** ordinal occurrence of `sub` in `s`,
or `-1` when `sub` does not occur — exactly C#'s `string.IndexOf`. The 3-arg overload
`Text.IndexOf(s, sub, startIndex)` begins the search **at** `startIndex` and runs forward, matching
C#'s `IndexOf(value, startIndex)`.

## Signature      {#signature}
```osy syntax
Text.IndexOf(<string> s, <string> sub) -> int
Text.IndexOf(<string> s, <string> sub, <int> startIndex) -> int
```

## Description    {#description}
Matching is **ordinal** (byte-for-byte, culture-independent), the same as [Contains, StartsWith, EndsWith](https://osysharp.com/reference/function/string-search/)'s
`Contains`. A miss returns `-1`. `Text.LastIndexOf` ([Text.LastIndexOf](https://osysharp.com/reference/function/text-lastindexof/)) is the backward-search
sibling.

The 3-arg `startIndex` is the C# contract: it is the *first* position considered, and the search runs
forward from it. `startIndex` may run from `0` to the string's length **inclusive** — a start exactly at
the end is legal and simply finds nothing — and anything outside that range faults rather than clamping.

⚠ **Dropping the `startIndex` is not a simplification.** The whole point of the overload is "find the next
one after the one I already found", so a search that restarts at `0` answers a position the caller has
already passed — and code that then compares that position against where it was looking takes the wrong
branch on every input, silently.

`Text.IndexOf` is **in-memory only** — like `Text.Reverse`, it has no faithful SQL form, so it may be
called on locals inside a function body but not pushed down into a query predicate.

## Examples       {#examples}
```osy title="walking a string one match at a time" test app=text-indexof
string SecondField(string line) {
  var first = Text.IndexOf(line, ",");
  if (first < 0) {
    return "";
  }
  var second = Text.IndexOf(line, ",", first + 1);   // resume AFTER the one just found
  if (second < 0) {
    return Text.Substring(line, first + 1);
  }
  return Text.Substring(line, first + 1, second - first - 1);
}
// SecondField("a,b,c")  ->  "b"
```

## See also       {#see-also}
- [Text.LastIndexOf](https://osysharp.com/reference/function/text-lastindexof/) — the backward-search sibling, with the same `startIndex` contract
- [Contains, StartsWith, EndsWith](https://osysharp.com/reference/function/string-search/) — `Contains` / `StartsWith` / `EndsWith`, when the POSITION is not needed
