Skip to main content
PROGRAMMING / PYTHON / LESSON 06

Functions & Reusable Code.

Stop copying the same logic into every script. Learn to define functions, pass inputs, return values, and build small reusable IT reporting tools.

Beginner → Intermediate60–75 minutesInteractive function walkthroughReal Python workbench
00

What you'll learn

So far, we've used variables, conditions, loops and structured data to make reports. Functions let us give those operations names, test them independently, and reuse them without repeating implementation details.

  • Define and call functions without confusing definition with execution.
  • Pass values using positional and keyword arguments and describe parameters.
  • Explain return versus print and recognize implicit None.
  • Keep variables local and understand when mutable inputs can change.
  • Use defaults safely and avoid shared mutable default arguments.
  • Compose, document and test functions using realistic IT datasets.
  • Compare generator expressions with explicit counting loops before using them in a summary.
How to learn it: Read the concept, predict the result, step through a function call, then prove your understanding in the real Python editor. The lesson comes before the coding clinic.
NAME A REPEATABLE ACTION
01

Define it once, call it when needed

A function gives a group of instructions a name. The def keyword creates the function, but its indented body does not execute until you call that name. This difference matters in IT automation: defining a device-health check does not contact or inspect anything by itself. Functions make repeated operations understandable and prevent copy-and-paste logic from drifting into inconsistent versions.

Definition versus execution

def label(host): creates a function object. label('SW-01') calls it. If you never call a function, its body does not run.

Indentation is part of the interface

Statements indented under def belong to the function. Statements at the left margin continue running in the surrounding module.

PYTHON / REASON THROUGH THE CALLPredict before you run
def show_device(host):
    print(f'Checking {host}')

print('Start')
show_device('SW-01')
show_device('AP-02')
What prints first, and how many times does the function body execute? Reveal explanation after you predict.

Start prints first. Then Checking SW-01 and Checking AP-02 print, because show_device is called twice. Defining it alone prints nothing.

Remember: Definition creates a callable; calling it executes the body. Use clear verb-based names.
PASS INFORMATION INTO A FUNCTION
02

Parameters and arguments are different

A parameter is the name written in a function definition, while an argument is the actual value provided at a call site. Each call binds its argument to a new local parameter name. This is how one function works for many hostnames or measurements. Passing the correct number of arguments matters: Python raises TypeError for missing required arguments or extra positional arguments.

Parameter is the placeholder

def classify_latency(milliseconds): introduces milliseconds as a local parameter; the name can take a different value on every call.

Argument is the supplied value

classify_latency(220) passes integer 220. The caller controls the input, while the function specifies what it needs to operate.

PYTHON / REASON THROUGH THE CALLPredict before you run
def classify_latency(milliseconds):
    if milliseconds >= 200:
        return 'critical'
    return 'normal'

print(classify_latency(35))
print(classify_latency(240))
Does changing the argument change the function definition? Reveal explanation after you predict.

No. The same definition executes twice with two different values bound to milliseconds, printing normal followed by critical.

INTERACTIVE FUNCTION CALL EXPLORER / SIMULATED EXECUTION

Follow the value into—and out of—a function.

Select a function, adjust the input, then step through the call. Switch between return and print to see why they are not interchangeable.

PYTHON / FUNCTION EXAMPLE
def classify_latency(milliseconds):
    result = 'critical' if milliseconds >= 200 else 'normal'
    return result

level = classify_latency(240)
STEP 1 / 5 — Call site

The caller evaluates the argument and invokes the named function.

classify_latency(240)
CONSOLE OUTPUT(none)
VALUE AT CALLER"critical"

This visualizer models function behavior; use the real Python workbench below to execute and inspect actual code. All sample values are fictional.

Remember: Parameters describe the inputs a function needs; arguments supply those inputs during each call.
VALUES MUST GET BACK TO THE CALLER
03

Return a value instead of only printing

print displays text but does not make a computed result available to the caller. return sends an object back to the expression that called the function and exits that call. A function that reaches the end without an explicit return returns None. If you assign the result of a print-only helper, the variable will contain None—not the displayed message. Returning values makes functions composable and testable.

Printing is a side effect

A printed line is useful for visibility, but calling code cannot calculate with a console message unless it also receives the value.

Return stops the current call

return exits the function immediately. A later statement in that call does not execute; the caller can store or pass the returned value.

PYTHON / REASON THROUGH THE CALLPredict before you run
def sum_ports(a, b):
    return a + b

count = sum_ports(2, 3)
print(count + 1)
What does count contain? What changes if return becomes print? Reveal explanation after you predict.

count is the integer 5, and the output is 6. Replacing return with print would print 5 inside the helper but assign None to count, causing TypeError at count + 1.

Remember: Use return for data the caller needs. Use print for a message a human should see.
KEEP INTERNAL DETAILS INTERNAL
04

Local scope and clean boundaries

Variables assigned inside a function normally belong to that function's local scope. They do not become available at module level just because the function ran. Using return makes the boundary explicit. An immutable argument such as a string cannot be altered in place, while mutating a list or dictionary passed into a function can affect the caller; choose whether to return a new value or intentionally modify an object.

Local names stay local

Inside normalize(host), the name cleaned refers to the lowercase version; trying to print cleaned outside raises NameError unless it is returned and assigned.

Avoid accidental shared mutation

A function that appends to an input list changes the caller's list. Build a new list for filtering unless mutation is an intentional documented behavior.

PYTHON / REASON THROUGH THE CALLPredict before you run
def normalize(host):
    cleaned = host.strip().upper()
    return cleaned

raw = ' sw-01 '
result = normalize(raw)
print(result)
print(raw)
Why does the last line still contain spaces and lowercase letters? Reveal explanation after you predict.

normalize creates and returns a new string. The original raw value is unchanged; cleaned is a local name that is not available outside the function.

Remember: Treat input parameters as explicit boundaries; prefer returning a new result instead of relying on global state.
DESIGN A FRIENDLY INTERFACE
05

Defaults and keyword arguments

A default parameter provides a value when callers omit that argument. Keyword arguments make the purpose of supplied values clear and can be used in a different order from the definition. Put required parameters before parameters with defaults. Default values are evaluated when the def statement runs, so avoid a shared mutable default list or dictionary: use None and create a fresh collection inside the function when needed.

Optional does not mean unknown behavior

def ticket(host, state='unknown'): makes the omission explicit. It does not silently label unreported devices healthy.

Avoid mutable defaults

Prefer def collect(value, items=None): then if items is None: items = []. Using items=[] directly can share the same list across calls.

PYTHON / REASON THROUGH THE CALLPredict before you run
def ticket_label(host, state='unknown'):
    return f'{host}: {state}'

print(ticket_label('SW-01'))
print(ticket_label(state='offline', host='AP-02'))
Which value is used for state on the first call, and why does the second call work? Reveal explanation after you predict.

The first call uses the default string unknown. The second uses named arguments, which bind to their parameter names regardless of order.

Remember: Defaults clarify optional inputs; keywords improve readability. Never use a mutable object as a shared default.
SMALL PARTS BUILD USEFUL SCRIPTS
06

Compose helpers into a workflow

One function can call another. This is where Python becomes much easier to maintain: a status-normalization helper can be used by a reporting function, so the same rules apply in every place. Each function should have one clear responsibility. Keep input parsing, business logic and printing separate whenever practical; this makes testing individual behaviors much simpler.

Reuse one rule

normalize_status handles extra whitespace and capitalization. A report should call it instead of copying .strip().lower() expressions in several different loops.

Create useful return contracts

A helper can return a list of offline hostnames; the caller can sort, count, format or display that list independently.

PYTHON / REASON THROUGH THE CALLPredict before you run
def normalize_status(value):
    return value.strip().lower()

def needs_attention(device):
    return normalize_status(device['status']) == 'offline'

print(needs_attention({'status': ' OFFLINE '}))
Which function runs first after needs_attention is called? Reveal explanation after you predict.

needs_attention starts executing; while evaluating its return expression, it calls normalize_status, receives 'offline', and compares it to 'offline'. The final printed value is True.

Remember: Good helpers have one responsibility and a predictable return value.
CHECK THE CONTRACT, NOT JUST ONE DEMO
07

Docstrings, edge cases and input checks

Functions are interfaces used by other code. A short docstring can explain what an input represents, what is returned and what errors may occur. Then test normal inputs and boundary cases: an empty list, zero, a missing field or an unexpected type. A function that works for one example but fails a second sample is not yet reliable. Use descriptive exception messages instead of silently replacing invalid measurements with made-up values.

Document behavior close to the code

The first string in a function can be its docstring. Describe arguments and returns, not a play-by-play of obvious syntax.

Test realistic boundaries

If the requirement includes '200 or more', test 199, 200 and 201. If a function counts devices, test an empty inventory as well as mixed statuses.

PYTHON / REASON THROUGH THE CALLPredict before you run
def is_critical(latency_ms):
    """Return True for latency at or above 200 ms."""
    return latency_ms >= 200

print(is_critical(199))
print(is_critical(200))
What changes between the two calls, and why is this a useful test? Reveal explanation after you predict.

The results are False then True. Testing the exact boundary reveals whether the function implements >= 200 rather than > 200.

Remember: A reusable function earns trust through clear inputs, a documented return contract and multiple tests.
SHORT EXPRESSIONS, SAME LOGIC
08

Read generator expressions before using them

A generator expression looks like (value for item in items if condition). Unlike a list comprehension, it produces values as another operation asks for them; it does not immediately build the whole list. You may see sum(1 for device in devices if is_offline(device)), but it is simply counting one for each matching device. Begin with an explicit count and loop to understand the intent before choosing the shorter form. For beginners, clarity is more important than cleverness.

First write the understandable loop

Set count = 0, visit every device, and increase the count only when its status matches. This makes every decision visible in the trace.

Then read the compact version

sum(1 for status in statuses if status == 'offline') adds one for each match. Here sum consumes the generated ones, so you don't need an intermediate list.

PYTHON / REASON THROUGH THE CALLPredict before you run
statuses = ['online', 'offline', 'offline']
count = 0
for status in statuses:
    if status == 'offline':
        count += 1
short_count = sum(1 for status in statuses
                  if status == 'offline')
print(count, short_count)
Why is 1 inside the generator expression? Does the generator create a list of hostnames? Reveal explanation after you predict.

Each matching status contributes the integer 1 to sum, which totals 2. It does not create a list of hostnames; a generator yields each value on demand. The expanded for loop is equally correct and often easier to debug.

Remember: Read sum(1 for ... if ...) as 'count the matches.' Prefer a plain loop when you are still learning the steps.
REUSABLE IT REPORTING
09

Build an inventory report from two helpers

A small health-report workflow can normalize status text, filter a list of dictionaries and return a summary. It is easier to reuse than one large script because data cleaning can change without rewriting the whole report. Use fictional inventory records here; the browser lesson does not connect to customer devices. The next milestone will introduce file and structured-data inputs so the same helpers can process stored reports.

Keep transformations pure

Read each record and build a new result; do not remove entries from the original inventory while looping.

Report without hiding unknowns

Count offline devices separately from unknown statuses. An absent status is not proof of online health.

PYTHON / REASON THROUGH THE CALLPredict before you run
def is_offline(device):
    return device.get('status', 'unknown').strip().lower() == 'offline'

def count_offline(devices):
    return sum(1 for device in devices if is_offline(device))

records = [{'status': ' OFFLINE '}, {'status': 'online'}, {}]
print(count_offline(records))
Why is the count one rather than two? Reveal explanation after you predict.

Only the first record normalizes to offline. The last record has no status, so get supplies unknown, which is not treated as offline.

Remember: A good automation-ready script separates business rules into small functions and tests them with different datasets.
10 / APPLY IT YOURSELF
10

Give your first reusable helper a return value

Run actual Python in your browser. The first exercise repairs a print-only hostname formatter and verifies it with two inputs. Next, move into arguments, defaults, scope and a two-function inventory report.

JOELEARNS / PYTHON WORKBENCH

Write it. Run it. Understand it.

Define reusable helpers, pass different arguments, and return results your caller can use. Verify each case in real Python.

Python 3.14 · in-browser
CHALLENGE 01 / 6 · BEGINNER

Return a reusable hostname label

The fictional Northstar inventory needs consistent uppercase hostname labels. Fix label_host() so it returns an uppercase trimmed hostname rather than printing it. Store both call results in first and second; print them on separate lines as 'SW-01' and 'AP-02'. The function must work on two different inputs.

EXPECTED OUTPUTSW-01 AP-02
script.py editableCtrl / Cmd + Enter to run
Your code is stored only in session storage when enabled. Never paste secrets. Clearing this option clears saved drafts, not the editor.
Code executes only when you choose Run or Check. No account needed.
HINTS · OPTIONAL

Try solving it before asking for help. Hints never show a completed program.

PYTHON OUTPUTReady

Your printed output and errors will appear here.

VARIABLE TRACER

Run the code, then step through the executed lines to watch variable values change.

About this environment: The Python runtime is loaded on demand from a version-pinned CDN and runs inside a sandboxed, opaque-origin browser frame. Runs have a time limit. OS/network automation is outside this introductory workbench. If your code calls input(), a response panel appears to supply text to those calls. Don't paste secrets or untrusted scripts.

Next required step: complete the 6-challenge Practice milestone →
NEXT STEP / PRACTICE

Write it once. Test it with different inputs.

Six original coding exercises check both console output and important returned variables. Each challenge includes optional progressive hints rather than an immediate completed answer.

Continue to the Functions coding clinic →
11

Reference material

Refer to the official Python documentation for function definitions, arguments and return behavior. You can return to these references as your scripts get more complex.

NEXT REQUIRED STEP · PYTHON FUNDAMENTALS

Complete the Practice milestone

You've reached the end of this written lesson. Your next step is practice—complete Python Functions & Reusable Code Coding Challenges before proceeding to a new lesson. This is milestone 12 of 18. You can revisit the explanation whenever you need it.