The following function checks if an array has any duplicates by taking each element, then iterating over the whole array to see if the element is there

```
_Bool contains_duplicates(const int *array, size_t len) {
for (int i = 0; i < len - 1; i++) {
for (int j = 0; j < len; j++) {
if (i != j && array[i] == array[j]) {
return 1;
}
}
}
return 0;
}
```

The inner loop performs at each iteration a number of operations that is constant with `n`

. The outer loop also does a few constant operations, and runs the inner loop `n`

times. The outer loop itself is run `n`

times. So the operations inside the inner loop are run `n^2`

times, the operations in the outer loop are run `n`

times, and the assignment to `i`

is done one time. Thus, the complexity will be something like `an^2 + bn + c`

, and since the highest term is `n^2`

, the O notation is `O(n^2)`

.

As you may have noticed, we can improve the algorithm by avoiding doing the same comparisons multiple times. We can start from `i + 1`

in the inner loop, because all elements before it will already have been checked against all array elements, including the one at index `i + 1`

. This allows us to drop the `i == j`

check.

```
_Bool faster_contains_duplicates(const int *array, size_t len) {
for (int i = 0; i < len - 1; i++) {
for (int j = i + 1; j < len; j++) {
if (array[i] == array[j]) {
return 1;
}
}
}
return 0;
}
```

Obviously, this second version does less operations and so is more efficient. How does that translate to Big-O notation? Well, now the inner loop body is run `1 + 2 + ... + n - 1 = n(n-1)/2`

times. This is *still* a polynomial of the second degree, and so is still only `O(n^2)`

. We have clearly lowered the complexity, since we roughly divided by 2 the number of operations that we are doing, but we are still in the same complexity *class* as defined by Big-O. In order to lower the complexity to a lower class we would need to divide the number of operations by something that *tends to infinity* with `n`

.