Elixir 8 min read

Detect Unused Public Functions in Elixir with This Simple Script

March 10, 2026

Overview

As your Elixir codebase grows, it's common to accumulate unused public functions that were once needed but are no longer called anywhere in your application. These orphaned functions create technical debt, making your code harder to navigate and maintain. This guide presents a simple yet powerful bash script that helps you identify these unused functions so you can clean up your codebase with confidence.

The Problem with Unused Functions

Dead code in your codebase has several negative impacts:

The Solution: A Simple Bash Script

While Elixir has excellent tooling, it doesn't include a built-in way to detect unused public functions. This bash script fills that gap by using standard Unix tools to analyze your codebase.

The Script

#!/bin/bash

# Find unused public functions using only Unix commands
# Usage: ./find_unused_functions.sh

echo "=========================================="
echo "UNUSED PUBLIC FUNCTIONS"
echo "Generated: $(date)"
echo "=========================================="
echo ""

# Find all public function definitions and check if unused
grep -rn --include='*.ex' '^[[:space:]]*def [a-z_][a-z0-9_?!]*[( ]' lib/ | \
  grep -v 'defp\|defmodule\|defmacro\|defdelegate\|defstruct\|defimpl\|defprotocol\|defexception' | \
  sed -E 's/^(.+):([0-9]+):[[:space:]]*def ([a-z_][a-z0-9_?!]*).*/\1:\2:\3/' | \
  while IFS=':' read -r file line func; do

    # Escape regex special characters in function name (? and !)
    escaped_func=$(echo "$func" | sed 's/[?!]/\\&/g')

    # Search for usages:
    # - .func( or .func/     : module.function() calls or captures
    # - &func/               : function captures
    # - :func                : atoms (apply, etc.)
    # - func( or func do     : calls with parens or definitions without parens
    # - <.func               : Phoenix component syntax
    pattern="\\.${escaped_func}[(/]|&${escaped_func}/|:${escaped_func}\\b|\\b${escaped_func}[( ]|<\\.${escaped_func}\\b"

    count=$(grep -rEc --include='*.ex' --include='*.exs' --include='*.eex' --include='*.heex' --include='*.leex' "$pattern" lib test 2>/dev/null | \
            grep -E ':[1-9][0-9]*' | \
            cut -d: -f2 | \
            awk '{sum+=$1} END {print sum+0}')

    # Only output if unused (count <= 1)
    if [ "$count" -le 1 ]; then
      echo "$func (defined in $file:$line)"
    fi

  done > /tmp/unused_functions.txt

# Print results
cat /tmp/unused_functions.txt

echo ""
echo "=========================================="
echo "Found $(wc -l < /tmp/unused_functions.txt) unused function(s)"
echo "=========================================="

How the Script Works

Let's break down what each part of the script does:

1. Finding Public Function Definitions

grep -rn --include='*.ex' '^[[:space:]]*def [a-z_][a-z0-9_?!]*[( ]' lib/

This searches for lines that start with def followed by a function name. It only looks in *.ex files within the lib/ directory.

2. Filtering Out Non-Function Definitions

grep -v 'defp\|defmodule\|defmacro\|defdelegate\|defstruct\|defimpl\|defprotocol\|defexception'

This excludes private functions (defp) and other def* keywords that aren't public function definitions.

3. Extracting Function Information

sed -E 's/^(.+):([0-9]+):[[:space:]]*def ([a-z_][a-z0-9_?!]*).*/\1:\2:\3/'

This extracts three pieces of information: the file path, line number, and function name.

4. Searching for Function Usages

The script searches for various patterns that indicate a function is being used:

5. Counting References

The script counts how many times each function is referenced across all .ex, .exs, .eex, .heex, and .leex files in both lib/ and test/ directories.

6. Identifying Unused Functions

if [ "$count" -le 1 ]; then
  echo "$func (defined in $file:$line)"
fi

A function is considered unused if it appears only once (its own definition). If the count is 1 or less, the function is likely unused.

Using the Script

Step 1: Save the Script

Create a file named find_unused_functions.sh in your project root and paste the script above.

Step 2: Make it Executable

chmod +x find_unused_functions.sh

Step 3: Run the Script

./find_unused_functions.sh

Example Output

==========================================
UNUSED PUBLIC FUNCTIONS
Generated: Mon Mar 10 14:32:15 PST 2026
==========================================

calculate_discount (defined in lib/shop/pricing.ex:42)
send_notification (defined in lib/users/notifier.ex:18)
format_address (defined in lib/utils/formatter.ex:67)

==========================================
Found 3 unused function(s)
==========================================

Important Considerations

False Positives

The script may report functions as unused in certain scenarios:

Manual Review Required

Always review the results manually before deleting functions. Consider:

Best Practices

1. Run Regularly

Include this script in your regular code review process. Consider running it:

2. Make Functions Private When Possible

If a function is only used within its module, convert it from def to defp:

# Before
def internal_helper(data) do
  # ...
end

# After
defp internal_helper(data) do
  # ...
end

3. Document Public APIs

Add @doc annotations to functions that are intentionally public, making it clear they're part of your API:

@doc """
Calculates the total price including tax.

This function is part of the public API and may be called by external systems.
"""
def calculate_total(subtotal, tax_rate) do
  # ...
end

4. Use Behaviours for Callbacks

Define behaviours to make it explicit which functions are callbacks:

defmodule MyApp.PaymentProcessor do
  @callback process_payment(amount :: float()) :: {:ok, String.t()} | {:error, String.t()}
end

Extending the Script

Exclude Specific Patterns

You can modify the script to exclude certain patterns. For example, to skip functions ending with _callback:

grep -rn --include='*.ex' '^[[:space:]]*def [a-z_][a-z0-9_?!]*[( ]' lib/ | \
  grep -v 'defp\|defmodule\|_callback'

Save to a File

Redirect output to a file for easier review:

./find_unused_functions.sh > unused_functions_report.txt

Integrate with CI/CD

Add the script to your CI pipeline to track unused functions over time:

# .github/workflows/code-quality.yml
- name: Check for unused functions
  run: |
    ./find_unused_functions.sh
    if [ $(wc -l < /tmp/unused_functions.txt) -gt 10 ]; then
      echo "Warning: More than 10 unused functions detected"
    fi

Alternative Tools

While this script works well for most projects, you might also consider:

Conclusion

Maintaining a clean codebase is essential for long-term project health. This simple bash script provides a quick and effective way to identify unused public functions in your Elixir projects. By regularly running this script and cleaning up dead code, you'll improve code readability, reduce maintenance burden, and keep your project lean and focused.

Remember that automated tools are helpers, not replacements for human judgment. Always review the results carefully and consider the context before removing functions. Some functions may appear unused but serve important purposes in your architecture.

References