Skip to main content

📦 Lesson 4.1: Defining and Calling Functions

Functions are the building blocks of organized code. Instead of copying and pasting the same logic everywhere, you give it a name and call it whenever you need it.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Define functions in Python (def), JavaScript (function), and C# (method syntax)
  • Call functions and understand the define-then-call pattern
  • Write functions that accept parameters and return values
  • Understand JavaScript's arrow functions as an alternative syntax
  • Recognize the difference between functions and methods in C#
  • Write functions that call other functions

Estimated Time: 45 minutes

Project: A temperature converter with functions for each conversion direction

📑 In This Lesson

Why Functions?

Imagine you're writing a program that greets users in multiple places. Without functions, you'd repeat the greeting logic every time:

# Without functions — lots of repetition
print("================================")
print("Welcome, Alice!")
print("Today is Monday.")
print("================================")

# ... later in the program ...
print("================================")
print("Welcome, Bob!")
print("Today is Monday.")
print("================================")

# ... and again ...
print("================================")
print("Welcome, Charlie!")
print("Today is Monday.")
print("================================")

Functions solve three problems at once:

  • Don't Repeat Yourself (DRY): Write the logic once, call it many times
  • Readability: greet_user("Alice") is clearer than 4 lines of print statements
  • Maintainability: Need to change the greeting? Update one function, not twenty copy-pasted blocks
graph LR A["Main Program"] --> B["greet_user('Alice')"] A --> C["greet_user('Bob')"] A --> D["greet_user('Charlie')"] B --> E["Function:
greet_user(name)"] C --> E D --> E style E fill:#10b981,stroke:#059669,color:#fff,stroke-width:2px

Defining a Function

A function definition tells the computer: "Here's a block of code I want you to remember. I'll tell you when to run it." Defining a function does not run it — you need to call it separately.

# Python: def keyword + function name + colon
def say_hello():
    print("Hello, world!")
    print("Functions are awesome!")

# The function is defined but NOT run yet.
# Nothing has been printed so far.
// JavaScript: function keyword + name + curly braces
function sayHello() {
    console.log("Hello, world!");
    console.log("Functions are awesome!");
}

// The function is defined but NOT run yet.
// Nothing has been logged so far.
// C#: return type + name + curly braces (inside a class)
static void SayHello()
{
    Console.WriteLine("Hello, world!");
    Console.WriteLine("Functions are awesome!");
}

// "static" = belongs to the class, not an instance (more on this later)
// "void" = this function doesn't return a value
// The function is defined but NOT run yet.

Anatomy of a Function Definition

Part 🐍 Python ⚡ JavaScript 🔷 C#
Keyword def function (return type instead)
Name snake_case camelCase PascalCase
Parameters In parentheses () In parentheses () In parentheses with types
Body Indented block Curly braces {} Curly braces {}
Return type Not declared Not declared Required (void, int, etc.)

💡 Naming Conventions Matter

Each language has its own naming style. Python uses snake_case (lowercase with underscores), JavaScript uses camelCase (lowercase start, capitalize each new word), and C# uses PascalCase (capitalize every word, including the first). Using the wrong convention won't break your code, but it will look wrong to experienced developers in that language.

Calling a Function

To actually run a function, you call it by name with parentheses. The parentheses are required even when there are no parameters.

def say_hello():
    print("Hello, world!")

# Call it — now it runs!
say_hello()          # Hello, world!

# Call it again — it runs again!
say_hello()          # Hello, world!

# Common mistake: forgetting the parentheses
# say_hello   ← This just references the function, doesn't call it
# print(say_hello)  → <function say_hello at 0x...>
function sayHello() {
    console.log("Hello, world!");
}

// Call it
sayHello();          // Hello, world!

// Call it again
sayHello();          // Hello, world!

// Without parentheses, you get the function itself:
// console.log(sayHello);  → [Function: sayHello]
// In C#, functions (methods) live inside a class
class Program
{
    static void SayHello()
    {
        Console.WriteLine("Hello, world!");
    }

    static void Main(string[] args)
    {
        // Call it
        SayHello();      // Hello, world!

        // Call it again
        SayHello();      // Hello, world!
    }
}

⚠️ Define Before You Call (Usually)

In Python, you must define a function before you call it — Python reads top to bottom. JavaScript is more forgiving: regular function declarations are "hoisted" (moved to the top automatically). In C#, method order within a class doesn't matter. For clarity, it's best practice in all languages to define functions before using them.

🎓 Instructor Note: Delivery Guidance

The "define vs. call" distinction is the single most important concept here. Students frequently define a function and then wonder why nothing happened. Do a live demo: define a function, show that nothing prints, then add the call and watch it come alive. Also demonstrate the "forgot the parentheses" mistake — it's subtle and confusing for beginners. In Python it silently returns the function object; in JS it's similar. Only C# gives a compile error if you try to use a method name without calling it.

Functions with Parameters

Parameters let you pass data into a function so it can work with different values each time. Think of parameters as placeholders that get filled in when the function is called.

graph LR A["greet('Alice')"] -->|"name = 'Alice'"| B["function greet(name)"] B --> C["'Hello, Alice!'"] D["greet('Bob')"] -->|"name = 'Bob'"| B style B fill:#3b82f6,stroke:#2563eb,color:#fff,stroke-width:2px
# One parameter
def greet(name):
    print(f"Hello, {name}!")

greet("Alice")     # Hello, Alice!
greet("Bob")       # Hello, Bob!

# Multiple parameters
def introduce(name, age, city):
    print(f"I'm {name}, {age} years old, from {city}.")

introduce("Alice", 30, "New York")
introduce("Bob", 25, "London")
// One parameter
function greet(name) {
    console.log(`Hello, ${name}!`);
}

greet("Alice");     // Hello, Alice!
greet("Bob");       // Hello, Bob!

// Multiple parameters
function introduce(name, age, city) {
    console.log(`I'm ${name}, ${age} years old, from ${city}.`);
}

introduce("Alice", 30, "New York");
introduce("Bob", 25, "London");
// One parameter — must declare the type
static void Greet(string name)
{
    Console.WriteLine($"Hello, {name}!");
}

Greet("Alice");     // Hello, Alice!
Greet("Bob");       // Hello, Bob!

// Multiple parameters — each needs a type
static void Introduce(string name, int age, string city)
{
    Console.WriteLine($"I'm {name}, {age} years old, from {city}.");
}

Introduce("Alice", 30, "New York");
Introduce("Bob", 25, "London");

💡 Parameter vs. Argument

Technically, parameters are the names in the function definition (name, age). Arguments are the actual values you pass when calling the function ("Alice", 30). Most people use the terms interchangeably in conversation, and that's fine — but knowing the distinction helps when reading documentation.

⚠️ C# Requires Types on Parameters

Python and JavaScript don't require type declarations for parameters — they accept any value. C# requires you to specify the type of each parameter (string name, int age). If you pass the wrong type, C# catches it at compile time. This is a trade-off: more typing upfront, but fewer bugs at runtime.

Return Values

Functions can send a value back to the code that called them. This is how you use a function to compute something rather than just do something.

graph LR A["result = add(3, 5)"] -->|"a=3, b=5"| B["function add(a, b)"] B -->|"return 8"| C["result = 8"] style B fill:#3b82f6,stroke:#2563eb,color:#fff,stroke-width:2px style C fill:#10b981,stroke:#059669,color:#fff,stroke-width:2px
# A function that returns a value
def add(a, b):
    return a + b

# Capture the return value in a variable
result = add(3, 5)
print(result)          # 8

# Use the return value directly in expressions
print(add(10, 20))     # 30
total = add(1, 2) + add(3, 4)   # 3 + 7 = 10

# A function without return gives None
def say_hi():
    print("Hi!")

value = say_hi()       # Prints "Hi!"
print(value)           # None
// A function that returns a value
function add(a, b) {
    return a + b;
}

// Capture the return value
let result = add(3, 5);
console.log(result);       // 8

// Use directly in expressions
console.log(add(10, 20));  // 30
let total = add(1, 2) + add(3, 4);  // 3 + 7 = 10

// A function without return gives undefined
function sayHi() {
    console.log("Hi!");
}

let value = sayHi();       // Logs "Hi!"
console.log(value);        // undefined
// Return type goes before the function name
static int Add(int a, int b)
{
    return a + b;
}

// Capture the return value
int result = Add(3, 5);
Console.WriteLine(result);       // 8

// Use directly in expressions
Console.WriteLine(Add(10, 20));  // 30
int total = Add(1, 2) + Add(3, 4);  // 10

// "void" means no return value
static void SayHi()
{
    Console.WriteLine("Hi!");
    // No return statement needed (or use: return;)
}

SayHi();  // Prints "Hi!"
// int value = SayHi();  // ❌ Compile error — void can't be assigned

Key Differences in Return Behavior

Behavior 🐍 Python ⚡ JavaScript 🔷 C#
Declare return type? No No Yes (required)
No return statement Returns None Returns undefined Must be void
Return multiple values? Yes (tuples) No (use objects/arrays) No (use tuples or out params)
# Python can return multiple values as a tuple
def get_min_max(numbers):
    return min(numbers), max(numbers)

low, high = get_min_max([3, 1, 4, 1, 5, 9])
print(f"Min: {low}, Max: {high}")   # Min: 1, Max: 9
// JavaScript: return an object or array
function getMinMax(numbers) {
    return {
        min: Math.min(...numbers),
        max: Math.max(...numbers)
    };
}

let { min, max } = getMinMax([3, 1, 4, 1, 5, 9]);
console.log(`Min: ${min}, Max: ${max}`);  // Min: 1, Max: 9
// C#: return a tuple
static (int Min, int Max) GetMinMax(int[] numbers)
{
    return (numbers.Min(), numbers.Max());
}

var result = GetMinMax(new[] { 3, 1, 4, 1, 5, 9 });
Console.WriteLine($"Min: {result.Min}, Max: {result.Max}");

Arrow Functions (JavaScript)

JavaScript has a second, more compact way to write functions: arrow functions. They're especially popular for short, one-line functions.

# Python's equivalent: lambda (limited to one expression)
add = lambda a, b: a + b
print(add(3, 5))  # 8

# Lambdas are limited — no multiple statements, no loops
# Use def for anything more than a simple expression:
def add(a, b):
    return a + b

# Lambdas are most useful in callbacks:
numbers = [3, 1, 4, 1, 5]
sorted_nums = sorted(numbers, key=lambda x: -x)
print(sorted_nums)  # [5, 4, 3, 1, 1]
// Regular function
function add(a, b) {
    return a + b;
}

// Arrow function (same thing, shorter)
const add2 = (a, b) => {
    return a + b;
};

// Arrow with implicit return (one expression — drop {} and return)
const add3 = (a, b) => a + b;

// Single parameter — parentheses optional
const double = x => x * 2;
console.log(double(5));  // 10

// All three forms work the same:
console.log(add(3, 5));   // 8
console.log(add2(3, 5));  // 8
console.log(add3(3, 5));  // 8

// Arrow functions are everywhere in modern JavaScript:
const numbers = [3, 1, 4, 1, 5];
const sorted = [...numbers].sort((a, b) => a - b);
console.log(sorted);  // [1, 1, 3, 4, 5]
// C# has lambda expressions (similar to JS arrow functions)
// Used mainly with delegates and LINQ

// Traditional method
static int Add(int a, int b)
{
    return a + b;
}

// Lambda assigned to a Func variable
Func<int, int, int> add2 = (a, b) => a + b;
Console.WriteLine(add2(3, 5));  // 8

// Lambda with a single parameter
Func<int, int> doubleIt = x => x * 2;
Console.WriteLine(doubleIt(5));  // 10

// Lambdas shine in LINQ:
int[] numbers = { 3, 1, 4, 1, 5 };
var sorted = numbers.OrderBy(x => x).ToArray();
// [1, 1, 3, 4, 5]

✅ When to Use Arrow Functions (JS)

Use arrow functions for short, simple functions — especially callbacks and one-liners. Use regular function declarations for named, standalone functions that are the main building blocks of your code. As a beginner, start with regular functions and add arrows when you're comfortable.

Functions in C# — They're Called Methods

In C#, every function lives inside a class. Because of this, C# calls them "methods" instead of "functions." For now, we use static methods so we can call them without creating objects (we'll cover objects in Module 6).

# Python: functions can live anywhere — no class required
def calculate_area(width, height):
    return width * height

def calculate_perimeter(width, height):
    return 2 * (width + height)

# Just call them directly
area = calculate_area(5, 3)
perimeter = calculate_perimeter(5, 3)
print(f"Area: {area}, Perimeter: {perimeter}")
// JavaScript: functions can live anywhere too
function calculateArea(width, height) {
    return width * height;
}

function calculatePerimeter(width, height) {
    return 2 * (width + height);
}

let area = calculateArea(5, 3);
let perimeter = calculatePerimeter(5, 3);
console.log(`Area: ${area}, Perimeter: ${perimeter}`);
// C#: methods must live inside a class
class Program
{
    // "static" lets us call without an object instance
    // "double" is the return type
    static double CalculateArea(double width, double height)
    {
        return width * height;
    }

    static double CalculatePerimeter(double width, double height)
    {
        return 2 * (width + height);
    }

    static void Main(string[] args)
    {
        double area = CalculateArea(5, 3);
        double perimeter = CalculatePerimeter(5, 3);
        Console.WriteLine($"Area: {area}, Perimeter: {perimeter}");
    }
}

// Don't worry about "static" and "class" too much yet.
// Just know: every C# function needs a class to live in.

💡 Top-Level Statements (C# 9+)

Modern C# (9 and later) lets you write simple programs without the class and Main boilerplate. The compiler wraps your code in a class behind the scenes. However, if you define methods, you'll still need to put them in a class or use local functions. We show the full structure here so you understand what's happening.

Functions Calling Functions

Functions can call other functions. This is how you break complex problems into smaller, manageable pieces — each function handles one job.

def celsius_to_fahrenheit(celsius):
    return celsius * 9/5 + 32

def fahrenheit_to_celsius(fahrenheit):
    return (fahrenheit - 32) * 5/9

def format_temperature(value, unit):
    return f"{value:.1f}°{unit}"

def convert_and_display(temp, from_unit):
    """Convert a temperature and display both forms."""
    if from_unit == "C":
        converted = celsius_to_fahrenheit(temp)
        print(f"{format_temperature(temp, 'C')} = {format_temperature(converted, 'F')}")
    else:
        converted = fahrenheit_to_celsius(temp)
        print(f"{format_temperature(temp, 'F')} = {format_temperature(converted, 'C')}")

# Each function does one small job
convert_and_display(100, "C")    # 100.0°C = 212.0°F
convert_and_display(72, "F")     # 72.0°F = 22.2°C
function celsiusToFahrenheit(celsius) {
    return celsius * 9/5 + 32;
}

function fahrenheitToCelsius(fahrenheit) {
    return (fahrenheit - 32) * 5/9;
}

function formatTemperature(value, unit) {
    return `${value.toFixed(1)}°${unit}`;
}

function convertAndDisplay(temp, fromUnit) {
    if (fromUnit === "C") {
        let converted = celsiusToFahrenheit(temp);
        console.log(`${formatTemperature(temp, "C")} = ${formatTemperature(converted, "F")}`);
    } else {
        let converted = fahrenheitToCelsius(temp);
        console.log(`${formatTemperature(temp, "F")} = ${formatTemperature(converted, "C")}`);
    }
}

convertAndDisplay(100, "C");    // 100.0°C = 212.0°F
convertAndDisplay(72, "F");     // 72.0°F = 22.2°C
static double CelsiusToFahrenheit(double celsius)
{
    return celsius * 9.0 / 5.0 + 32;
}

static double FahrenheitToCelsius(double fahrenheit)
{
    return (fahrenheit - 32) * 5.0 / 9.0;
}

static string FormatTemperature(double value, string unit)
{
    return $"{value:F1}°{unit}";
}

static void ConvertAndDisplay(double temp, string fromUnit)
{
    if (fromUnit == "C")
    {
        double converted = CelsiusToFahrenheit(temp);
        Console.WriteLine($"{FormatTemperature(temp, "C")} = {FormatTemperature(converted, "F")}");
    }
    else
    {
        double converted = FahrenheitToCelsius(temp);
        Console.WriteLine($"{FormatTemperature(temp, "F")} = {FormatTemperature(converted, "C")}");
    }
}

ConvertAndDisplay(100, "C");    // 100.0°C = 212.0°F
ConvertAndDisplay(72, "F");     // 72.0°F = 22.2°C
🎓 Instructor Note: Delivery Guidance

The temperature converter is a great example because each function is small, testable, and has a clear purpose. Walk through the call chain: convert_and_display calls celsius_to_fahrenheit which does the math, then calls format_temperature which handles display. This "decomposition" skill — breaking a problem into functions — is arguably more important than the syntax. Ask students: "What if we wanted to add Kelvin? How many functions would need to change?" (Answer: just add new converter functions and a case in the display function.)

Exercises

🏋️ Exercise 1: Greeting Generator

Objective: Write a function that takes a name and a time of day ("morning", "afternoon", "evening") and returns an appropriate greeting.

  • Morning → "Good morning, {name}!"
  • Afternoon → "Good afternoon, {name}!"
  • Evening → "Good evening, {name}!"
  • Anything else → "Hello, {name}!"
✅ Solution
def greet(name, time_of_day):
    match time_of_day.lower():
        case "morning":
            return f"Good morning, {name}!"
        case "afternoon":
            return f"Good afternoon, {name}!"
        case "evening":
            return f"Good evening, {name}!"
        case _:
            return f"Hello, {name}!"

print(greet("Alice", "morning"))     # Good morning, Alice!
print(greet("Bob", "evening"))       # Good evening, Bob!
print(greet("Charlie", "midnight"))  # Hello, Charlie!
function greet(name, timeOfDay) {
    switch (timeOfDay.toLowerCase()) {
        case "morning":
            return `Good morning, ${name}!`;
        case "afternoon":
            return `Good afternoon, ${name}!`;
        case "evening":
            return `Good evening, ${name}!`;
        default:
            return `Hello, ${name}!`;
    }
}

console.log(greet("Alice", "morning"));
console.log(greet("Bob", "evening"));
console.log(greet("Charlie", "midnight"));
static string Greet(string name, string timeOfDay)
{
    return timeOfDay.ToLower() switch
    {
        "morning" => $"Good morning, {name}!",
        "afternoon" => $"Good afternoon, {name}!",
        "evening" => $"Good evening, {name}!",
        _ => $"Hello, {name}!"
    };
}

Console.WriteLine(Greet("Alice", "morning"));
Console.WriteLine(Greet("Bob", "evening"));
Console.WriteLine(Greet("Charlie", "midnight"));

🏋️ Exercise 2: Calculator Functions

Objective: Write four functions — add, subtract, multiply, divide — each taking two numbers and returning the result. The divide function should handle division by zero gracefully.

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

def subtract(a, b):
    return a - b

def multiply(a, b):
    return a * b

def divide(a, b):
    if b == 0:
        return "Error: Division by zero!"
    return a / b

# Test them
print(f"10 + 3 = {add(10, 3)}")        # 13
print(f"10 - 3 = {subtract(10, 3)}")    # 7
print(f"10 * 3 = {multiply(10, 3)}")    # 30
print(f"10 / 3 = {divide(10, 3):.2f}")  # 3.33
print(f"10 / 0 = {divide(10, 0)}")      # Error: Division by zero!
function add(a, b) { return a + b; }
function subtract(a, b) { return a - b; }
function multiply(a, b) { return a * b; }

function divide(a, b) {
    if (b === 0) return "Error: Division by zero!";
    return a / b;
}

console.log(`10 + 3 = ${add(10, 3)}`);
console.log(`10 - 3 = ${subtract(10, 3)}`);
console.log(`10 * 3 = ${multiply(10, 3)}`);
console.log(`10 / 3 = ${divide(10, 3).toFixed(2)}`);
console.log(`10 / 0 = ${divide(10, 0)}`);
static double Add(double a, double b) => a + b;
static double Subtract(double a, double b) => a - b;
static double Multiply(double a, double b) => a * b;

static string Divide(double a, double b)
{
    if (b == 0) return "Error: Division by zero!";
    return (a / b).ToString("F2");
}

Console.WriteLine($"10 + 3 = {Add(10, 3)}");
Console.WriteLine($"10 - 3 = {Subtract(10, 3)}");
Console.WriteLine($"10 * 3 = {Multiply(10, 3)}");
Console.WriteLine($"10 / 3 = {Divide(10, 3)}");
Console.WriteLine($"10 / 0 = {Divide(10, 0)}");

🏋️ Exercise 3: Password Strength Checker

Objective: Write a function that takes a password string and returns its strength: "Weak", "Medium", or "Strong".

  • Weak: Less than 8 characters
  • Medium: 8+ characters but missing uppercase, lowercase, or digits
  • Strong: 8+ characters with uppercase, lowercase, AND digits

Hint: Break this into helper functions — one for each check.

✅ Solution
def has_uppercase(text):
    return any(c.isupper() for c in text)

def has_lowercase(text):
    return any(c.islower() for c in text)

def has_digit(text):
    return any(c.isdigit() for c in text)

def check_strength(password):
    if len(password) < 8:
        return "Weak"

    has_all = has_uppercase(password) and has_lowercase(password) and has_digit(password)

    if has_all:
        return "Strong"
    return "Medium"

# Test
passwords = ["hi", "password", "Password1", "ALLCAPS123"]
for pw in passwords:
    print(f"'{pw}' → {check_strength(pw)}")
# 'hi' → Weak
# 'password' → Medium
# 'Password1' → Strong
# 'ALLCAPS123' → Medium
function hasUppercase(text) {
    return /[A-Z]/.test(text);
}

function hasLowercase(text) {
    return /[a-z]/.test(text);
}

function hasDigit(text) {
    return /[0-9]/.test(text);
}

function checkStrength(password) {
    if (password.length < 8) return "Weak";

    let hasAll = hasUppercase(password) && hasLowercase(password) && hasDigit(password);
    return hasAll ? "Strong" : "Medium";
}

let passwords = ["hi", "password", "Password1", "ALLCAPS123"];
for (let pw of passwords) {
    console.log(`'${pw}' → ${checkStrength(pw)}`);
}
static bool HasUppercase(string text) => text.Any(char.IsUpper);
static bool HasLowercase(string text) => text.Any(char.IsLower);
static bool HasDigit(string text) => text.Any(char.IsDigit);

static string CheckStrength(string password)
{
    if (password.Length < 8) return "Weak";

    bool hasAll = HasUppercase(password) && HasLowercase(password) && HasDigit(password);
    return hasAll ? "Strong" : "Medium";
}

string[] passwords = { "hi", "password", "Password1", "ALLCAPS123" };
foreach (string pw in passwords)
{
    Console.WriteLine($"'{pw}' → {CheckStrength(pw)}");
}
🎓 Instructor Note: Delivery Guidance

Exercise 1 is a quick win — it combines functions with the switch/match from Lesson 7. Exercise 2 introduces the idea that functions compute and return, not just print. The division-by-zero handling previews error handling (Module 7). Exercise 3 is the star: it demonstrates decomposition — breaking a complex check into small helper functions. Ask students to add the helpers first, then compose them in the main function. If time allows, challenge them to add a "Very Strong" level that also requires special characters.

Summary

🎉 Key Takeaways

  • Python uses def, JavaScript uses function, C# declares a return type — but the concept is the same everywhere
  • Defining a function doesn't run it — you must call it with parentheses
  • Parameters let functions accept input; return values let functions send output back
  • C# requires type declarations for parameters and return values; Python and JS don't
  • JavaScript has arrow functions (=>) as a compact alternative; C# has similar lambda expressions
  • In C#, all functions live inside classes and are called methods
  • Functions calling other functions is how you decompose complex problems into simple, testable pieces
  • Naming conventions: snake_case (Python), camelCase (JS), PascalCase (C#)

🚀 What's Next?

Now that you can define and call functions, the next lesson dives deeper: parameters, arguments, and return values — including default values, keyword arguments, and how to make your functions more flexible.

🎯 Quick Check

Question 1: What happens when you define a function but don't call it?

Question 2: What does a Python function return if it has no return statement?