When should I use a stream pipeline vs a loop in EK9?

← Streams and Pipelines · Ref: Q237

EK9 provides both stream pipelines and loops. Use streams for filtering, transforming, limiting, and chaining operations. Use loops for side effects and condition-based iteration.

USE STREAMS WHEN

Filtering (filter by), transforming (map with), limiting (head N as early exit), deduplication (sort | uniq), chaining multiple operations, or grouping (group by).

USE FOR-IN LOOPS WHEN

Side effects on every item (printing, saving). Simple accumulation where a loop is more readable.

USE FOR-IN EXPRESSIONS WHEN

Complex multi-variable accumulation or fold/reduce logic. See Q82.

USE WHILE LOOPS WHEN

Condition-based iteration (polling, retrying, external APIs). See Q66.

DECISION SUMMARY

  Filter or limit?           Stream (filter by, head)
  Transform each item?       Stream (map with)
  Early exit?                Stream (head N) — See Q125
  Side effects on all items? For-in loop
  Complex accumulation?      For-in expression or collect as
  Condition-based?           While loop

See Q51 for abstract functions in pipelines. See Q52 for dynamic functions. See Q54 for Predicate/Comparator types. See Q64-Q66 for loop types. See Q80/Q82 for loop expressions. See Q89 for stream basics. See Q122 for collect as. See Q125 for head as early exit. See Q145 for replacing break/continue. See Q235 for stream operations reference. See Q261 for idiomatic patterns.

Example

defines module qa.streams.vsloops

  defines function

    isEven() as pure
      -> num as Integer
      <- rtn as Boolean: num mod 2 == 0

    doubleIt() as pure
      -> num as Integer
      <- rtn as Integer: num * 2

    intToString() as pure
      -> num as Integer
      <- rtn as String: $num

  defines program

    StreamsVsLoopsDemo()
      stdout <- Stdout()

      numbers <- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

      // === STREAM: Filter + Transform + Limit ===

      topThreeDoubledEvens <- cat numbers
        | filter by isEven
        | map with doubleIt
        | head 3
        | collect as List of Integer
      stdout.println(`Stream (filter+map+head): ${topThreeDoubledEvens}`)

      // === STREAM: Sum with collect as Integer ===

      total <- cat numbers | collect as Integer
      stdout.println(`Stream sum: ${total}`)

      // === FOR-IN LOOP: Side effects ===

      stdout.println("Loop (side effects):")
      for num in numbers
        stdout.println(`  Processing ${num}`)

      // === FOR-IN EXPRESSION: Accumulation ===

      sum <- for n in numbers
        <- rtn <- 0
        rtn: rtn + n
      stdout.println(`Loop expression sum: ${sum}`)

      // === FOR-IN EXPRESSION: Complex accumulation ===

      evenCount <- for n in numbers
        <- rtn <- 0
        if n mod 2 == 0
          rtn: rtn + 1
      stdout.println(`Even count via loop: ${evenCount}`)

      // === STREAM: Same result as above ===

      evenList <- cat numbers | filter by isEven | collect as List of Integer
      stdout.println(`Even count via stream: ${length evenList}`)

Common mistakes

E07520 — A predicate function used with 'filter by' must return Boolean. Returning Integer (e.g., the modulo result) instead of Boolean triggers E07520. See ek9 -h E07520 for details.

Incorrect:

<- rtn as Integer: num mod 2

Correct:

<- rtn as Boolean: num mod 2 == 0
Other ways to ask this
  • What is the difference between a stream and a loop in EK9?
  • Should I use cat pipe collect or a for loop in EK9?
  • When are streams better than loops in EK9?
  • How do I choose between a stream pipeline and a for-in loop in EK9?

Coming from another language?

Java/Kotlin: Streams vs for-each, similar trade-offs but EK9 head replaces break. Python: list comprehensions vs for-loops. Go: only for-loops. EK9: cat|filter|map|collect for chains, for-in for side effects, for-in expression for accumulation, head replaces break.

Keywords: pipe, collect, while, vs, head, when, stream, transform, for, versus, choose, accumulate, loop, filter, effect, comparison, side, guide, decide