Python Built-in Functions vs User-Defined Functions

Understand the differences between Python's built-in functions and the functions you write yourself, including performance, availability, and how they work together.

7 min read

Every Python program uses built-in functions. The print that writes output to the console, the len that measures the size of a collection, the type that tells you what kind of object something is, these are all built-in functions that Python provides without requiring any import. You have been calling them since your very first script. At the same time, you have been writing user-defined functions with the def keyword throughout this entire Functions section. Understanding how these two categories of functions relate, how they differ in performance and behavior, and how to combine them effectively is the capstone that connects Python's built-in vocabulary with the vocabulary you create for your own programs.

The article on Python functions explained introduced the concept that built-ins are not magical; they are functions like yours, just written in C rather than Python and compiled into the interpreter. This article goes deeper into the practical implications of that difference: when built-in performance matters, how built-ins interact with the scope system, and the design patterns for wrapping built-ins with your own functions.

Where built-in functions come from

Python's built-in functions live in the builtins module, which Python imports automatically into every module's namespace at startup. This is why you can write print without importing anything, even though print is technically defined in a module like any other function. The automatic import of builtins is part of Python's startup sequence, and it is what makes the built-in scope the final level in the LEGB lookup order covered in the article on variable scope in Python.

The builtins module contains more than just functions. It includes built-in types like int, str, list, and dict, constants like True, False, and None, and exception classes like ValueError and TypeError. Everything in builtins is available without import, and together they form the core vocabulary that every Python programmer knows. You can inspect the full list by calling dir(builtins) in the REPL, which reveals names you use daily alongside names you may never have needed, like copyright and license.

The implementation of built-in functions varies. Some, like len and sum, are implemented entirely in C for maximum performance. Others, like print, are thin Python wrappers around C implementations that add convenience features, such as print's ability to accept multiple arguments and handle the separator and end parameters. Understanding this implementation split is not necessary for using built-ins, but it explains why some built-ins are faster than user-defined equivalents and why the performance advantage varies by function.

Performance differences between built-ins and user-defined functions

Built-in functions are faster than user-defined Python functions for two reasons. First, they are compiled C code that runs directly on the processor, bypassing Python's bytecode evaluation loop. Second, calling a built-in function avoids some of the overhead of calling a Python function, such as setting up a full Python frame object with local variable storage.

The performance difference is usually negligible for individual calls. Calling len(my_list) takes a fraction of a microsecond regardless of whether len is a built-in or a user-defined function. The difference becomes meaningful in performance-critical loops that call a function millions of times:

pythonpython
def user_len(seq):
    count = 0
    for _ in seq:
        count += 1
    return count
 
# Built-in len is dramatically faster on large sequences
result = len(huge_list)       # C-level loop, very fast
result = user_len(huge_list)  # Python-level loop, much slower

The user-defined user_len iterates through the sequence in Python, with Python bytecode executing for each iteration. The built-in len calls the object's len method, which for built-in types like list and str is implemented in C and returns the precomputed length directly. The performance gap widens as the sequence grows because the built-in's time is constant while the user-defined version's time is proportional to the sequence length.

This performance difference does not mean you should avoid writing functions. It means you should use built-in functions for their intended purposes and not reimplement them in Python unless you have a specific reason. Functions like sorted, max, min, sum, any, and all are both fast and correct; rewriting them introduces bugs and slows down your code.

Shadowing built-in functions

Defining a function with the same name as a built-in shadows the built-in within the scope where the definition occurs. The name in the global scope now refers to your function, not Python's, and any code that tries to use the built-in will call your function instead:

pythonpython
def len(obj):
    print("Custom len called!")
    return 0
 
print(len([1, 2, 3]))  # Prints "Custom len called!" then 0

This is valid Python, and it is sometimes done intentionally when a custom len provides additional behavior, such as logging each call or handling a type that the built-in len does not support. In most cases, though, shadowing built-ins is a mistake. It confuses anyone reading the code, who expects len to be the standard function, and it breaks any library code that your program imports and that relies on the built-in behaving correctly.

If you must shadow a built-in, document it prominently and consider whether the behavior you need would be better expressed as a function with a different name. A function called custom_len or safe_len is less likely to be confused with the built-in than one simply called len. If you need the original built-in after shadowing it, you can access it through the builtins module as builtins.len, but this workaround is a sign that the shadowing should be reconsidered.

Wrapping built-in functions

A cleaner pattern than shadowing is wrapping. A wrapper function accepts the same arguments as a built-in, adds behavior before or after calling the built-in, and returns the built-in's result. The wrapper has a distinct name, so there is no risk of shadowing, and the built-in remains available in its original form:

pythonpython
def safe_divide(a, b, default=None):
    """Divide a by b, returning default if b is zero."""
    return a / b if b != 0 else default

This function wraps the division operator with a safety check, returning a default value instead of raising a ZeroDivisionError. It does not shadow any built-in because safe_divide is not a built-in name. Callers who want the original division behavior can still use the / operator directly. Callers who want the safe version use safe_divide.

Wrapping is also useful for adding logging, timing, or validation around built-in operations. A function that logs every call to sorted, or that validates inputs before passing them to sum, keeps the built-in's behavior intact while adding the cross-cutting concern. This pattern is explored in greater depth in the article on Python decorators explained, where the wrapper becomes a decorator that can be applied to multiple functions.

Designing user-defined functions that feel built-in

The best user-defined functions feel like natural extensions of Python. They accept arguments in the same style as built-ins, they have clear names that describe what they do, and they behave consistently regardless of the context in which they are called. The sorted function is a model: it accepts an iterable, has keyword-only parameters for key and reverse with sensible defaults, and returns a new list without modifying the input.

Design your functions to follow the same patterns. Return values instead of modifying arguments in place, unless mutation is the function's explicit purpose. Use keyword-only parameters for optional configuration so callers can specify only what they need. Give functions verb-based names in snake_case. Document them with docstrings as described in the article on documenting Python functions with docstrings. These habits make your functions predictable and easy to learn, which is exactly what makes Python's built-ins valuable.

Rune AI

Rune AI

Key Insights

  • Built-in functions are implemented in C, always available without imports, and typically faster than Python-level functions.
  • User-defined functions are written in Python with def or lambda and extend the language with domain-specific operations.
  • Shadowing a built-in by defining a function with the same name is allowed but strongly discouraged.
  • Shaded built-ins can still be accessed through the builtins module: builtins.len.
  • Wrapping a built-in with your own function is useful when you need to add validation or logging to a standard operation.
RunePowered by Rune AI

Frequently Asked Questions

Are Python built-in functions faster than user-defined functions?

Yes, typically. Built-in functions like len, sum, and sorted are implemented in C and compiled directly into the Python interpreter. They execute with less overhead than user-defined functions written in Python because they bypass the Python bytecode evaluation loop. The difference is usually small for single calls but becomes significant in performance-critical loops.

What happens if I define a function with the same name as a Python built-in?

Your function shadows the built-in within the scope where it is defined. If you define a function called len, any call to len in that scope will call your function, not the built-in. The built-in is still accessible through the builtins module as builtins.len, but shadowing built-in names is strongly discouraged because it confuses readers and can break code that expects the standard behavior.

How can I see all available Python built-in functions?

Call dir(__builtins__) in the Python REPL to list all names in the built-in namespace. This includes built-in functions like print and len, built-in types like int and str, constants like True and None, and exception types like ValueError. The complete list is documented in the Python standard library reference under Built-in Functions.

Conclusion

Python's division between built-in functions and user-defined functions is a practical one. Built-ins provide the essential vocabulary that every Python program relies on, implemented in C for performance and available without imports. User-defined functions extend that vocabulary with domain-specific operations. Understanding the difference helps you choose the right tool, avoid shadowing bugs, and write functions that feel like natural extensions of the language.