Some time ago, I have encountered a programming exercise in which the goal was to implement Luhn algorithm for credit card number validation.
At first I implemented the algorithm in Ruby and then decided to implement it in Elixir. Eventually, I like how Elixir version looks like.

In this article Elixir 1.2.2 is being used.

Initial version

It is assumed that credit card numbers are being read from a file and that check digit is included in a credit card number.

The Luhn algorithm is quite straightforward. Description from Wikipedia 1:
> The formula verifies a number against its included check digit, which is usually appended to a partial account number to generate the full account number. This number must pass the following test:

From the rightmost digit, which is the check digit, moving left, double the value of every second digit; if the product of this doubling operation is greater than 9 (e.g., 8 × 2 = 16), then sum the digits of the products (e.g., 16: 1 + 6 = 7, 18: 1 + 8 = 9).
Take the sum of all the digits.
If the total modulo 10 is equal to 0 (if the total ends in zero) then the number is valid according to the Luhn formula; else it is not valid.

My main function of the Luhn algorithm clearly outlines the steps of the algorithm:

The part that made me think hard is doubling of every second digit.
It was implemented by converting a string with a credit card number into list of codepoints and converting this list into list of lists of at most 2 elements, doubling every second element in each list and then those lists were flattened into one list:

Function sum_double_numbers/1 converts each number from a list of numbers (eg., ["2", "18"]) into list of codepoints. If this list contains two codepoints, then it sums them, otherwise the codepoint is returned as is.

Refactoring

To search for possible improvements for the code, I have installed a static code analysis tool called credo.
After running mix credo in my project I have got 8 refactoring opportunities and 1 code readability issue:

Pipe chain should start with a raw value

This means that I should refactor my pipes to start with a raw value (clearly!).
For example, function remove_whitespace/1 will looks like:

As a general suggestion that can be derived from this optimization is that it is beneficial to pass credit card number as an integer instead of a string.
By refactoring code to pass around credit card numbers as integers we again see notable improvement in speed:
We went from 33,13 µs/op (with 50000 iterations) to 14,34 µs/op (with 100000 iterations).

Conclusion

We implemented Luhn algorithm in Elixir and saw how pipelines outline steps of the algorithm. After coding the initial version we used credo to receive suggestions on style of our code. Lastly, we improved performance of the algorithm by implementing several functions differently and by treating a credit card number as an integer instead of a string. Nice side-effect of this refactoring is that credo does not find any problems with the code.