From computability theory, we know that a programming language which only contains terminating programs (say, without loops and recursion) is strictly less powerful than a Turing machine (or any other Turing-complete programming languages).
So, if we want a programming language $P$ which is able to express, say, a Python interpreter, a JVM implementation, or even an interpreter for $P$ itself, we need a language having some sort of unbounded loop / recursion.
Technically, if we have a
while loop, we no longer need
if condition then cTrue else cFalse
is equivalent to
branchTaken = false
while condition and !branchTaken do
branchTaken = true
while !condition and !branchTaken do
branchTaken = true
branchTaken is an otherwise unused boolean variable.
But this is terribly inconvenient to use in practice!
So, programming languages tend to include
if, even if not strictly needed.
while (ans similar unbounded loops). We said that we need some construct for unbounded looping/recursion. Why
Well, recursion alone suffices, but it is not very convenient in imperative programming. Note that, by contrast, functional programming languages typically do not have any
while loop, and only exploit recursion -- in functional programming, recursion is far more natural and convenient, especially if the language is pure (without side effects) and a WHILE loop would be pointless, since we can not mutate variables.
Most widespread imperative programming languages use
while or similar loops. In principle, they could instead use
goto, which have the same power. However, practice showed that
goto often led to code which was very hard to read and modify. Dijkstra wrote his famous letter "GOTO considered harmful", advocating for using structured programming (
while and friends) instead. One of the scientific motivations behind this, is that to properly reason about a
while loop we need to think about a loop invariant. This is already quite hard, in general. In a
goto-using program, we need an invariant for every label/line number which is being targeted by some
goto. This is much harder, in general, since many
gotos may point to the same label, so the invariant must take into account the multiple "incoming" points. A solution for this could be using
goto in a more disciplined way, but if we do, we'll probably end up in using
while (or another loop) under a different notation.