How do I find the first match and stop searching without break?

← Control Flow Without break/continue/return · Ref: Q284

For simple first-match, use filter + head 1 in a stream pipeline. For complex search with transformation or context, use a function with unset return and a for loop.

SIMPLE FIRST MATCH: FILTER + HEAD

The basic pattern for finding the first match:

  first <- cat items | filter by predicate | head 1 | collect as List of String

This stops processing after the first match. No break needed.

PATTERN 1: SEARCH WITH TRANSFORMATION

Find the first match and return a TRANSFORMED result, not the match itself:

  findAndTransform()
    -> items as List of String
    <- result as String: String()
    for item in items
      if length item > 5 and ~result?
        result :=? item + " (long)"

The :=? ensures only the first long item gets transformed and assigned.

PATTERN 2: SEARCH WITH INDEX

Find an item AND its position:

  findWithIndex()
    -> items as List of String, target as String
    <- position as Integer: Integer()
    idx <- 0
    for item in items
      if item == target and ~position?
        position :=? idx
      idx: idx + 1

The caller gets the index of the first occurrence, or an unset Integer if not found.

PATTERN 3: MULTI-FIELD SEARCH

Search objects by multiple criteria using a helper predicate:

  isMatch() as pure
    -> item as String
    <- matched as Boolean: item contains "admin" and length item > 7
  found <- cat users | filter by isMatch | head 1 | collect as List of String

KEY INSIGHT

Stream filter + head for simple cases. Function decomposition with :=? for complex ones where you need transformation, context, or multi-step logic.

See Q145 for basic filter + head. See Q125 for head, tail, and skip. See Q89 for stream pipelines. See Q50 for unset return as signal.

Example

defines module qa.without.findmatch

  defines function

    isLong() as pure
      -> item as String
      <- long as Boolean?
      minLongLength <- 5
      long: length item > minLongLength

    findAndTransform()
      -> items as List of String
      <- result as String: String()
      minLongLength <- 5
      for item in items
        if length item > minLongLength and ~result?
          result :=? item + " (long)"

    findWithIndex()
      ->
        items as List of String
        target as String
      <- position as Integer: Integer()
      idx <- 0
      for item in items
        if item == target and ~position?
          position :=? idx
        idx: idx + 1

    containsAdmin() as pure
      -> item as String
      <- matched as Boolean?
      minAdminLength <- 7
      matched: item contains "admin" and length item > minAdminLength

  defines program

    FindFirstDemo()
      stdout <- Stdout()

      items <- ["hi", "apple", "banana", "cherry", "date", "elderberry"]

      // === SIMPLE: FILTER + HEAD ===

      firstLong <- cat items | filter by isLong | head 1 | collect as List of String
      stdout.println(`First long item: ${firstLong}`)

      // === SEARCH WITH TRANSFORMATION ===

      transformed <- findAndTransform(items)
      if transformed?
        stdout.println(`Transformed: ${transformed}`)

      // === SEARCH WITH INDEX ===

      pos <- findWithIndex(items, "cherry")
      if pos?
        stdout.println(`Cherry found at index: ${pos}`)

      notFoundPos <- findWithIndex(items, "mango")
      if ~notFoundPos?
        stdout.println("Mango not found in list")

      // === MULTI-FIELD SEARCH ===

      users <- ["user_joe", "admin_alice", "user_bob", "admin_charlie"]
      admins <- cat users | filter by containsAdmin | head 1 | collect as List of String
      stdout.println(`First admin: ${admins}`)

Common mistakes

E01070 — EK9 has no break statement. To find the first match, use guard assignment (:=?) which only assigns when the variable is unset, replacing break-on-found. See ek9 -h E01070 for details.

Incorrect:

break

Correct:

position :=? idx
Other ways to ask this
  • How do I do early termination search without break in EK9?
  • How do I return the first matching item from a list in EK9?
  • How do I search with transformation without break in EK9?

Coming from another language?

Java: for (item : list) { if (match) { result = item; break; } } or stream().filter().findFirst(). Python: for item in list: if match: result = item; break, or next(x for x in list if match, None). Rust: iter().find(|x| predicate). Go: for _, item := range list { if match { return item } }. Kotlin: list.firstOrNull { predicate }. EK9: cat items | filter by predicate | head 1 | collect, or function with :=? for complex search.

Keywords: termination, linear, match, search, transform, alternative, first, index, no-break, filter, find, lookup, no-return, head, stop, early