Functions & Scope

6 min read

Reading Progress0%
CS Fundamentals Index
Tier 1 -- Foundations
Tier 2 -- Core Concepts
Tier 3 -- Debugging & Tradeoffs
CS Fundamentals Index
Tier 1 -- Foundations
Tier 2 -- Core Concepts
Tier 3 -- Debugging & Tradeoffs

Functions & Scope

1. What Is It?

A function is a named block of code you can invoke with arguments to get a result. is the rule that decides which names are visible from where. Together they form the basic unit of reuse: a function does one thing, takes inputs, returns an output, and its internals are hidden from the rest of the program.

Without functions, every program is one long script with no reuse and no way to isolate bugs. Without rules, every collides with every other — you'd need globally unique names for everything. Understanding both is what separates code that works once from code that works as a system.

QUICK CHECK

A junior developer on your team writes a 500-line script to process API responses. All logic lives at the top level, and variables like data, result, and index are reused throughout. The team reports that fixing one bug keeps breaking unrelated parts of the script. What is the most direct cause of this problem?

Choose one answer

2. How It Works

When a function is called:

  1. A new frame is allocated for its local variables and parameters.
  2. Arguments are bound to parameters (by value or by , depending on the language).
  3. The function body executes.
  4. On return, the result is passed back, the frame is torn down, and locals disappear.
# Python
def area(width, height):
    result = width * height       # 'result' is local to this frame
    return result

a = area(3, 4)                    # a = 12
# 'result' no longer exists here
// Java
int area(int width, int height) {
    int result = width * height;
    return result;
}
// C++
int area(int width, int height) {
    int result = width * height;
    return result;
}

Scope: where a name is visible

A is the region of code where a name can be resolved. Most languages use lexical (static) : you can determine a name's scope by reading the source. It doesn't depend on who called whom at runtime.

Python's scope lookup follows the LEGB rule:

  • Local (inside the current function)
  • Enclosing (inside any surrounding function, for closures)
  • Global ( top level)
  • Built-in (print, len, etc.)
x = "global"
def outer():
    x = "enclosing"
    def inner():
        # x = "local"  # uncomment to shadow
        print(x)        # "enclosing" — LEGB lookup finds it
    inner()
outer()

Java and C++ use block scope: a name declared inside { } exists only within those braces and any nested blocks.

void f() {
    for (int i = 0; i < 10; i++) {
        int temp = i * 2;    // 'temp' visible only inside this loop body
    }
    // 'i' and 'temp' are both gone here
}
void f() {
    if (cond) {
        int x = 5;     // local to this if-block
    }
    // x is not accessible here
}

Parameters and arguments

  • Parameter — the name in the function's definition.
  • Argument — the value you pass in at the call site.
# Python — positional, keyword, default, variadic
def greet(name, greeting="Hello", *extras, **options):
    ...

greet("Ada")                        # positional
greet(name="Ada", greeting="Hi")    # keyword
greet("Ada", "Hi", "and", "bye")    # *extras captures the rest
// Java — positional only; overloading lets you define multiple signatures
void greet(String name) { ... }
void greet(String name, String greeting) { ... }     // overload
// C++ — default arguments and overloading
void greet(const std::string& name, const std::string& greeting = "Hello") { ... }
QUICK CHECK

A backend developer writes the following Python function and calls it from a route handler:

x = 'global'

def process_request():
    x = 'local'
    def build_response():
        return x
    return build_response()

result = process_request()
What value does result hold, and why?

Choose one answer

3. What You Actually Need to Know

  • Locals die when the function returns. In C++, returning a pointer to a local is undefined behavior (the slot is reused). In Java/Python, returning a to a local is fine because the lives on the — only the local name disappears.
  • Shadowing silently replaces outer names. A local x inside a function hides a -level x. Useful, but if you meant to modify the outer one, you need global x (Python) or pass it in / return it (Java/C++).
  • Assignment in Python creates a local. x = 5 inside a function always declares a local x. To modify an enclosing / global one, use nonlocal or global. This surprises people coming from other languages.
  • Don't use default arguments in Python. def f(xs=[]) shares one list across every call that omits the argument — a classic footgun. Use xs=None and build inside the body.
  • Overloading vs. defaults. Java picks overload resolution; Python uses default arguments; C++ offers both. Same outcome, different style.
  • Pure functions are easier. A function that reads only its parameters and returns a value (no globals, no I/O, no mutation) is the easiest to test, reason about, and reuse. Push impurity to the edges of the program.
  • Debugging clues:
    • UnboundLocalError (Python) — you assigned to a name later in a function, so the interpreter treats it as local and reading it earlier fails.
    • NameError — name isn't in any visible (typo or missing import).
    • Value unexpectedly persists between calls → default argument or global state.
    • overflow → usually runaway ; sometimes very deep call chains.
QUICK CHECK

A backend developer writes the following Python function to collect API request logs:

def log_request(path, headers, log=[]):
    log.append({'path': path, 'headers': headers})
    return log
After several requests are handled, the developer notices the log keeps growing across unrelated calls even when they expect a fresh list each time. What is the root cause?

Choose one answer

4. Language Differences

AspectPythonJavaC++
First-class functionsYes (functions are objects)Kind of (method references, lambdas as functional interfaces)Yes (function pointers, lambdas, std::function)
Default argumentsYes (but beware mutable defaults)No (use overloading)Yes
Keyword argumentsYesNoNo
Variadic (*args, **kwargs)YesYes (String... names)Yes (variadic templates, ... for C-style)
Nested function definitionsYes (closures)Local classes / lambdas onlyLambdas; free functions must be at namespace scope
Overloading on typesNo (runtime dispatch via isinstance or singledispatch)YesYes
Lambda / anonymous functionlambda x: x+1 (limited to expressions)x -> x + 1[](int x) { return x+1; }

Python's dynamic dispatch means there's no overloading — the latest def greet(...) just overwrites the earlier one, silently.

QUICK CHECK

A backend developer is migrating a Python API endpoint to Java. The Python function uses keyword arguments so callers can pass parameters in any order by name, like create_user(role='admin', name='Alice'). What is the Java equivalent approach to achieve similar flexibility?

Choose one answer

5. Tradeoffs & Decisions

  • Short functions vs. long functions. A function should do one thing at one level of abstraction. If you need a mental "section break" inside a function, that's a sign to extract another function.
  • Parameters vs. globals. Passing dependencies as parameters makes functions testable and explicit. Globals make the function shorter but couple it to the rest of the program — harder to test, harder to reuse.
  • By- vs. by-value parameters. Use by-value when the callee shouldn't affect the caller's data. Use by- when the data is large (avoid copy cost) or when mutation is the point. In C++, const T& is the common default for "don't copy, don't mutate."
  • Returning vs. mutating. Prefer returning new values over mutating parameters — especially for data you share across owners. Mutation is fine locally; aliased mutation is the source of many bugs.
  • Function length. There's no magic number, but anything over ~30–50 lines usually hides a sub-function trying to get out.

If you see a function taking 10+ parameters, it usually means the parameters want to be an . You'd choose an explicit return over mutation when callers might share the argument.

QUICK CHECK

A backend developer writes a function that updates a user record object. Multiple services in the application hold references to the same user object. Which approach is safer, and why?

Choose one answer

6. Interview Cheat Sheet

  • A function is a reusable block of code taking parameters and returning a value. Each call gets a new frame for its locals.
  • Lexical means a name's is determined by where it's written, not who calls it. Python uses the LEGB rule; Java and C++ use block scope.
  • Parameter is the name in the definition; argument is the value passed in.
  • default arguments in Python are a footgun — the default is created once at definition time, not per call.
  • Pure functions (no globals, no mutation, no I/O) are the easiest to test and reason about.
  • Assignment inside a Python function creates a local unless you use global / nonlocal.

Follow-ups:

  • "What's the difference between a parameter and an argument?" — Parameter is the in the definition; argument is the value supplied at call time.
  • "Why can't I modify a global from inside a Python function?" — Assignment creates a local. Use global (or better: pass it in and return it) to modify the global binding.
  • "What does returning a to a local mean in C++?" — Undefined behavior — the frame is gone and the slot will be overwritten.
Glossary History

Click dotted jargon to save explanations here.

Glossary History

Click dotted jargon to save explanations here.