06. Procedures and Functions
Learn how procedures and functions organize programs, accept inputs, return results, manage scope, limit side effects, and support decomposition and abstraction in Python.
The role of
A procedure or function is a named, reusable block of code designed to perform a focused task. Reuse prevents the same logic from being copied throughout a program and gives each operation a clear place to be tested and improved.
The terms are related but are not identical in every language. A procedure usually emphasizes an action, such as displaying text or updating a file. A function usually emphasizes producing a result. In Python, both kinds of routines are defined as functions; a routine that reaches its end without an explicit return produces None.
Takeaway: Use a named routine when an operation is meaningful enough to reuse, test, or understand independently.
Defining and calling reusable routines
A describes what a routine does. It gives the routine a name, lists zero or more parameters, and contains the statements that make up its body. Defining a function normally does not execute its body immediately.
A causes the body to run. For example, a definition named greet can be called as greet(). A function that accepts a name can be called with different arguments, such as greet_person("Maya") or greet_person("Jordan"), so one algorithm can process multiple inputs.
A is the placeholder named in the definition. An is the actual value supplied by the caller. Keeping this distinction clear helps identify whether a problem is in the function's design or in the way it is called.
Takeaway: The definition establishes the routine's interface; the call supplies inputs and starts execution.
Returning results
A function communicates a computed result with a . For example, a function that calculates an area can return the product of its width and height. The calling code can store that result, print it, compare it, or use it in another operation.
Executing return also ends the current . Statements after an executed return are not run. If a Python function reaches the end without returning a value, its result is None.
Printing and returning serve different purposes. Printing makes information visible on the screen, but returning makes the information available to the calling code. A routine that only prints a doubled number therefore cannot provide that number to an assignment in the same way that a routine using return can.
A function should generally return a value when its main purpose is calculation. A routine whose purpose is an observable action may instead perform a procedure-like .
Takeaway: Return data when other code must use the result; print or otherwise perform an action when visibility or external change is the main purpose.
Designing parameters and arguments
Parameters state what information a function requires. A function may have no parameters, one , or several. With positional arguments, the first normally matches the first , the second matches the second, and so on.
Some languages support named arguments and default values. For example, a power-calculating function can use an exponent default of , so a call with base uses exponent , while a call that supplies exponent produces .
A well-designed list makes dependencies visible. Pass information that the function genuinely needs instead of relying unnecessarily on variables outside the function. This makes calls easier to understand and makes the function easier to test with different inputs.
Takeaway: Design parameters around the function's real inputs, and make optional behavior explicit through appropriate defaults when the language supports them.
and local data
determines where a name can be accessed. A created inside a function normally belongs to that function's local context and cannot be accessed directly from outside it.
For example, a function that calculates a total might create local variables named tax and total. The calling code can receive the final result through return, but it cannot normally use tax directly after the function finishes. Each call creates its own local context, so separate calls can calculate different results without sharing their local variables.
A function may be able to read a name from an enclosing or global , but heavy reliance on global variables hides dependencies and makes testing more difficult. Passing required data as parameters and returning results explicitly gives a clearer flow of information.
Takeaway: Keep temporary data local, pass required inputs explicitly, and return outputs rather than depending on hidden global state.
Side effects and predictable behavior
A occurs when a function changes something outside the result it returns. Displaying text, modifying a shared list, changing a file, and updating a user interface are examples.
Side effects are sometimes necessary, but they make behavior harder to predict when they are excessive or hidden. A depends only on its parameters, returns a result, and does not change outside state. For instance, a function that adds two inputs and returns their sum is pure. Repeated calls with the same inputs produce the same output.
Pure functions are usually easier to test because the expected result can be checked without preparing or inspecting unrelated external state. Functions that perform side effects should still have a clear purpose and predictable behavior.
Takeaway: Prefer explicit inputs and outputs for calculations, and isolate necessary side effects in focused routines.
into focused functions
divides a large problem into smaller subproblems. Each subproblem can become a function with one clear responsibility.
Consider a final-result program. One function can calculate a weighted score from a quiz, project, and exam. A second function can convert the score into a letter category. A third function can coordinate the two operations and return the combined result. Separating these responsibilities means that changing grade thresholds does not require rewriting the weighted-score calculation.
A decomposed design also supports independent testing. The calculation can be tested with numerical cases, the category rules can be tested at their boundaries, and the coordinating function can be tested to confirm that it combines the smaller results correctly.
Takeaway: Split a complex task where distinct responsibilities, rules, or calculations can be named and tested separately.
and reusable interfaces
An presents a simple interface while hiding implementation details. A caller using a function named is_even needs to know what input it accepts and what result it provides; the caller does not need to know that the implementation checks a remainder.
A useful function usually has a focused purpose, a descriptive name, clearly defined parameters, and a predictable or . It should also minimize dependence on hidden global state. Documentation strings can record the purpose and expected inputs so that the interface remains understandable to other programmers.
allows an implementation to change while its callers continue to use the same interface. This supports maintenance and reuse because users depend on the operation's behavior rather than on every internal statement.
Takeaway: Design functions around stable, understandable interfaces and hide details that callers do not need to manage.
Common errors and design checks
Several mistakes recur when working with functions:
Confusing printing with returning: A function that prints a result may still return
None, so the displayed value is not automatically available to the caller.Omitting required arguments: A call must provide the inputs required by the function's parameters unless suitable defaults are defined.
Using a outside its : Receive needed data through the function's instead of attempting to access an internal directly.
Giving one function too many responsibilities: A routine that validates input, calculates results, formats reports, and saves files is difficult to test and reuse. Separate focused functions usually provide a clearer design.
When debugging, first check the function's interface: identify its parameters, inspect the arguments supplied by the call, determine whether the desired data is returned or merely printed, and verify which names are in .
Takeaway: Clear interfaces, explicit data flow, and focused responsibilities prevent many function-related errors.
Putting the ideas together
make programs easier to build by giving operations names, inputs, outputs, and boundaries. Parameters and arguments connect a routine to its callers. Return values communicate results, while controls where local data can be used.
turns a complex task into smaller responsibilities. then lets callers use those responsibilities without depending on their internal implementation. Functions with explicit inputs, predictable outputs, and few unnecessary side effects are generally easier to test, reuse, and modify.
A practical design sequence is:
Identify the distinct tasks in the larger problem.
Give each task a focused function name.
Specify the parameters each function genuinely needs.
Decide whether each function should return a result, perform an action, or do both deliberately.
Keep temporary data local and avoid unnecessary global dependencies.
Test the smaller functions before combining them into the larger operation.
Final takeaway: Good functions make a program's structure visible: each routine has a clear responsibility, an understandable interface, and a controlled flow of data.