I’m reading Programming Elixir ≥ 1.6 right now and there are two interesting examples of coding Fizzbuzz.

defmodule Fizzbuzz do
  def upto(n) when n > 0, do: _upto(1, n, [])

  defp _upto(_current, 0, result), do: Enum.reverse(result)

  defp _upto(current, left, result) do
    next_answer =
      cond do
        rem(current, 3) == 0 and rem(current, 5) == 0 -> "Fizzbuzz"
        rem(current, 3) == 0 -> "Fizz"
        rem(current, 5) == 0 -> "Buzz"
        true -> current

    _upto(current+1, left-1, [next_answer | result])

This is the output:

iex> Fizzbuzz.upto(20)

[1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz","Buzz", 11, "Fizz", 13, 14, "Fizzbuzz", 16, 17,
"Fizz", 19, "Buzz"]

Here the function upto is the trampoline for the function. It calls the private function _upto as long as the input n is greater than 0.
_upto is the recursive function with accumulator. When n reaches 0, it spits out the accumulator result. We need Enum.reverse because the main function builds up the list in the wrong order.
The second case is the workhorse function. It sets up a binding for next_answer which determines whether a number satisfies one of the Fizzbuzz conditions. Then it recurses until done.
[next_answer | result] builds the list with next_answer as the head and result as the tail.

The book also has an interesting second example which uses Enum.map and pattern matching.

defmodule Fizzbuzz do
  def upto(n) when n > 0, do: 1..n |> Enum.map(&fizzbuzz/1)

  defp fizzbuzz(n), do: _fizzword(n, rem(n, 3), rem(n, 5))

  defp _fizzword(_n, 0, 0), do: "Fizzbuzz"
  defp _fizzword(_n, 0, _), do: "Fizz"
  defp _fizzword(_n, _, 0), do: "Buzz"
  defp _fizzword(n, _, _), do: n

Here, upto maps over the range of 1 to the input number with the private function fizzbuzz.
And fizzbuzz uses _fizzword and pattern matching to determine the output result.

I find Elixir’s pattern-matching novel, as I haven’t encountered a language that does things this way.

Further Reading