While loops are control structures that repeatedly run a block of code while a boolean condition remains `true`

, or a `break`

statement is encountered. Here’s an example of a while loop in Solidity:

```
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
contract WhileLoopSyntaxExample {
function incrementingLoop(uint iterations) public pure {
uint i = 0;
while (i < iterations) {
// Loop code goes here
i++;
}
}
function decrementingLoop(uint iterations) public pure {
while (iterations > 0) {
// Loop code goes here
iterations--;
}
}
}
```

While loops create a “stripped down” looping structure that can use non-traditional loop conditions. This makes them useful for algorithms, such as those for sorting arrays or performing advanced mathematical and programming operations.

However, it is easy to accidentally create infinite loops if the developer forgets to update their loop condition.

**Contents**Expand

## Syntax Breakdown

Just like for loops, while loops typically consist of four components:

**Counter Variable**: A local variable used for counting loop iterations**Loop Condition**: A boolean expression used to repeat the loop when`true`

, or terminate it when`false`

**Loop Body**: The code block to repeat**Increment Expression**: An assignment operation that alters the value of the counter variable

The counter variable is always declared *outside* the loop, and is altered *inside* the loop.

Whether the counter variable is altered at the end or the beginning of the body depends on whether its pre-increment or post-increment value is needed.

## Avoiding Infinite Loops

A common pitfall that while loops suffer from is the ease with which an infinite loop can be created. While loops are infinitely repeating *by default*, and we have to impose finite constraints on them.

### Forgetting Increment/Decrement Expressions

The most common culprit of this is forgetting to include a counter variable’s increment expression:

```
contract InfiniteLoops {
// This loop will continue forever... forgot to decrement iterations!
function infiniteLoop(uint iterations) public pure {
while(iterations > 0){
console.log("Iterations remaining: ", iterations);
}
}
// This loop will terminate
function finiteLoop(uint iterations) public pure {
while(iterations > 0){
console.log("Iterations remaining: ", iterations);
iterations--;
}
}
}
```

The only difference between the above example that repeats forever and the one that doesn’t is `iterations--`

. This is so easy to overlook that even experienced developers forget them from time to time.

### Forgetting to Increment/Decrement Before `Continue`

Another common pitfall is forgetting to include an increment or decrement expression before a `continue`

statement:

```
contract InfiniteLoops {
// Uh oh, we forgot to decrement before calling `continue`!
function infiniteLoop(uint iterations) public pure {
while(iterations > 0){
if(iterations == 5) continue; // Forgot to decrement!
console.log("Iterations remaining: ", iterations);
iterations--;
}
}
// Here we remembered to decrement before calling `continue`
function finiteLoop(uint iterations) public pure {
while(iterations > 0){
if(iterations == 5){
iterations--;
continue
}
console.log("Iterations remaining: ", iterations);
iterations--;
}
}
```

Unlike for loops, while loops don’t automatically run the increment expression after a `continue`

statement, so we must include this expression ourselves. Where we place this expression is very important for ensuring our loop can terminate, but we also have complete freedom to place the expression wherever makes sense.

## Use Cases

While loops have many use cases, and are much more flexible in their setup than for loops and do-while loops.

### Incrementing and Decrementing Over Arrays

A major use case of loops in Solidity is to iterate over an array of values or structs. This is primarily done with for loops, but while loops can be built to serve the exact same function by including the same components.

Let’s add two new functions to our previous example that log the elements of the `values`

array to the console, one in ascending order and the other in descending order:

```
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
import "hardhat/console.sol";
contract WhileLoopSyntax {
uint[] public values;
// Moves "down" the array and logs its values to the console
function incrementingLoop() public pure {
uint i = 0;
while (i < values.length) {
console.log(values[i]);
i++;
}
}
// Moves "up" the array and logs its values to the console
function decrementingLoop() public pure {
uint i = values.length
while (i > 0) {
i--; // Notice we are decrementing first
console.log(values[i]);
}
}
}
```

You may notice that the `decrementingLoop()`

function decrements `i`

*before* using its value. This is to avoid off-by-one bugs, out-of-bounds errors, and integer underflow errors that are common with decrementing loops.

### Repeatedly Calling a Function

We can easily use a while loop to repeatedly call a function. For instance, let’s create an array and a pair of functions for generating and pushing pseudo-random numbers to it:

```
contract WhileLoopSyntax {
uint[] public values;
// Repeatedly pushes random values to the `values` array
function addRandomValues(uint totalValues, uint maxValue) public {
while(totalValues > 0){
values.push(randomNumber(maxValue, totalValues));
totalValues--;
}
}
// Simple getter to display the `values` array
function getValues() public view returns(uint[] memory){
return values;
}
// !!! DO NOT USE IN PRODUCTION !!!
// Generates a pseudo-random number for testing
function randomNumber(uint max, uint salt) public view returns(uint){
return uint(keccak256(abi.encode(salt, block.timestamp))) % max;
}
}
```

In this example, we are calling the `randomNumber()`

function to generate pseudo-random numbers, and the while loop repeatedly generates and pushes these numbers to the `values`

array until the desired `totalValue`

has been reached.

## Algorithms and While Loops

Whereas for loops are specialized for iterating over arrays, while loops are specialized for implementing algorithms. An algorithm is a repeating sequence of steps that perform a complex task, often with an unknown number of repetitions.

While Solidity isn’t a great language for implementing algorithms due to gas concerns, they are great examples of situations where a while loop’s condition is not dependent upon a loop counter.

Let’s explore some common algorithms we can implement in Solidity.

### Counting Digits in a Number

A simple example is an algorithm that counts the number of digits in an input number. This is a great example of a “non-traditional” loop, since the loop condition is not dependent upon a loop counter:

```
contract DigitCounter {
function countDigits(uint number) public pure returns (uint digitCount) {
if (number == 0) return 1;
digitCount = 0;
while (number != 0) {
number /= 10; // Remove the last digit
digitCount++;
}
return digitCount;
}
}
```

This algorithm divides the input `number`

by `10`

each round, incrementing the `digitCount`

by `1`

each time, until the `number`

reaches `0`

(this happens due to integer division).

### Converting Unsigned Integers to Strings

Sometimes we need to convert numbers to strings so we can concatenate them together, as Solidity does not provide any native functionality for concatenating strings and other data types together.

Converting from `uint`

to `string`

is a complex process that requires understanding how the EVM processes strings and integers, and how the ASCII table works.

Fortunately, we can convert integers into `bytes`

, and `bytes`

into `string`

s:

```
contract UintToString {
function uintToString(uint256 value) public pure returns (string memory) {
// Handle zero case explicitly
if (value == 0) return "0";
// Count number of digits
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
// The buffer is used to store intermediate bytes characters
bytes memory buffer = new bytes(digits);
// Convert each digit to ASCII and store in buffer
while (value != 0) {
digits--; // Start from the end and move backwards
// Convert last digit to its ASCII value
uint8 asciiDigit = (value % 10) + 48;
// Convert to bytes1 and store in the buffer
buffer[digits] = bytes1(asciiDigit);
// Remove last digit from the value
value /= 10;
}
// Convert the bytes buffer into a string
return string(buffer);
}
}
```

If you need to convert integers to strings in a real contract, then you should import OpenZeppelin’s `Strings`

library, as their `toString()`

function is highly gas-optimized.

### Calculating Square Root Via Bablyonian Method

Let’s see an example of a function that calculates a square root using the Babylonian Method. The Babylonian Method is an ancient mathematical algorithm for approximating the square root of a number, and it is still in use today:

```
contract BabylonianSquareRoot {
function sqrt(uint x, uint decimals) public pure returns (uint) {
x *= 10**(decimals*2);
if (x == 0) return 0;
uint z = (x + 1) / 2;
uint y = x;
// Continue iterating until the approximation is stable
while (z < y) {
y = z;
z = (x / z + z) / 2;
}
return y;
}
}
```

A full explanation of how this algorithm works is far beyond the scope of this article, so you will just have to take it at face-value.

This function accepts two inputs: The `x`

we wish to take the square root of, and how many `decimals`

of accuracy we want the result to have. The output number will be an integer, so we must manually place the decimal point ourselves.

For example, `sqrt(2, 8)`

will output `141421356`

, which is `1.41421356`

after we add the decimal point. Then, `sqrt(150, 8)`

outputs `1224744871`

, which is `12.24744871`

with the decimal added. You can verify that these results are precise to their chosen decimal place with a calculator app.

### Sorted Arrays and Insert/Remove Operations

While sorting arrays in Solidity is a very bad idea due to gas costs, implementing an array that uses insertion and removal algorithms to maintain a sorted state from its beginning is an acceptable compromise.

Let’s create a contract that maintains an array of sorted values, and includes insertion and removal functions:

```
contract SortedArray {
uint[] public sortedValues;
// Function to insert a value into the sorted array
function insertValue(uint value) public {
sortedValues.push(value); // Add the value to the end of the array
uint i = sortedValues.length - 1;
// Bubble the new value "up" the array to its correct position
while (i > 0 && sortedValues[i] < sortedValues[i - 1]) {
(sortedValues[i], sortedValues[i - 1]) = (sortedValues[i - 1], sortedValues[i]);
i--;
}
}
// Function to remove a value from the sorted array
function removeValue(uint value) public {
require(sortedValues.length > 0, "Array is empty");
uint i = 0;
bool found = false;
// Find the value to remove
while(!found && i < sortedValues.length) {
if (sortedValues[i] == value) found = true;
else i++;
}
require(found, "Value not found");
// Bubble the value "down" the array until it reaches the end
while (i < sortedValues.length - 1) {
(sortedValues[i], sortedValues[i + 1]) = (sortedValues[i + 1], sortedValues[i]);
i++;
}
sortedValues.pop(); // Pop the value from the array
}
}
```

This example allows us to keep an array of sorted values where we never need to sort the entire array, as the only way to add or remove values is through sorted insertion and removal. Note that we used tuples to swap values, rather than relying on an intermediate variable.

There are ways to make this system far more gas-efficient, such as implementing Binary Search algorithms and caching the array in memory, but that’s a topic better left to a gas optimization techniques article.

Whereas while loops are less common in Solidity than for loops, they are useful when a custom looping control structure is needed. This is valuable when implementing algorithms, which often have an unknown number of repetitions involved.

Also, while loops do not provide any safeguards to prevent infinite loops, and the programmer is responsible for ensuring they function as-intended. However, in return they provide a great degree of customization of the loop’s behavior and form, allowing for more advanced designs.