
What if you couldn’t program with loops? - helloiloveyou
http://www.mikealche.com/software-development/what-if-you-couldnt-program-with-loops
======
pcstl
While the article is a cute way to introduce the loop body extraction
refactoring, I think it is a disservice to talk about not using loops without
mentioning recursion.

I am quite often shocked at the number of programmers who, even after years of
experience, are not able to use recursion properly. (Not saying this is true
of the article's author - but they should have mentioned recursion)

~~~
BubRoss
Recursion is either an abstract way to create a loop or a way to use the call
stack as a stack data structure. There isn't really a good reason why it needs
to be part of a programmer's toolbox.

I think the clever nature of recursion is what has made it into something that
some programers think is a better way to traverse a data structure, but I
think it is often difficult to justify.

Even trees can be traversed with a stack of the current state. If something
goes wrong, checking the traversal stack is much easier than a runaway call
stack.

~~~
tombert
I completely disagree. For handling recursive data structures, recursion will
lead to shorter, simpler code than trying to hack something together with a
loop and a custom stack object. While I agree that some of the more dogmatic
FP people acting like recursion is the best thing ever might be a bit
overstated, I think it's short-sighted to say that "There isn't really a good
reason why it needs to be part of a programmer's toolbox".

Not to mention, without using recursion, there's no direct way of dealing with
purely immutable data structures. You're going to be stuck creating some kind
of mutable reference to serve as a counter to a loop, or to append to a list.

~~~
BubRoss
But what is a 'recursive' data structure? Recursive describes execution, not
data. Data in a tree is a heirarchy. Traversing a tree can be done with a
stack, since that's what recursion is doing in a less direct way.

Immutable also describes execution and is really just a way of hiding what is
really happening under the hood. There isn't anything that recursion enables.

~~~
pcstl
A recursive data structure is a data structure that contains a pointer to
another instance of itself. Linked lists, trees, etc.

~~~
Izkata
Since you listed linked lists first, here's a fun little tidbit from my early
days: I couldn't understand that until I first understood the recursive
structure of binary trees. Only afterwards did I have the realization "ohhh,
it's like a tree with only one child at each node" and suddenly understand the
recursive structure in linked lists.

------
dig1
In Scheme, the only way to loop is via recursion. In Clojure, all high level
functions and looping macros (for/doseq) are implemented via recursion as well
(loop/recur).

Yes, it requires significant paradigm shift when you start to use these
languages, but after a while, you start to appreciate high level functions
(map, reduce) instead of language looping constructs (for, while) - they can
be easily composed, parallelized, scaled over cluster, refactored... Even code
looks much cleaner.

You like it even more if you are mathematician, because things looks more
"natural" :D

~~~
smartstakestime
unless you actually start with these language , then move to loops. Which is
really easy. Map/reduce is fundamental to scheme like the closure is
fundamental to javascript.

Im not sure how many colleges teach it 1st year but Northeastern University
uses scheme as the intro language. The professor there wrote the blue book.

~~~
LandR
I know of zero universities that use Scheme or any LISP.

Most nowadays that I know of start with Javascript. My old university started
with C. It now goes straight to javascript.

~~~
brlewis
Actually I think you do know of more than zero universities, since you replied
to a comment that named Northeastern. A sibling comment to yours named
another. Here's another:
[https://cs.brown.edu/courses/info/csci0170/](https://cs.brown.edu/courses/info/csci0170/)

------
lincpa
In the pure function pipeline data flow, no loop is required.

I wrote a 100k lines of pure clojure language project, no loop is used, and
tail recursion is only used once. Most of them use high-order functions for
dataflow processing or data driving.

[The Pure Function Pipeline Data
Flow]([https://github.com/linpengcheng/PurefunctionPipelineDataflow](https://github.com/linpengcheng/PurefunctionPipelineDataflow))

~~~
fnord123
See also: [http://reactivex.io/](http://reactivex.io/)

~~~
lincpa
I read the documentation on the URL you provided, and the pure function
pipeline data flow is essentially different from Rx:

1\. The essential difference between programming methods is the inherent
thoughts and models. The idea and model of pure function pipeline data flow
are highly consistent with integrated circuits.

2\. The pure function pipeline data flow emphasizes the data flow, Rx
emphasizes the asynchronous event flow, and does not mention or emphasize the
data flow.

3\. The pure function pipeline data stream is composed of only 5 components,
and the model is much simpler than Rx.

4\. Pure function pipeline data flow emphasizes the sequential structure
(series of pipeline components), and the maintenance (code reading, expansion,
debugging, etc.) is simpler.

5\. The asynchronous event flow of the pure function pipeline data flow is
simpler than Rx. I wrote an asynchronous event flow in my project, it is just
a queue processing, so it is not necessary to specifically mention it.

6.The clojure language doesn't require Rx at all.

~~~
fnord123
Thanks for summarizing the differences. I think anyone interested in function
pipeline data flows will also be interested to know about Rx and RxClojure as
they both allow programs to be structured to work over a stream of data in
many other languages as is available in Rx.

------
sk5t
So, I was sort of expecting this article to use recursion instead of a regular
loop, but it was not to be, as the author went ahead and used a for-loop
anyway and abstracted a bit of logic into a separate function. Silly premise
overall.

~~~
deaddodo
I agree. The correct solution (given javascript as the language of choice) is
something along the following lines:

    
    
      let highlightProducts = (products, index = 0) => {
        if(index >= products.length) {
          return;
        }
        
        product = products[index];
        if(product.name.indexOf('mountain') !== -1) {
          products[index].highlighted = true;
        } else if(product.stock > 100 && product.price < 5) {
          products[index].highlighted = true;
        } else if(product.category == 'outdoors' && product.price < 35 && product.price > 20) {
          products[index].highlighted = true;
        }
        
        highlightProducts(products, index+1);
      }
    

Personally, I think this exercise is completely fruitless _except_ to teach
recursion.

~~~
dan-robertson
I was expecting the answer to look something like:

    
    
      products.forEach((p) =>
        p.highlighted = (
             /mountain/.test(p.name)
          || (p.stock > 100 && p.price < 5)
          || (p.category == 'outdoors' && 20 < p.price && p.price < 35)))
    

And if one wants the (implicitly hidden) behaviour that highlighted products
stay highlighted forever, just add the condition p.highlighted to the
disjunction above.

~~~
deaddodo
forEach is a loop, so this doesn't answer the basic question OP presented.

~~~
dan-robertson
The standard doesn’t specify whether or not forEach is a “loop” or not. One
could implement it using tail recursion. FWIW I would claim that such
recursion should also be called a loop, because the control flow goes in a
loop.

~~~
deaddodo
You're splitting hairs. I think we can both agree that no implementation does
it as such. And if you're going to go to that low of a level, there's no such
thing as a loop beyond emergent behavior (they're all conditional
branches/jumps).

------
whatshisface
Everyone in this comment section was expecting some kind of programming
language trickery, but that's not what this post is about. The author is
illustrating a refactoring technique.

~~~
Sahhaese
Half the people here are discussing the headline not the article because
people don't read articles, they read the headline and jump to the comments.

In this case it drowns out discussion of the article because the article is
terrible. It doesn't start with the code as it would look like with loops, but
instead says "Let's pretend you don't have loops" and then refactors the logic
which has nothing to do with loops, then puts the loop back in.

The same refactoring would be just as clear with loops, and in fact the
version without the refactor might be even more clear.

A static function which is only called from a single place isn't always more
clear than having the logic inside the loop, especially if all that is left
from the original function is the loop.

~~~
ssully
I think calling the article terrible is a little harsh. It's clearly meant for
people just starting with programming, and it utilizes a teaching technique
that can be very powerful. It isn't meant to teach you how to do things in the
best possible way, but to artificially constrain you so that you approach the
techniques you know from many different angles to reach a better
understanding.

------
molticrystal
I was thinking it would be an essay on computational completeness and memory
or functionality without the ability to loop, which is as fundamental as
moving the tape head left or right on a Turing machine. I suppose the title of
the article overly encourages what one might interpret it to be about. The
upvotes and comments here provide evidence that the article is useful, just
not what everybody might be expecting.

------
diego
I'd use functional programming, which is what I'd thought this post would be
about. It seems the author is not familiar with the concepts of mapping,
reducing and filtering. This is just a map() command.

~~~
Msurrow
If you were a programming language designer, would you implement a map, filter
or reduce command?

~~~
lagadu
God yes. As a .net developer, it's extremely rare for a day to go by where I
don't put them to use (via linq). They get so much stuff done.

------
deanCommie
It's really hard to teach clean code to beginners.

Concepts like SOLID principles feel like meta-academic overkill, and get in
the way of "getting things done".

Juniors don't click in to the value of well structured well modularized code
until they handle the pain of refactoring code that isn't, and introducing
unintentional bugs into it.

Your only hope, as a senior engineer, who's been through it already, is that
they have enough trust in you that when you leave code review feedback that
says "It would be more clear and maintainable if you refactor that into a
function/make the function return the value instead of mutating it as a side
effect/etc etc etc" they trust you, even if they don't yet feel the real value
of it)

I can't tell if this blog post helps or not. I need to ask one of my juniors.

~~~
stinos
_Concepts like SOLID principles feel like meta-academic overkill, and get in
the way of "getting things done"._

For small programs, certainly. But I've witnessed the effects of not applying
any kind of SOLID principle at all to larger scale software and it was never
pretty.

 _Your only hope, as a senior engineer, who 's been through it already, is
that they have enough trust in you that when you leave code review feedback
that says "It would be more clear and maintainable if you refactor that into a
function/make the function return the value instead of mutating it as a side
effect/etc etc etc" they trust you, even if they don't yet feel the real value
of it)_

This sounds kinda pessimistic. Why would you not hope for the best i.e. for a
junior understandting what you're saying and most importantly _why_ you're
saying it, and _why_ the refactored version is better? I mean, in the end
that's what's supposed to happen. And even if that doesn't work out, I'd
rather have them questioning my reasoning than just blindly trust me. I'm much
more satisfied with a junior showing critical thinking, even if they just
don't understand the matter, than just blind trust.

~~~
indigochill
In fact, I'd even say blind trust is the "mutate as side effect" of human
understanding. It may work in small, isolated incidents, but apply it at scale
and everything goes wrong.

A junior who learns engineering dogma from his seniors and repeats it without
understanding it is a liability, since when his code goes wrong, he won't know
why because he doesn't understand the principles at work. So it's the
responsibility of the mentor to not settle for blind trust, but ensure his
protege does actually understand the principles.

~~~
deanCommie
Not sure why you and stinos think I'm advocating for blind trust.

My point is simply that cleaner code is not intuitively self evidently better
without experience. It feels intuitive to senior engineers but even side by
side comparisons aren't necessarily enough for juniors.

The effort overhead required to achieve (effectively zero once you internalize
it), but far from zero when you are a beginner does not seem like it offsets
the hypothetical benefits.

So articles like OP's are quite fascinating to me because they take a totally
unorthodox approach to advocating for it.

------
rthomas6
Functional programming is the answer. In python the answer is just this:

    
    
      def action(thing)
          ...
    
      map(action, array_of_things)
    

I'm sure there's some similar solution in Javascript.

~~~
IceDane
I agree, but I think it's worth pointing out that this abstraction costs more
in a language like python. Unless I'm mistaken, I don't believe python has any
way to fuse these higher order operations into a single pass.

~~~
fnord123

        [action(x) for x in array_of_things if x > 7]
    

Action is the function you are applying. `for x in ...` will pull an iterator
from a generator which could be doing a lot of interesting stuff. And `if x >
7` is the filter.

------
regecks
There are real life situations where you must program without loops, and eBPF
programs are an example. They also have an program size and various other
limitations
([http://www.brendangregg.com/ebpf.html#bccprogramming](http://www.brendangregg.com/ebpf.html#bccprogramming)).

When I was writing an ACME (Let's Encrypt) TLS-ALPN client that ran in-kernel
with an eBPF tc program, it led to some pretty silly looking code:
[https://dpaste.de/82aZ](https://dpaste.de/82aZ)

------
KhoomeiK
This is sort of what Microsoft's new/experimental Bosque language is supposed
to be about—a paradigm shift to "Regularized Programming", similar to the
advent of Structured Programming in the 1960's.

Here's the paper: [https://www.microsoft.com/en-
us/research/uploads/prod/2019/0...](https://www.microsoft.com/en-
us/research/uploads/prod/2019/04/beyond_structured_report_v2.pdf)

------
alecmg
Just recently was rminding myself good numpy practices and honestly thought
this was also an article about broadcasting and vectorization.

Look Ma, No For-Loops: Array Programming With NumPy
[https://realpython.com/numpy-array-
programming/](https://realpython.com/numpy-array-programming/)

Which is powerful and enables multicore execution even on notorious Python GIL

------
fouc
It ends up being a form of mapping. It's a good idea/exercise for developers
to engage in occasionally in order to improve their ideas on how to organize
their code.

~~~
helloiloveyou
Exactly, thank you! I believe it is a very good exercise for when you are
teaching a programming class.

Another possible exercise is to not use ifs. The way to navigate around that
would be to use double dispatch. Not because of any reason of performance gain
with the branch predictor like other comments mentioned, but because it adds
another tool to your toolbelt

------
robomartin
At the risk of inspiring a violent reaction (virtually, of course): Sorry
folks, this is silly. Go solve real problems.

Does a patient care if the ML code that discovered a malignant tumor used an
explicitly written loop?

As a silly test: What would be the value in rewriting the Facebook, Google,
Amazon, etc. codebases to avoid loops? What’s the ROI?

Kindly provide a few examples at scale (not academic oddities) where this
might actually matter?

You can’t avoid loops. Any time you are doing the same thing repeatedly, you
are using a loop, whether you explicitly wrote it or not.

Sure, sure, there are corner cases. I have unrolled loops in assembly in real
time embedded code where every microsecond counted. That is far from the norm
and, frankly, should be avoided with absolute passion.

I have also worked with APL for ten years. You can do amazing things with the
language while seemingly not looping. In reality nearly everything you do with
APL includes heavy looping behind the curtains, and you better be well aware
of this if performance is important.

Don’t get me wrong, the academic question is interesting. However, if you live
and work in the real world there are far more interesting and pressing issues
to be concerned with.

------
lalos
I could see this as an effective pedagogical tool when learning to program.

~~~
helloiloveyou
Thank you! That's really what this post is about :) Facing the loop's body so
many times forces you to analyze if something better can be done

~~~
necovek
In real world, factoring out a function that's only ever used once is usually
not the smartest thing to do early. You'd do that if you eg. do TDD.

The article is a false "tip": author explicitely decides _not_ to extract the
entire loop body (why?) into a function but only a part of the logic. They've
chosen the factoring approach despite no loop, not thanks to it.

Their intrinsic reasoning that led to deciding what to split out would have
been more useful, and then we could argue if it was good design tip or not.

------
patrickhogan1
The example calls indexOf instead of calling a loop directly and indexOf uses
a loop under the hood.

[https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Refe...](https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)

~~~
jplayer01
Is the goal to remove all for loops at all levels of code or to abstract them
away to some benefit?

~~~
chaosite
I don't know!

The goal seems to be to restrict yourself for the sake of restricting
yourself, as a way to induce creativity. So, hiding the for-loop away in a
library function does seem like cheating...

------
glangdale
A strange emphasis. I imagined old-school GPGPU (only with loops with fixed
bounds).

Programming without branches of any kind (loop branches or otherwise) is a fun
diversion; I've enjoyed it sufficiently to have put my blog at branchfree.org
- will be more on branch free programming when my superoptimizer is more
mature.

------
taffer
Since no one has mentioned SQL yet:

    
    
        SELECT *
        FROM products
        WHERE
          (stock > 100 AND price < 5)
          OR (price BETWEEN 20 AND 35 AND category = 'outdoors')
          OR name ILIKE '%mountain%';

------
harimau777
I primarily program in JavaScript and between Lodash and the built in iterator
functions I don't see any reason to use loops other than the while loop.

I often use recursion rather than while loops; however, I find that is often
difficult to do in a way that is readable and that uses tail recursion.

What I really wish is that something like "forUntil", "mapUntil", and
"reduceUntil" would be added to the core language. Maybe the forEach, map, and
reduce could be made generic functions that operated on any iterable rather
than being defined as methods of Array.

------
Agentlien
The title of this article reminded me of that one time when I found a small
programming app for android with a very limited language. It had some basic
variables, if statements and a drawing routine for solid colour circles. It
contained no function calls and I don't think it allowed for loops.

What it did have was timers which took a piece of callback code and could be
set to repeat that code with a given frequency.

It was all very simplistic and restricted, so I spent an evening making a
breakout clone in it, just to see if I could. That was a lot of frustrating
fun.

------
leshow
I really thought the blog post would end with recursion, and found no mention
of it anywhere.

------
jchw
I was sort of wondering if the conclusion would be to use map/filter/etc, but
instead... it was to use an ordinary loop.

I am aware that there is a programming practice for writing “branchless” code.
I was expecting this to be more along those lines.

~~~
jonathankoren
Just because the loop is put in a function, the loop is still there. I would
be _very_ disturbed if someone said, "Let's not use a loop!" and then used a
normal map().

Now, if they had implemented their own map() that used tail recursion, then
we'd have something.

~~~
jchw
Well, I agree it’s still imperfect. The thing is, though, in JS the fact that
map uses a “loop” is an implementation detail; Array.prototype.map is a native
function.

Since the point of this experiment is to constrain the code to emphasize other
details, I can see why it might make sense to limit use of just native JS loop
control flow, though it is unclear that using map/filter/forEach/etc. would
lead to better code than a simple for loop. (In this case it might be alright,
but I do get annoyed when there’s a mess of functional operations going on
with complicated transformations that would’ve been really mundane with
imperative control flow.)

------
Mugwort
I have a much harder time reasoning with loops than using map, reduce,
recursion etc. I'll just say the whole truth, I can't program with loops, I
get a headache and I'm forced to quit.

------
ChrisSD
Use Rust's `const fn`. In its current state you can't do any looping, Nor any
other control flow. Fun times.

Doing recursion works easily enough but the hard part is stopping.

------
thb567
You could just get the thing going running an external file/script. And stop
it with an if condition. It's a classic structure from very noob admins.

------
zadkey
I think you would just use recursion to implement loops.

------
vincent-toups
No stinking loops link: [http://nsl.com/](http://nsl.com/)

------
pmlnr
If you program in C, try MISRA C and it's requirements/restrictions.

------
hkai
In fact, you can set up an eslint rule banning any loops from your code.

------
cozzyd
Kind of like matlab or numpy? (If you don't want it to be super slow).

------
fvilers
Not that hard, a loop is just an "if" and a "jump" instructions.

------
cr0sh
As an alternative, what if you didn't have (or didn't want to use) common
structured programming techniques, and you wanted to implement a state
machine? Like, you could use if-then, but not while, loop, do, etc? But goto
was allowed (argh?).

About a decade ago I was in a discussion with someone on this, and his
technique changed my mind on where, when, and how goto could be acceptable in
a real code base; some of you may or may not agree - but I thought I would
show you, warts (on my part) and all. Here's the thread:

[https://www.electro-tech-online.com/threads/state-machine-
in...](https://www.electro-tech-online.com/threads/state-machine-in-
mcu.113718/)

...also, here's the state machine (as implemented in C); the original post of
the image on the forum, I think, requires a login to see, thus this copy:

[http://i.imgur.com/JkND1Av.png](http://i.imgur.com/JkND1Av.png)

Read the thread to understand the arguments for and against, but it ultimately
(I think) comes down to implementation readability and maintainability. This
is a more critical thing in an embedded hardware system, of course, but I
think similar techniques, or at least the mindset behind them, could be an
advantage.

It's kinda like how learning Golang, much later, forced me to rethink the idea
of error handling. In Go, there's the concept of using "guard statements" at
the top of functions/methods to essentially exit out as soon as possible if
something untoward occurs or is passed as an argument incorrectly. Basically a
bunch of simple if-then statements at the beginning, then your code proper. It
was one of those things I recall as being something I railed against at first,
but I quickly came to understand the reasoning behind doing it that way,
rather than nested if-then statements or other constructs within the logic. It
produced simpler, easier to follow code - but at first, it felt very "wrong"
to me for some reason. It was one of many "golang-isms" I had to come to terms
with, before I really appreciated the whys and whats of the language.

Which ultimately is what these techniques are all about - the author touches
upon this: Restriction of your toolset can lead to fascinating solutions, and
in some or many cases, this simplification is reflected in the code as well;
things can become easier to understand, since less is being used. It's also
one of the reasonings behind such "demo competition" contests such as the 256
byte challenge, or 1K intros, etc. Also behind such things as pixel-art,
especially tile art using only 8 colors (or even just black and white!) with
only an 8 x 8 grid-space to work in. Some amazing art comes out of that,
operating within such restrictions.

I've heard/read that great programmers are those who are able to remove more
code than they add for a given change (aka - simplifying the code). These
techniques and everything else may all be a part of that idea.

------
cr0sh
IIRC, Mel, The Real Programmer, didn't need loops...

[https://news.ycombinator.com/item?id=9913835](https://news.ycombinator.com/item?id=9913835)

\---

I'm going to expand on this - the author of the article doesn't mention a
couple of things that you can do if you don't use loops, or don't have them
for some reason, but it is understandable that it wasn't mentioned - because
the author is working in javascript, not in something lower level.

However, that may change with WASM coming into its own; depending on if anyone
takes the challenge up of hand coding WASM (that actually isn't an "if" \- I
am certain people are doing it as I type this).

Those two commonly known (well, used to be - less so today, maybe, outside of
embedded programmers of 8 and 16 bit controllers - and the retro and demo
scene) methods:

1\. Self-modifying code

2\. Overflows

Number 2 could be seen as a subset of number 1, depending on how an overflow
is exploited.

It's been decades since I've played with either of them - and really only at a
"toy" level, but they were common techniques for those well versed in
assembler (for whatever processor or architecture), as they could be used to
write both more efficient and smaller code for a given routine.

Especially in the old 8-bit realm and before, when memory was precious and
expensive, and not very large, such techniques could allow for more
functionality than what might seemingly fit into memory space available. They
were used for everything, particular games on 8-bit machines, as well as more
mundane software.

The downside is that the code created was often very "opaque" in the sense
that you really had to understand how potentially the whole system was working
in order to understand how the technique worked and was being used in context.
This worked to the advantage of some developers - namely those writing old-
school viruses/trojans/malware - as the ability to have self-modifying code
meant they could easily hide themselves from a malware detector, and do other
nefarious (and honest, sometimes quite interesting) techniques. It's less a
thing today, but back in the day, there were some amazing bits of code
floating around that scene.

The guy who wrote about Mel understood all this - or came to understand it -
very well. Mel did it mainly because he didn't trust compilers to make fast
code (which they probably didn't back in his day - even today, there are edge
cases that fall thru the cracks - it's just code, it can't do everything -
yet) - so he hand coded everything; he also had the advantage that he knew
precisely how the system worked, especially the drum memory of that system,
and by precisely timing things, and deft use (abuse?) of certain registers -
well, he was able to do seemingly wizard-like tasks. I can't say he didn't use
any loops - he did in fact use one; it looked like an endless loop with
nothing inside it; in fact, it should just cause the machine to hang. But the
author of Mel's story found that the machine, stepping through the code, would
pass right thru this seemingly endless loop. Well - he explains how Mel did
it, and it really is a thing of wonder, combining both register manipulation,
overflow techniques, and self-modifying code to the utmost.

Read the story to find out more (Mel - as far as can be determined, was likely
a real person; there is a picture of him out there that was dug up. No one
knows for certain whether that Mel is The Mel - but most believe the person
depicted probably is).

Also google around on such techniques to learn how they work; you likely won't
be able to apply them to your day-to-day (unless you work in assembler or
something like that), but you might learn something useful for your toolbag.

Oh - one other thing - I kinda lied about how these tricks can only apply to a
low-level; that's not completely true. There are certain techniques that can
be done with higher level languages (and not using exploitable bugs). I won't
go into detail here, though. Maybe you can work it out on your own?

------
exabrial
Why not though? Generally our CPUs are based around jumps

~~~
onion2k
Not all code runs on a CPU. For example, older GPUs didn't support for loops.

~~~
exabrial
... specifically why I said CPUs. A loop generally gets translated to a jmp
instruction in most architectures.

------
yjhoney
I teach a JS coding class and I had the luxury of creating the curriculum. To
the horror of my peers, I decided that coding with loops was not necessary for
beginners and got rid of loops altogether from our curriculum. I thought OP
was proposing the same thing, but I was wrong. the refactoring was good, but
my students will probably end up with the following result (more in line with
your no loops title):

```

function highlightProducts(aListOfProducts, i=0){

    
    
      if (i === aListOfProducts.length) return null
    
      if(productShouldBeHighlighted(product)){
    
        product.highlighted = true
    
      }
    
      return highlightProducts(aListOfProducts, i + 1)
    

}

```

The added benefit of the above approach is that debugging is easier! If you
want to debug the last 5 items, you can simply call the function like this:

highlightProducts(aListOfProducts, aListOfProducts.length – 5)

Ultimately, though, everyone should be using higher order functions (map,
reduce filter, find, etc.)

~~~
BalinKing
This seems a bit worrying to me as a general principle, since Javascript's
lack of tail call optimization (TCO) means that you can blow the stack pretty
easily by manually recursing. Just my two cents :-)

~~~
BalinKing
(Out of curiosity, have you considered starting at raw recursion – like in
your example – but then moving on to using utility functions like `map` from
e.g. Ramda or lodash?)

~~~
yjhoney
Yes. curriculum starts off with recursion, then moves on to higher order
functions and nobody touches recursion again.

