6 min read
CS Fundamentals Index
Tier 1 -- Foundations
Object-Oriented Programming
Tier 2 -- Core Concepts
Tier 3 -- Debugging & Tradeoffs
CS Fundamentals Index
Tier 1 -- Foundations
Object-Oriented Programming
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.
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?
2. How It Works
When a function is called:
- A new frame is allocated for its local variables and parameters.
- Arguments are bound to parameters (by value or by , depending on the language).
- The function body executes.
- 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") { ... }
A backend developer writes the following Python function and calls it from a route handler:
What value doesx = 'global' def process_request(): x = 'local' def build_response(): return x return build_response() result = process_request()
result hold, and why?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
xinside a function hides a -levelx. Useful, but if you meant to modify the outer one, you needglobal x(Python) or pass it in / return it (Java/C++). - Assignment in Python creates a local.
x = 5inside a function always declares a localx. To modify an enclosing / global one, usenonlocalorglobal. 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. Usexs=Noneand 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.
A backend developer writes the following Python function to collect API request logs:
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?def log_request(path, headers, log=[]): log.append({'path': path, 'headers': headers}) return log
4. Language Differences
| Aspect | Python | Java | C++ |
|---|---|---|---|
| First-class functions | Yes (functions are objects) | Kind of (method references, lambdas as functional interfaces) | Yes (function pointers, lambdas, std::function) |
| Default arguments | Yes (but beware mutable defaults) | No (use overloading) | Yes |
| Keyword arguments | Yes | No | No |
Variadic (*args, **kwargs) | Yes | Yes (String... names) | Yes (variadic templates, ... for C-style) |
| Nested function definitions | Yes (closures) | Local classes / lambdas only | Lambdas; free functions must be at namespace scope |
| Overloading on types | No (runtime dispatch via isinstance or singledispatch) | Yes | Yes |
| Lambda / anonymous function | lambda 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.
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?
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.
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?
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.