You can walk through the array once and keep track of the number of elements that have each remainder up to k-1, when they are divided by k. That takes O(k) space, but allows you to solve the problem in O(n + k) steps, which is much better if k is small.
Pseudocode (AKA JavaScript):
function countDivisiblePairs( arr, k ) {
const remainders = Array( k ).fill( 0 );
for ( let i = 0; i < arr.length; i++ ) {
remainders[ arr[ i ] % k ]++;
}
// Count the pairs of elements that were individually
// divisible by `k` (had a remainder of `0`), so
// added together they are still divisible by k
//
// `remainders[ 0 ]*remainders[ 0 ]` of them if we wanted all, but
// since we only want to count when i < j, so we subtract
// `remainders[0]` to get rid of the cases where `i = j` and then
// we divide by 2 to remove the cases where `i > j`.
let numPairs = (remainders[ 0 ]*remainders[ 0 ] - remainders[ 0 ])/2;
// Count the cases where the remainder wasn't 0,
// if the remainders sum to `k`, then the sum is divisible by `k`.
for ( let i = 1; i <= k/2; i++ ) {
// Note that i + (k - i) = k, so each elements with a
// remainder of `i` can be paired with each element with
// a remainder of `k`, but, if `k` was even there will be a
// final iteration where `i = (k - i)`, so we need to add a
// special case to only count when `i < j`, we do it the same
// way as we did for the case when the remainder was 0. with
// the if statement `(2*i === k)` is another way of writing
// the expression `i === (k - i)`.
if ( 2*i === k )
numPairs += (remainders[ i ]*remainders[ i ] - remainders[ i ])/2;
else
numPairs += remainders[ i ]*remainders[ k - i ];
}
return numPairs;
}
The code above assumes that there are no duplicates in the input array. In that case you need special care EG, [2,2,4,4,4], 2 should ouput 6 but instead outputs 10 (the correct output for [2,4,6,8,10], 2) but it should give you the jist of the algorithm. Output:
countDivisiblePairs( [1,2,3,4,5,6,7,8], 2 ); // 12
countDivisiblePairs( [1,2,3,4,5,6,7,8], 3 ); // 10
countDivisiblePairs( [1,2,3,4,5,6,7,8], 4 ); // 6
countDivisiblePairs( [1,2,3,4,5,6,7,8], 5 ); // 6
Note that the special case in the loop only happens on the last iteration and only when k is even, EG. if k is 4, then when i = 2, i === (k - i), so if we didn't have the special handling we would count extra elements. EG for the example output where I used a k of 4, there are two elements that have a remainder of 2: [2,6]. If we didn't have the extra handling it would say there are four ways to pair them, amounting to (2,2),(2,6),(6,2),(6,6), but the extra logic subtracts the cases where they're paired with themselves, so we have (2,6),(6,2) remaining and then the division by 2 subtracts the case where i > j, so you're left with only one pair being counted: (2,6).