How to avoid procedural loops in this example?

As is demonstrated very well in this post you can use a criteria for your pattern, thereby only applying your function as long as you are searching and not to all elements. Also there is a specific FirstPosition function.

f[x_] := Module[{}, Pause[0.5]; 2 x]

AbsoluteTiming[
 Position[f /@ Range[10], 10, 1, 1]
]

AbsoluteTiming[
 FirstPosition[f /@ Range[10], 10]
]

{5.00824, {{5}}}

{5.01055, {5}}

AbsoluteTiming[
 Position[Range[10], _?(f[#] == 10 &), 1, 1]
]

AbsoluteTiming[
 FirstPosition[Range[10], _?(f[#] == 10 &)]
]

{3.00378, {{5}}}

{3.00329, {5}}


SelectFirst would work:

SelectFirst[Range[10], f[#] == 10 &]

You can tell that it only executes f on indexes 1-5 if you add a Print statement to the definition:

f[x_] := (Print[x]; 2 x)

1 + LengthWhile[Range@10, f@# != 10 &]

5