AlgoMaster Logo

Functions Basics

High Priority22 min readUpdated June 6, 2026
Listen to this chapter
Unlock Audio

A function is a named block of code that you can run as many times as you want by referring to its name. Once a function exists, the rest of your program can lean on it without caring how it works inside, which is the smallest, most useful form of abstraction Python gives you. This lesson covers what a function is, how to define one with def, how to call it, and the conventions around naming.

Why Functions Exist

Code that lives at the top level of a file runs once, top to bottom, in the order it appears. That works for a handful of lines, but the moment you need to do the same thing twice, you have a choice: copy the lines, or wrap them in a function and call the function twice.

Consider a small e-commerce script that prints a welcome banner before showing the cart, then again before showing the order summary. Without functions, the printing logic gets duplicated:

Six lines do one job that should be one line of intent. If the store name changes, you have to find every copy. If you want to add an extra line to the banner, you have to add it in both places. The duplication is the bug waiting to happen.

A function fixes both problems:

The three printing lines now live in one place, behind the name show_welcome_banner. The rest of the program asks for the banner by name and doesn't care how it's drawn. If the banner ever changes, you change it once.

That's the first reason to write a function: reuse. The second reason is just as important: naming. The line show_welcome_banner() reads in plain English. Anyone scanning the file can tell what's happening without parsing three print calls. Even if you only call a function once, giving a stretch of code a name is often worth it on its own.

Functions also separate what from how. The caller knows what show_welcome_banner does (it shows a welcome banner) and doesn't need to know how it does it (three prints with equals signs). That separation is what lets you change the inside of a function later without breaking the rest of the program.

Defining a Function With def

Python uses the def keyword to define a function. The shape is fixed: the keyword, the function name, parentheses, a colon, and then an indented body. Every function you write follows this skeleton.

Six things to notice in those four lines.

  1. def is the keyword that starts a function definition. It tells Python "the next thing is a new function".
  2. show_welcome_banner is the function's name. You'll use this name later to call it.
  3. () is the empty parameter list. This function takes no inputs, but the parentheses are still required.
  4. : ends the header. The colon is what tells Python "the body starts now".
  5. The body is indented. Python uses indentation, not braces, to mark which lines belong to the function. Four spaces is the standard.
  6. The body has three statements. They run together, in order, every time the function is called.

The body can be any valid Python: prints, assignments, loops, conditionals, calls to other functions, anything. The only rule is that it has to be indented under the header. The function ends at the first line that's no longer indented.

Two lines belong to display_cart_header because they're indented. The third print is back at the top level, so it's outside the function. Notice the output: only the top-level line ran. Defining a function does not run it. That's the next thing to cover.

The cyan box is the header, the orange boxes are the indented body, and the teal box is the first line that ends the function (because it's back at column zero). Python uses indentation alone to decide where the function stops.

If you forget the colon at the end of the header, Python raises a SyntaxError before the program runs:

The error points right at the spot where the colon should be. Python won't even try to run the file until you fix it. The same kind of error fires if you forget the parentheses, or if you indent the body wrong.

Calling a Function

Defining a function tells Python "here's a recipe". Calling a function tells Python "follow that recipe right now". The two are completely separate steps, and beginners often confuse them.

To call a function, write its name followed by parentheses:

The first four lines define the function. Nothing prints when those lines run; Python just registers the name show_welcome_banner and remembers the body. The fifth line, show_welcome_banner(), is the call. That's when the body actually runs.

The parentheses are what make it a call. Without them, you're just referring to the function as a value, not asking Python to run it.

The line show_welcome_banner (no parentheses) is a valid Python expression. It produces the function object, but Python doesn't do anything with it, so nothing happens. The next line, show_welcome_banner(), has the parentheses, so Python runs the body.

You can call the same function as many times as you want, in any order, from anywhere in your code (as long as it's defined first).

Each call runs the body independently. The function doesn't "remember" that it ran before; every call starts fresh.

One small thing about ordering: Python reads the file top to bottom, so the def has to appear before the call. Calling a function that hasn't been defined yet is a NameError:

By the time Python reaches the first line, it hasn't seen the def yet, so the name show_welcome_banner doesn't exist. The fix is just to put the def before the call.

This is the call cycle: the caller hits a call, Python jumps into the function body, runs it to completion, and then comes back to the line right after the call. Anything you write below the call won't run until the function finishes. For a function with three prints, that's nearly instant. For a function that does heavy work, it could take a while, but the rule is the same.

Defining vs Calling: The Difference That Trips People Up

Defining a function and calling a function are two separate events, often pages apart in real code. The def block sets up the recipe. The call runs it. Mixing these up is one of the first stumbles new Python programmers hit.

A definition runs once, when Python reaches the def line. It doesn't execute the body; it just files the body away under the function's name. A call can happen zero, one, or many times after that, and each call runs the body again.

Read the output in order. The two top-level prints around the def run as Python passes through them. The def itself doesn't print anything, so there's no output for it. Then greet_customer() runs the body, which prints the greeting. Then the last top-level print runs. The function's body printed exactly once because we called it exactly once.

If you define a function but never call it, the body simply never runs:

print_order_confirmation is defined, but no line in the script calls it. The two prints inside the body produce nothing. Python knows the function exists (you could call it from the REPL after the script finishes if you imported it), but on its own a definition is silent.

The opposite mistake also happens: people put a call where they meant to put a definition, or vice versa. A common version is calling the function inside its own definition by accident:

This is a recursive function with no base case, which is a separate topic. The point for now is that the syntax is legal even though the behavior is broken. Python doesn't stop you from writing code that calls a function from inside its own body. Recursion is covered in a much later chapter; until then, keep function bodies free of calls to themselves.

The mental model that helps the most is this: think of def as "save this recipe under this name" and the call as "cook the recipe now". The recipe can sit on the shelf forever. Cooking it requires explicitly grabbing it and going.

Functions Without Return Values

Some functions exist purely to do something: print a banner, write a file, log a message. They don't hand a value back to the caller. The function bodies we've seen so far are all in this group: they call print, but they don't produce a result you can store in a variable.

In Python, every function returns something. If you don't say what, the function returns None. That's the special value Python uses to mean "nothing of interest here". It's a value, but it's the kind of value you generally don't do anything with.

The function body printed its greeting and stopped. The call expression greet_customer() produced None, which we stored in result. Printing result shows the word None, which is Python's way of saying "the function didn't have a meaningful answer".

You don't normally write code that captures the return of a print-only function. The example above is just to show what's there. In practice, you'd write the call as a statement on its own line:

No assignment, no use of the return value, just "do the thing". This style is fine and very common. Functions that exist for their side effects (printing, writing, modifying something) are sometimes called procedures in other languages, but in Python they're just functions that happen to return None.

The return statement is what you use when you want a function to produce a value the caller can use. For now, two things to remember:

  1. A function without a return returns None.
  2. The keyword return exists for producing values.

Until then, the functions in this lesson will do their work through print.

Naming Functions

A function name is just an identifier, the same kind of name you'd use for a variable. The same rules apply: it must start with a letter or underscore, can contain letters, digits, and underscores, and can't be a Python keyword. But the conventions are different from variables in a few subtle ways.

The most important convention is snake_case: lowercase words separated by underscores. Python's official style guide, PEP 8, recommends this for both function names and variable names, and the standard library uses it everywhere.

These names read naturally and tell you what each function does at a glance. Compare them to less helpful styles:

ShowWelcomeBanner uses PascalCase, which Python reserves for class names. Mixing it into function names confuses readers who expect the convention. showwelcomebanner is technically valid but hard to read because the words run together. swb is short but tells you nothing. doStuff is camelCase (more common in JavaScript) and vague to boot. None of these are illegal; they just go against the grain of how Python code is normally written.

A good function name is a verb phrase that describes what the function does. Functions act, so their names should reflect action.

PatternExamples
verb_objectshow_banner, print_total, send_email, clear_cart
verb_for_objecttotal_for_cart, tax_for_order, discount_for_customer
is_/has_ (predicates)is_empty, has_discount, is_in_stock
get_ for retrievalget_customer_email, get_cart_total

The shape of the name should hint at what kind of work the function does. Boolean-returning functions (yes/no questions) usually start with is_ or has_. Functions that produce a value usually start with get_, compute_, calculate_, or use a noun-phrase like cart_total. Functions that just do something with no useful return often start with a plain verb: print_, show_, display_, save_, send_.

Each name reads like a description of what's about to happen. The calling code is essentially a list of small, named actions. That's the kind of code that's pleasant to come back to a month later, because the names do most of the explaining.

A few practical rules of thumb for naming:

  • Prefer clarity over brevity. print_order_confirmation is better than poc.
  • Don't shadow built-ins. Naming a function list or sum or print will hide the built-in of the same name, which causes weird bugs later.
  • Don't reuse the same name for very different things. If process_order does three different things in different files, readers can't trust the name.
  • One thing per function. If a function name needs the word and in it (save_and_email_receipt), it's probably doing two things; split it.

One forward note: Python lets you attach a short description called a docstring to a function, written as a string literal on the first line of the body. Docstrings are how you document what a function does, what its parameters mean, and what it returns. Good names plus a one-line docstring carry a lot of weight.

Where Functions Live

A function definition has to appear somewhere in your code. The most common place is at the module level: directly inside a .py file, not indented under anything else. Functions defined at the module level can be called from anywhere in that file (below their def), and they're what other files import when they do from yourfile import some_function.

The def line starts at column 0, and the body is indented one level. That makes show_welcome_banner a module-level function. Most functions you write will live here.

Python also lets you define a function inside another function. That's called a nested function (or sometimes an inner function), and it's a real feature, not a syntax accident.

print_divider is defined inside show_order_summary. Outside the outer function, the name print_divider doesn't exist; it's only meaningful while show_order_summary is running. Each time show_order_summary is called, Python re-creates the nested function.

Nested functions are useful for a few specific things: hiding a helper that's only relevant to one outer function, capturing values from the surrounding scope (called a closure), and a handful of patterns around decorators and callbacks. The point for now is that nested functions are legal Python, and you'll see them in real codebases.

Don't reach for nested functions in your first programs. Module-level functions are the default for a reason: they're easier to read, easier to test in isolation, and easier to reuse from other parts of your code. A function buried inside another function is harder to find and harder to call from outside. Use them when there's a clear reason; otherwise, keep your def lines at column 0.

A small rule of thumb: if a helper function is only ever called from one other function, and the body is short, a nested definition can be cleaner. If the helper might be useful elsewhere, define it at the module level so other callers can reach it too.

The diagram shows the layout: the file (cyan) holds two module-level functions (orange) and the top-level calls (green). One of those module-level functions has a nested helper (teal) that lives only inside it. From outside the file, you can import the two module-level functions, but not the nested one.

A Slightly Bigger Example

Putting the ideas above together, here's a small e-commerce script that uses several small functions to print a tidy order summary. None of the functions take parameters or return values; they just do their part and finish.

The four def blocks come first. None of them run when defined; Python just registers the four names. Then four calls produce the full output, one piece at a time. The script as a whole reads like an outline of the page: banner, cart header, cart items, confirmation. Each function name is a label for its piece.

The thing this example can't yet handle is variation. Every call produces the same output, because the functions have no way to receive data from the caller. display_cart_items always prints the same three lines, even if the cart is different. To make these functions useful for real carts, real customers, and real orders, we need a way to feed information into them. That's what parameters are for.

Quiz

Functions Basics Quiz

10 quizzes