Back to Blog
Writing Clean Python Functions: Code Organization Made Simple

Writing Clean Python Functions: Code Organization Made Simple

August 17, 2026
Updated August 17, 2026
Reynov Christian

By now you have written programs with variables, conditionals, loops, and lists. But if you look back at your practice files, you have probably typed similar lines of code more than once. Functions solve that. A function is a named, reusable block of code, like a recipe you write once and can follow again anytime you need it, without rewriting the steps.

The Anatomy of a Function

def greet(name):
    message = f"Hello, {name}! Welcome to Python."
    return message

print(greet("Chrisnov"))

Every function starts with the keyword def, short for define, followed by a name you choose, a pair of parentheses, and a colon. Inside the parentheses go the function's parameters, the pieces of information it expects to receive. The indented block below is the function's body, the code that runs each time the function is called. The return keyword sends a value back to whoever called the function.

Notice the difference between defining a function and calling it. The def block only describes what the function will do, it does not run yet. Nothing happens until you actually call it by name, like greet("Chrisnov") on the last line. You can define a function once near the top of your file and call it as many times as you want, with different input each time.

Parameters and Arguments

These two words get mixed up constantly, so it helps to separate them clearly. A parameter is the name listed inside the function definition, like name in the example above. An argument is the actual value you pass in when you call the function, like "Chrisnov". In short, parameters live in the definition, arguments live in the call.

Positional Arguments

def describe_pet(name, animal_type):
    print(f"{name} is a {animal_type}.")

describe_pet("Brownie", "dog")

Here, "Brownie" matches name and "dog" matches animal_type, purely based on their order. This is called a positional argument. Swap the order of the two values and Python will swap the meaning too, without any error, which is exactly why order matters so much with this style.

Keyword Arguments

describe_pet(animal_type="dog", name="Brownie")

By naming each argument explicitly, order no longer matters. This line works exactly the same as the positional version above. Keyword arguments are especially useful once a function has several parameters, since they make each call self explanatory without needing to check the function definition.

Default Parameters

def describe_pet(name, animal_type="dog"):
    print(f"{name} is a {animal_type}.")

describe_pet("Brownie")
describe_pet("Milo", "cat")

Giving a parameter a default value with = makes it optional. The first call above uses the default, "dog", since no second argument was given. The second call overrides the default with "cat". One important rule: every parameter with a default value must come after all parameters without one, so def describe_pet(animal_type="dog", name): would raise a SyntaxError.

Return Values

def add(a, b):
    return a + b

def print_sum(a, b):
    print(a + b)

result = add(3, 5)
print(result)          # 8

nothing = print_sum(3, 5)
print(nothing)          # None

This comparison shows a mistake that trips up almost every beginner at some point. add() uses return, so calling it hands back an actual value that you can store in a variable, like result. print_sum() only prints the value to the screen, it never uses return. That means print_sum() technically gives back None, Python's way of saying “nothing here”, which is why nothing prints as None instead of 8. If you plan to use a function's output later in your program, that function needs a return statement, not just a print() call.

A function can also return more than one value at once, separated by commas:

def get_min_max(numbers):
    return min(numbers), max(numbers)

lowest, highest = get_min_max([4, 9, 1, 7])
print(lowest, highest)  # 1 7

Behind the scenes, Python bundles these two values into a tuple, then unpacks them into lowest and highest in one line.

Scope: Local vs Global Variables

city = "Ruteng"  # global variable

def show_city():
    city = "Labuan Bajo"  # local variable, different from the one above
    print(f"Inside the function: {city}")

show_city()
print(f"Outside the function: {city}")

Running this prints “Inside the function: Labuan Bajo”, then “Outside the function: Ruteng”. A variable created inside a function is local. It only exists while that function is running, and it does not affect a variable with the same name outside the function. A variable created outside any function, like city at the top, is global and can be read from anywhere in the file.

This separation is a feature, not a limitation. It means you can freely reuse simple names like total or count inside different functions without them interfering with each other. As a general rule, avoid modifying global variables from inside a function. Pass values in as parameters and get results back with return instead, since that keeps each function predictable and easier to test on its own.

Docstrings: Documenting Your Functions

def calculate_area(width, height):
    """Calculate the area of a rectangle.

    Args:
        width: the rectangle's width, in any unit.
        height: the rectangle's height, in the same unit.

    Returns:
        The area, as width times height.
    """
    return width * height

A docstring is a short description written as the very first line inside a function, wrapped in triple quotes. It explains what the function does, and often what it expects and returns, without needing to read the code itself. Python tools even display docstrings automatically, try running help(calculate_area) in the REPL after defining this function. For small personal scripts a one line docstring is often enough. For anything you plan to reuse or share, a fuller docstring like the one above saves a lot of guessing later, including for yourself in a few months.

Function Design: Do One Thing Well

A common sign that a function has grown too large is a name like process_data() that does not clearly say what it does. Compare that to focused names like clean_whitespace(), convert_to_uppercase(), and remove_duplicates(). Each one does exactly one job, and each one is easy to test, reuse, and explain on its own. If you find yourself struggling to describe what a function does in a single short sentence, it is usually a sign that it should be split into smaller functions.

Practical Project: A Small Utility Function Library

def is_valid_email(email):
    """Check whether a string looks like a basic email address."""
    return "@" in email and "." in email.split("@")[-1]


def celsius_to_fahrenheit(celsius):
    """Convert a Celsius temperature to Fahrenheit."""
    return (celsius * 9 / 5) + 32


def word_count(text):
    """Count the number of words in a string."""
    return len(text.split())


def format_currency(amount, symbol="Rp"):
    """Format a number as a simple currency string, e.g. Rp 15,000."""
    return f"{symbol} {amount:,.0f}"


# Try the library
print(is_valid_email("[email protected]"))   # True
print(is_valid_email("not-an-email"))       # False
print(celsius_to_fahrenheit(18))            # 64.4
print(word_count("Ruteng is cold at night"))  # 5
print(format_currency(15000))               # Rp 15,000

Each of these four functions follows the design principle from the section above: one clear job, a short docstring, and a name that says exactly what it does. A few details are worth slowing down on. is_valid_email() uses email.split("@")[-1] to grab everything after the @ symbol, then checks whether a dot appears in that part. This is a simple check, not a complete email validator, but it is a realistic first pass you would actually use in a beginner project. format_currency() uses the format specifier :,.0f inside the f-string, which adds a comma as a thousands separator and drops any decimal places, turning 15000 into 15,000 automatically.

Once functions like these exist, you can import and reuse them in any future project instead of rewriting the same logic every time, which is exactly the point of building a small utility library early.

Testing Your Functions

You do not need a formal testing framework to start checking your functions. A simple pattern works well while you are learning:

assert celsius_to_fahrenheit(0) == 32
assert celsius_to_fahrenheit(100) == 212
assert word_count("") == 0
print("All checks passed.")

assert checks that a condition is true. If it is, nothing happens and the program keeps running. If it is false, Python raises an AssertionError immediately, pointing you straight to the broken line. Writing a few asserts like this for each function, especially for edge cases like an empty string or the number zero, catches a surprising number of bugs before they ever reach a real user. Python's built in unittest module and the popular third party pytest library both build on this same idea, with much more structure, once your projects grow larger.

Common Beginner Mistakes

  • Forgetting to call the function: defining def greet(name): does nothing by itself. You must also call it, like greet("Chrisnov"), for the code inside to actually run.
  • Confusing print() with return: a function that only prints its result cannot pass that result to other code. If you need to use the value later, use return.
  • Mixing up parameters and arguments: remember, parameters are the names in the function definition, arguments are the actual values you pass in when calling it.
  • Placing default parameters before required ones: def greet(greeting="Hi", name): raises a SyntaxError. Required parameters must always come first.
  • Expecting a local variable to exist outside its function: a variable created inside a function disappears once the function finishes running, unless you return it and store the result in a variable outside.

Try It Yourself

Practice these three short exercises in a new file called practice_functions.py before moving on:

  1. Write a function called is_even(number) that returns True if a number is even, and False otherwise. Test it with both an even and an odd number.
  2. Write a function called greet_user(name, language="en") that prints a greeting in English by default, but prints in Indonesian if language="id" is passed in.
  3. Add a fifth function to the utility library above, called reverse_words(text), that returns a sentence with its words in reverse order. Give it a proper docstring.

Frequently Asked Questions

Do all functions need a return statement?

No. Functions that only perform an action, like printing a message or saving a file, often do not need one. A function without return simply gives back None automatically.

What's the difference between a parameter and an argument?

A parameter is the placeholder name written inside the function definition. An argument is the real value supplied when the function is actually called.

Can a function call another function?

Yes, this is extremely common and often good practice. Breaking a big task into several small functions that call each other usually makes code easier to read and to fix.

How many parameters should a function have?

There is no strict number, but if you find yourself needing more than 4 or 5, it is often a sign the function is trying to do too much and could be split into smaller pieces.

Are docstrings required?

They are not required by Python itself, but they are considered a professional best practice, especially for any function you expect to reuse or share with others.

Ready to organize data with meaningful labels instead of just positions? In our next article, we'll explore Python dictionaries. You'll discover how key-value pairs let you model real-world information more naturally than lists alone, and you'll build a contact management system that puts your new function-writing skills to use as well.

Learning programming works best as a collaborative process, and your questions and insights help make these tutorials more effective for everyone. If you get stuck on scope, mix up parameters and arguments, or come up with a creative addition to the utility function library, please share your experience in the comments. Your feedback helps create a learning community where we can all benefit from each other's discoveries and solutions.

You Might Also Like

Comments

No comments yet — be the first to start the conversation!

Leave a Comment

0/5000