Overview
This chapter introduces functions and recursion in Python — fundamental tools for designing clear, reusable and modular programs. It defines what a function is, how to declare and call functions, and how to pass data using different kinds of parameters (positional, keyword, default, variable-length). The chapter also covers anonymous (lambda) functions and commonly used functional tools, and explains scope and lifetime of variables (local vs global). The second major part presents recursion: the idea of a function calling itself, the necessity of a base case, and typical recursive patterns (e.g., factorial, Fibonacci, gcd, recursive traversal). Importance is emphasized: functions support modular programming, code reuse and easier testing, while recursion provides elegant solutions for problems naturally defined in terms of smaller subproblems. Students will learn to design, trace and implement both iterative and recursive solutions, compare their correctness and efficiency, reason about call stacks and recursion depth, and follow good coding practices (clear signatures, docstrings, return values and error handling). Practical examples and exercises build skills in writing,…
Learning Objectives
- Define a function and state its components (name, parameters, body, return value).
- Explain different parameter-passing mechanisms (pass-by-value vs pass-by-reference) with examples.
- Differentiate between local and global scope and demonstrate the use of scope modifiers (e.g., global).
- Write function definitions and calls including default arguments, keyword arguments, and variable-length arguments.
- Implement recursive functions for common problems (factorial, Fibonacci, GCD, binary search) and test their correctness.
- Apply recursion to design solutions and convert recursive algorithms into equivalent iterative versions.
- Trace the execution of recursive calls using call stacks and draw recursion trees for selected examples.
- Analyze time and space complexity of recursive algorithms and derive simple recurrence relations.
Topics in this chapter
11 topics · tap a topic title to jump straight to it.
Introduction to Functions
Introduction to Functions
Key Point: Mathematical mapping notation: f: A → B, where each x ∈ A has exactly one f(x) ∈ B.
What is a function? A function is a named, self-contained block of code that performs a specific task and optionally returns a value. In mathematics, a function f maps each element of a domain to exactly one element of a codomain: f: A → B. In programming, a function (also called procedure/method) has a signature (name, parameter list, return type) and a body.
Parts of a programming function — name, parameters (formal arguments), return type/value, body (statements), and optional parameter/default values. Example signature: int sum(int a, int b) { return a + b; }.
Calling and parameter passing — when a function is called, actual parameters (arguments) are supplied. Parameters can be passed by value (a copy) or by reference (alias). Scope rules determine visibility of variables (local vs global).
Why use functions? Modularity, reusability, easier testing and debugging, abstraction (hide implementation), and clearer code structure.
Recursive functions — a function that calls itself to solve a smaller instance of the same problem. A correct recursive function must have:
- Base case(s) — one or more stopping conditions that return directly without recursion.
- Recursive case — reduces the problem toward the base case.
Example recursion ideas: factorial, Fibonacci, binary search, tree traversals. Recursion uses the call stack: each call creates a new activation record (local variables, return address). Excessive recursion depth can cause stack overflow; some recursive problems are better solved iteratively for efficiency.
Complexity and recurrence — many recursive algorithms satisfy recurrence relations (e.g., T(n) = T(n-1) + O(1) for linear recursion). Solving these recurrences gives time complexity (e.g., factorial recursion O(n), naive recursive Fibonacci O(φ^n)).
Common mistakes — missing/incorrect base case, not reducing the problem on each call, modifying shared mutable state unexpectedly, expensive repeated work in naive recursion (use memoization/DP).
Good practices — keep functions short and single-purpose, use clear parameter names, document preconditions/postconditions, prefer iterative solutions when recursion depth or repeated work is problematic, and use memoization to optimize overlapping subproblems.
Short pseudo-code examples:
// factorial (recursive)
int fact(int n) {
if (n == 0) return 1; // base case
return n * fact(n - 1); // recursive case
}
// sum of first n numbers (function)
int sumN(int n) {
return n * (n + 1) / 2; // direct formula inside a function
}
- Temperature conversion: function celsiusToFahrenheit(c) returns (c * 9/5) + 32 — modularizes conversion logic.
- ATM withdrawal: withdraw(account, amount) checks balance, debits account, returns success/failure — separates transaction logic.
- Factorial (recursive): fact(n) = 1 if n=0 else n * fact(n-1). Practical for teaching recursion and call stack.
- Binary search (recursive or iterative): search(arr, low, high, key) — demonstrates divide-and-conquer and logarithmic time.
- Recipe as a function: bakeCake(ingredients) encapsulates a sequence of steps (real-life procedural abstraction).
- \[Mathematical mapping notation: f: A → B\]\[where each x ∈ A has exactly one f(x) ∈ B.\]
- \[Function composition: (f ∘ g)(x) = f(g(x)).\]
- \[Factorial recurrence: n! = n × (n − 1)!\]\[with 0! = 1.\]
- \[Sum of first n natural numbers: S(n) = 1 + 2 + ... + n = n(n + 1)/2.\]
- \[Fibonacci recurrence: F(n) = F(n − 1) + F(n − 2)\]\[with F(0)=0\]\[F(1)=1.\]
- \[Common recurrence patterns and complexities: - Linear recursion: T(n) = T(n − 1) + O(1) → T(n) = O(n). - Divide and conquer (binary search): T(n) = T(n/2) + O(1) → T(n) = O(log n). - Naive Fibonacci: T(n) = T(n − 1) + T(n − 2) + O(1) → exponential time O(φ^n).\]
Function Definition and Calling
Function Definition and Calling
Key Point: Function signature (C/C++/Java): return_type function_name(parameter_list) { // body }
What is a function? A function is a named block of code that performs a specific task, optionally accepts input values (parameters), and optionally returns a value. Functions promote modularity, reuse and abstraction.
Components of a function: name, parameter list (formal parameters), body (statements), return type/value (optional) and, in some languages, a declaration/prototype.
Definition vs Declaration: A definition provides the body (actual implementation). A declaration (or prototype) announces the function signature so the compiler/ interpreter knows how to call it. In some languages (Python) declaration and definition are combined.
Calling a function: To use a function you call it from another part of the program by writing its name and supplying arguments (actual parameters). Control transfers to the function body; when execution finishes (or a return is executed) control returns to the caller, optionally with a return value.
Parameter passing:
- Call by value: the function receives copies of arguments — changes inside do not affect caller variables.
- Call by reference (or by pointer): the function receives references so changes affect the original variables.
Scope and lifetime: Local variables declared inside a function are accessible only within that function (local scope) and exist while the function is active. Global variables are accessible across functions (global scope) but should be used sparingly.
Return statement: Used to send a value back to the caller. A function that does not return a value is often called a procedure (void in C/C++/Java).
Advantages: code reuse, easier debugging, readability, divide-and-conquer design, easier testing.
Functions and recursion: A function may call itself (recursion). Recursive solutions must have one or more base cases to stop recursion and one or more recursive cases that reduce the problem size. Each recursive call creates a new activation record on the call stack.
Common pitfalls: forgetting base case (infinite recursion), incorrect parameter passing (unexpected side effects), stack overflow for deep recursion, mismatched return types/signature.
Practical note: Choose between iterative and recursive approaches based on clarity, performance and stack usage. Tail recursion can be optimized by some compilers/ interpreters.
- Python (definition and call): def add(a, b): return a + b result = add(3, 4) # call; result is 7
- C++ (declaration, definition, call): int multiply(int x, int y); // declaration (prototype) int multiply(int x, int y) { // definition return x * y; } int main() { int p = multiply(2, 5); // call }
- Recursive factorial (Python): def factorial(n): if n <= 1: # base case return 1 return n * factorial(n - 1) # recursive call print(factorial(5)) # 120
- Naive recursive Fibonacci (shows exponential calls): def fib(n): if n <= 1: return n return fib(n-1) + fib(n-2) print(fib(6)) # 8
- Real-life example (recipe as function): Think of 'make_tea()' as a function. It may call sub-functions like 'boil_water()', 'steep_tea()' and 'add_milk()'. Each step is modular, reusable and hides internal details from the caller.
- Modular program example (banking): Functions: create_account(), deposit(amount), withdraw(amount), check_balance(). The main program calls these functions instead of handling all steps inline, improving clarity and maintainability.
- \[Function signature (C/C++/Java): return_type function_name(parameter_list) { // body }\]
- \[Function signature (Python): def function_name(parameter_list): # body return value\]
- \[Parameter passing types: Call-by-value\]\[Call-by-reference (or pointer)\]\[Call-by-object-reference (Python's model for mutable/immutable objects).\]
- \[Recursive recurrence examples: - Linear recursion (e.g.\]\[factorial): T(n) = T(n-1) + O(1) => O(n) - Divide-and-conquer (e.g.\]\[mergesort): T(n) = 2T(n/2) + O(n) => O(n log n) - Exponential (naive Fibonacci): T(n) = T(n-1) + T(n-2) + O(1) => O(phi^n) where phi ≈ 1.618\]
- \[Space (stack) usage for recursion: O(d) where d is maximum recursion depth (e.g.\]\[O(n) for factorial).\]
Types of Functions
Types of Functions
Key Point: Factorial recurrence: n! = n × (n-1)! with base case 0! = 1
What is a function? A function (or procedure/method) is a named block of code that performs a specific task, can take inputs (parameters) and can optionally return a result. Functions help modularize code, improve readability and enable reuse.
Classification of functions
- By parameters and return value (the most commonly taught classification in Class 12):
- Type 1 – No parameters, no return value
Used when the function performs a task that needs no input and returns nothing (e.g., print a fixed message). - Type 2 – Parameters, no return value
Takes inputs and performs some action (e.g., display a formatted report) but does not return a value to the caller. - Type 3 – No parameters, returns a value
Uses internal data or global state to compute and return a value (less common; e.g., generate and return a random number). - Type 4 – Parameters and return value
Accepts inputs and returns a computed result (e.g., add two numbers and return the sum).
- Type 1 – No parameters, no return value
- By origin:
- Built-in / Library functions — provided by the programming language or libraries (e.g., print(), sqrt()).
- User-defined functions — written by the programmer to perform specific tasks in the program.
- By behavior:
- Recursive functions — a function that calls itself directly or indirectly to solve a problem by breaking it into smaller subproblems. Every recursive function needs a base case to stop recursion.
- Non-recursive (iterative) functions — use loops or other constructs instead of self-calls.
- Pure functions — same inputs always produce same outputs and have no side effects (useful in functional programming).
- Impure functions — depend on or modify external state (I/O, global variables).
Types of recursion (important in Chapter: Functions & Recursion):
- Direct recursion — function calls itself (e.g., factorial).
- Indirect recursion — function A calls B and B calls A.
- Tail recursion — recursive call is the last operation; can be optimized by some compilers/interpreters into iteration.
- Non-tail (general) recursion — has additional work after the recursive call (e.g., building up results), e.g., recursion used in Fibonacci naive form.
- Linear recursion — each call makes at most one recursive call.
- Tree recursion — calls multiple recursive instances (e.g., naive Fibonacci), creating a branching tree of calls.
Key ideas when using functions and recursion:
- Always identify inputs (parameters), outputs (return value) and side effects.
- For recursion, clearly define a base case (stopping condition) and a recursive case (how the problem is reduced).
- Consider time and space costs: recursion uses call stack memory proportional to recursion depth.
- No parameters, no return: A function displayHello() that prints "Hello, welcome!" (used for greeting).
- Parameters, no return: A function printReport(studentName, marks) that formats and prints a student's report — it uses inputs but returns nothing.
- No parameters, returns value: A function getCurrentTimestamp() that reads system clock and returns the timestamp.
- Parameters and return value: A function add(a, b) that returns a + b.
- Recursive (direct, linear): factorial(n) where factorial(0)=1 (base case) and factorial(n)=n*factorial(n-1) (recursive case); used for permutations/combinations.
- Recursive (tree): naive Fibonacci: fib(n)=fib(n-1)+fib(n-2) with fib(0)=0, fib(1)=1 — illustrates exponential call growth.
- \[Factorial recurrence: n! = n × (n-1)! with base case 0! = 1\]
- \[Fibonacci recurrence: F(n) = F(n-1) + F(n-2)\]\[with F(0)=0\]\[F(1)=1\]
- \[Sum of first n natural numbers (recursive view): S(n) = n + S(n-1)\]\[S(0)=0 → closed form S(n) = n(n+1)/2\]
- \[Generic linear recursion time: T(n) = T(n-1) + O(1) → T(n) = O(n)\]
- \[Tree recursion (naive Fibonacci) time: T(n) = T(n-1) + T(n-2) + O(1) → exponential time O(φ^n) where φ≈1.618\]
Function Arguments and Parameters
Function Arguments and Parameters
Key Point: Function signature: return_type function_name(param1: type1, param2: type2, ...)
Definitions: A parameter (formal parameter) is a variable listed in a function's definition. An argument (actual parameter) is the real value passed to the function when it is called. Example: in def add(x, y): x and y are parameters; in add(2, 3), 2 and 3 are arguments.
Kinds of parameters / arguments:
- Positional (positional-only): Values are matched by order (e.g.,
f(a, b)). - Keyword (named): Values passed with parameter names (e.g.,
f(b=2, a=1)); order not required. - Default parameters: Parameters given default values in the definition; callers may omit them.
- Variable-length (varargs): Functions that accept an arbitrary number of arguments (e.g.,
*args,**kwargsin Python; variadic functions in other languages). - Function-as-argument (higher-order): A function can accept another function as an argument (used in callbacks, map/filter).
Parameter passing mechanisms:
- Pass-by-value: A copy of the value is passed; changes inside the function do not affect caller variables (common in C for primitive types).
- Pass-by-reference: A reference (alias) is passed; changes in the function affect the caller's variable (used in C++ with references, or when passing mutable objects in some languages).
- Language-specific model: Many modern languages use a hybrid model (e.g., Python: names bound to objects; mutable objects can be changed via a parameter, immutable cannot be re-bound by the function to change the caller's object).
Why parameters matter in recursion: Each recursive call creates a new set of parameters (a new stack frame). Parameters carry the current state; correctly updating parameters and ensuring a base case are essential to avoid infinite recursion or stack overflow.
Best practices: keep parameter lists short and meaningful, use default/keyword arguments for clarity, avoid side-effects unless intended (document when functions modify passed objects), and validate arguments early (type/range checks).
- Simple positional: def add(x, y): return x + y ; call add(2, 3) -> arguments 2 and 3 map to parameters x and y.
- Keyword/default: def greet(name, msg='Hello'): print(msg, name) ; greet('Anita') -> prints 'Hello Anita'; greet('Anita', msg='Hi') -> 'Hi Anita'.
- Variable-length: def sum_all(*nums): total=0; for n in nums: total+=n ; sum_all(1,2,3,4) -> 10.
- Pass-by-value example (C-style): void inc(int a){ a = a + 1; } int x=5; inc(x); // x still 5.
- Pass-by-reference example (C++): void inc(int &a){ a = a + 1; } int x=5; inc(x); // x becomes 6.
- Mutable/object behaviour (Python): def add_item(lst): lst.append(4) ; L=[1,2,3] ; add_item(L) -> L becomes [1,2,3,4].
- \[Function signature: return_type function_name(param1: type1\]\[param2: type2, ...)\]
- \[Arity: arity(f) = number of parameters of function f.\]
- \[Parameter-argument mapping: for i from 1 to n\]\[formal_param_i := actual_arg_i (for positional calls).\]
- \[Pass-by-value: callee receives a copy -> changes inside do not affect caller.\]
- \[Pass-by-reference: callee receives reference -> changes in callee affect caller.\]
- \[Recurrence (example factorial): T(n) = T(n-1) + O(1)\]\[with base T(0)=O(1).\]
Return Statement and Multiple Returns
Return Statement and Multiple Returns
Key Point: Function signature (general): return_type functionName(parameters) { ... return expression; }
What is a return statement? A return statement ends a function’s execution and optionally sends a value (or values) back to the caller. The caller can then use that value in expressions, assignments or further processing. In many languages a function with no explicit return returns a default (for example None in Python).
Behavior and flow: When a return executes, control immediately goes back to the caller — code after that return in the function is not executed. A function may contain several return statements (multiple returns) placed in different branches (for example inside if blocks) to produce different outcomes depending on input or conditions.
Multiple returns — two meanings:
- Multiple return statements: more than one
returnappears in the function body; only one executes per call, determined by runtime path. This is common for early exits, error checks, or cases handling. - Returning multiple values: a function may return a compound value (tuple, list, object) so the caller receives several pieces of data at once. Languages differ: Python supports returning multiple values naturally as a tuple, while languages like Java/C++ return a single value but you can return an array or an object to carry multiple results.
Return in recursion: In recursive functions the return propagates values up the call stack. Each recursive call returns a value computed from smaller subproblems; the caller uses that returned value to compute its own result. Correct base-case return is essential to stop recursion and start unwinding.
Differences between print and return: print only displays data to the console; return gives data to the caller for further use. Tests and composition of functions require return, not print.
- 1) Simple single return (Python): def square(x): return x * x result = square(5) # result = 25
- 2) Multiple return statements (early-exit): def classify_age(age): if age < 0: return "invalid" if age < 13: return "child" if age < 20: return "teen" return "adult" # Only one return executes depending on age
- 3) Returning multiple values (Python tuple): def min_max(a, b, c): mn = min(a, b, c) mx = max(a, b, c) return mn, mx mn, mx = min_max(4, 1, 9) # mn=1, mx=9
- 4) Recursion with return — factorial (base case + recursive return): def fact(n): if n == 0: return 1 # base case return return n * fact(n-1) # recursive return used by caller fact(4) # returns 24
- 5) Real-life example — bank transaction function returning status and new balance: def process_withdraw(account, amount): if amount <= 0: return False, account.balance if amount > account.balance: return False, account.balance account.balance -= amount return True, account.balance success, new_balance = process_withdraw(my_account, 200)
- 6) Returning structured data (common in languages that allow only single return value): # Return an object or dict/tuple to package multiple results def analyze(scores): return {"avg": sum(scores)/len(scores), "min": min(scores), "max": max(scores)}
- \[Function signature (general): return_type functionName(parameters) { ... return expression\]\[}\]
- \[Python (multiple values): def f(...): return v1\]\[v2 # returns tuple (v1\]\[v2)\]
- \[Implicit return: If no return is provided\]\[function returns None (Python) or default/undefined behaviour in other languages.\]
- \[Recurrence (factorial): F(n) = n * F(n-1)\]\[with base F(0) = 1\]
- \[Recurrence (Fibonacci): Fib(n) = Fib(n-1) + Fib(n-2)\]\[with base Fib(0)=0\]\[Fib(1)=1\]
- \[Conditional multiple returns pattern: if condition1: return A elif condition2: return B else: return C\]
Scope and Lifetime of Variables
Scope and Lifetime of Variables
Key Point: Scope ⊆ Program source text where name is visible (lexical nesting determines membership).
Definition: Scope of a variable is the region of the program source code where the name of the variable is visible and can be used. Lifetime (or storage duration) is the time period during program execution when the variable exists in memory and retains a value.
Kinds of Scope:
- Block (local) scope: Visible only inside the block or function where it is declared. Example: variables declared inside { } or inside a function.
- Function scope: Name is visible throughout the function (common for labels or C-style identifiers inside a function).
- File (internal) scope: Visible to all functions in the same source file (in C/C++ this can be created with the static keyword at file level).
- Global (external) scope: Visible across the entire program (other files can access with extern in C/C++).
- Class/instance scope: In OOP, member variables have scope tied to class or instance and access controlled by access specifiers.
- Lexical (static) scope: Most modern languages use lexical scoping where nested blocks determine visibility; inner blocks can access outer variables.
Kinds of Lifetime / Storage Duration:
- Automatic (local) lifetime: Created on entry to the block or function (usually allocated on the stack) and destroyed when the block/function exits. Each call to a function gets a fresh set of these variables. This is the default for local variables in C/C++ and many languages.
- Static lifetime: Allocated once and exist for the entire program run. Examples: global variables and variables declared static at function or file scope in C/C++.
- Dynamic lifetime: Created and destroyed explicitly at runtime (allocated on the heap using malloc/new or language-specific allocators). Lifetime is controlled by programmer or garbage collector.
Important interactions:
- Two different variables can have the same name if their scopes do not overlap (shadowing). The inner declaration hides the outer one while inside the inner scope.
- In recursion, automatic variables are distinct per activation (each call has its own copy on the stack), while static variables are shared across calls.
- Access modifiers and keywords: in C/C++ keywords like static, extern, register modify linkage or storage class; in Python keywords like global and nonlocal affect name binding in nested scopes.
Small code examples:
// C++: scope and lifetime
int global_x = 10; // global: file/external scope, static lifetime
void f() {
int local_y = 5; // local: block scope, automatic lifetime
static int s = 0; // static local: block scope, static lifetime
s++;
}
// Recursion: each call gets its own automatic variables
int fact(int n) {
int result = 1; // result is recreated each call
if (n <= 1) return 1;
return n * fact(n-1);
}
Practical notes:
- Prefer small scope: declare variables as near as possible to their use to avoid bugs from unintended access or modification.
- Use static/global sparingly: they persist for entire run and can cause hidden dependencies and harder-to-find bugs.
- Understand memory model: automatic -> stack, static/global -> data segment, dynamic -> heap. This affects lifetime, performance, and concurrency behavior.
- Office analogy: A global variable is like a company ID card kept at reception (available to everyone at all times). A local variable is like a meeting room note that exists only during a meeting and is removed after the meeting ends. A static local variable is like a logbook kept in the meeting room that persists between meetings.
- Library analogy: A book in the library shelf (global/static) is always available for anyone to borrow across the day. A book you check out (dynamic) exists with you until you return it. A note you make while reading (local) is erased once you leave the reading room.
- Recursion: Each recursive function call is like a stack of forms; every form holds that call's local answers. When a call returns, its form is removed, but a static sheet kept in a binder is shared by all calls.
- Programming example (Python): - local variable: defined inside function, not visible outside - global variable: defined outside, use 'global' keyword to modify inside function - nonlocal: in nested functions, to modify variable in enclosing scope
- \[Scope ⊆ Program source text where name is visible (lexical nesting determines membership).\]
- \[Lifetime(variable) = [time_created\]\[time_destroyed] during program execution (automatic variables: created at block entry\]\[destroyed at block exit\]\[static variables: created before main and destroyed after program end or vice versa).\]
- \[Storage mapping: automatic -> stack\]\[static/global -> data segment (BSS/data)\]\[dynamic -> heap.\]
- \[Shadowing rule: If name declared in inner scope\]\[inner_name hides outer_name within inner scope.\]
- \[Recursion rule: For each function call i\]\[automatic variables occupy a distinct stack frame_i.\]
Recursive Functions
Recursive Functions
Key Point: Factorial recursive definition: n! = n × (n-1)! , with 0! = 1.
What is a recursive function?
A recursive function is a function that calls itself directly or indirectly to solve a problem by breaking it into smaller subproblems. Each recursive call works on a smaller or simpler input until a base case is reached, which returns a result without any further recursion.
Key components
- Base case: Condition under which the function returns a result immediately (terminates recursion).
- Recursive case: Part where the function calls itself with a smaller/simpler argument.
- Call stack / activation records: Each call is placed on the program stack storing local variables and return address. When a base case returns, calls are popped in reverse order.
Important properties and rules
- Every recursive function must have at least one base case that is reachable; otherwise it leads to infinite recursion (stack overflow).
- Recursion divides the original problem into one or more subproblems of the same kind.
- Types: direct recursion (function calls itself) and indirect recursion (A calls B and B calls A).
- Tail recursion: the recursive call is the last operation in the function. Some compilers/interpreters can optimize tail recursion to reuse stack frames.
- Recursion is useful for problems with natural self-similar structure: tree traversals, factorial, Fibonacci, divide-and-conquer algorithms.
Advantages and disadvantages
- Advantages: Cleaner and simpler code for many problems (trees, graphs, divide-and-conquer); mirrors mathematical definitions.
- Disadvantages: Extra memory overhead for the call stack; may be inefficient (e.g., naive Fibonacci); sometimes harder to reason about space/time than iterative solutions.
Simple examples (Python-style pseudocode)
# Factorial (linear recursion)
def fact(n):
if n == 0:
return 1 # base case
return n * fact(n-1) # recursive case
# Binary search (divide and conquer)
def binary_search(arr, low, high, key):
if low > high:
return -1
mid = (low + high) // 2
if arr[mid] == key:
return mid
elif arr[mid] > key:
return binary_search(arr, low, mid-1, key)
else:
return binary_search(arr, mid+1, high, key)
Performance intuition
Recurrence relations express the running time of recursive algorithms. Solving these recurrences gives time complexity (examples below).
When to convert recursion to iteration
If recursion causes large stack usage or duplicated work (e.g., naive Fibonacci), you can convert to loops or use memoization/dynamic programming to improve efficiency.
Debugging tips
Trace calls on paper or draw the call stack. Ensure each recursive call progresses toward the base case.
- Factorial (mathematical definition and code): n! = n * (n-1)! with 0! = 1. Recursive code: def fact(n): if n==0: return 1 else: return n*fact(n-1). Time: O(n), space: O(n) (stack).
- Fibonacci (naive recursion): F(n)=F(n-1)+F(n-2) with F(0)=0,F(1)=1. Naive recursion is exponential (~O(phi^n)). Use memoization or iterative DP to reduce to O(n).
- Binary Search (divide & conquer): recurrence T(n)=T(n/2)+O(1) → O(log n). Recursively halve the search interval until base case (not found or found).
- Euclid's GCD (recursive): gcd(a,b) = gcd(b, a mod b), with gcd(a,0)=a. Runs in O(log min(a,b)).
- Sum of digits (simple recursion): sumDigits(n) = n%10 + sumDigits(n//10), base case n==0 returns 0. Depth ≈ number of digits.
- \[Factorial recursive definition: n! = n × (n-1)!\]\[with 0! = 1.\]
- \[Fibonacci recurrence: F(n) = F(n-1) + F(n-2)\]\[with F(0)=0\]\[F(1)=1.\]
- \[Linear recursion time: T(n) = T(n-1) + O(1) ⇒ T(n) = O(n).\]
- \[Divide-and-conquer (binary search): T(n) = T(n/2) + O(1) ⇒ T(n) = O(log n).\]
- \[Tree recursion (naive Fibonacci): T(n) = T(n-1) + T(n-2) + O(1) ⇒ T(n) = Θ(φ^n) where φ ≈ 1.618.\]
- \[Euclid's algorithm: gcd(a,b) reduces roughly by factor\]\[worst-case number of steps is O(log min(a,b)).\]
Tracing and Analyzing Recursive Calls
Tracing and Analyzing Recursive Calls
Key Point: Linear recursion: T(n) = T(n−1) + Θ(1) ⇒ Θ(n)
What tracing a recursive call means
Tracing a recursive function means simulating its execution manually (or with a debugger) to record the sequence of calls, the values of parameters in each call, the order in which calls return, and how results combine. Tracing highlights the call stack (activation records), base case hits, and the flow of control between recursive calls.
Why tracing is important
It helps you verify correctness, find missing/incorrect base cases, understand time and space costs, and spot opportunities for optimization (tail recursion, memoization, converting to iteration).
Key elements when tracing
- Identify the base case(s) and the recursive case(s).
- Record the parameter values on each call and the return value when the call finishes.
- Draw the call stack (top = current call) or the call tree (each node = one call) to visualize order and concurrency of subcalls.
- Count calls to estimate time cost and note maximum stack depth to estimate space cost.
How to trace (step-by-step)
- Start from the initial call: write its parameters.
- If it meets a base case, write the return value and pop it from the stack.
- If not, expand it into the recursive call(s): push the first subcall and repeat.
- When a subcall returns, use its return value(s) to compute the parent’s return, then pop the parent when done.
- Repeat until the initial call returns.
Analyzing complexity from traces
Use the pattern of calls you saw to write a recurrence for time T(n). Solve the recurrence or apply the Master Theorem for divide-and-conquer recurrences. Space complexity is typically proportional to the maximum recursion depth (plus any additional per-call local space).
Common patterns
- Linear recursion (one subcall per call): T(n) = T(n-1) + O(1) → O(n). Example: factorial, linear list processing.
- Binary/branching recursion (two or more subcalls): T(n) = T(n-1) + T(n-2) + O(1) or similar → often exponential (e.g., naive Fibonacci).
- Divide-and-conquer (a subproblems each of size n/b): T(n) = a T(n/b) + f(n) → use Master Theorem (e.g., merge sort).
- Tail recursion: recursive call is the last action. Can be converted to iteration or optimized by some compilers to O(1) extra space.
Practical tips
- Always verify base cases first — missing or incorrect base cases cause infinite recursion (stack overflow).
- When multiple subcalls repeat computation, consider memoization or dynamic programming to avoid exponential blow-up.
- Use a debugger or draw call trees for medium-sized inputs to see patterns; derive general recurrence from the observed pattern.
- Factorial (n!): trace of fact(4) Call stack (top is current): fact(4) → fact(3) → fact(2) → fact(1) Base hit at fact(1)=1. Returns unwind: fact(2)=2, fact(3)=6, fact(4)=24. Number of calls = n+1 (including fact(0) or fact(1) depending on base). Recurrence: T(n)=T(n-1)+O(1) ⇒ Θ(n). Max recursion depth = n.
- Naive Fibonacci: trace of fib(5) with fib(0)=0, fib(1)=1 Call tree nodes: fib(5) calls fib(4) and fib(3); fib(4) calls fib(3) and fib(2); many repeated calls (fib(3) computed twice, etc.). Number of calls C(n) satisfies C(n)=C(n-1)+C(n-2)+1 with C(0)=1, C(1)=1. Solution: C(n)=2·F(n+1)−1, which grows ≈ φ^n (exponential). Time: exponential without memoization. Space: recursion depth = n (O(n)).
- Tower of Hanoi (moves and recursion): To move n disks: M(n)=2·M(n−1)+1 with M(1)=1. Solve: M(n)=2^n − 1. Trace shows a perfect binary-like recursion tree; time and number of calls are O(2^n).
- \[Linear recursion: T(n) = T(n−1) + Θ(1) ⇒ Θ(n)\]
- \[Logarithmic recursion: T(n) = T(n/2) + Θ(1) ⇒ Θ(log n)\]
- \[Divide & Conquer (Master Theorem): T(n) = a·T(n/b) + f(n)\]\[Compare n^{log_b a} to f(n): - If f(n) = O(n^{c}) with c < log_b a ⇒ T(n)=Θ(n^{log_b a}) - If f(n) = Θ(n^{log_b a}) ⇒ T(n)=Θ(n^{log_b a}·log n) - If f(n) = Ω(n^{c}) with c > log_b a and regularity holds ⇒ T(n)=Θ(f(n))\]
- \[Fibonacci recursion (naive): T(n) ≈ T(n−1)+T(n−2)+Θ(1) ⇒ Θ(φ^n) where φ = (1+√5)/2 ≈ 1.618\]
- \[Call count for naive fib: C(n) = 2·F(n+1) − 1 (F is Fibonacci sequence)\]\[roughly ≈ φ^n\]
- \[Tower of Hanoi moves: M(n) = 2·M(n−1) + 1 ⇒ M(n) = 2^n − 1\]
Common Recursive Algorithms and Examples
Common Recursive Algorithms and Examples
Key Point: Factorial recurrence: F(n) = n * F(n-1), F(0)=1 => closed form: n! = 1·2·...·n
What is recursion? Recursion is a technique where a function calls itself to solve a smaller instance of the same problem. Every correct recursive function has:
- Base case(s): one or more conditions that stop further recursion (prevent infinite calls).
- Recursive case(s): one or more self-calls that move the problem toward the base case.
Key concepts:
- Call stack / stack frames: each recursive call uses a new stack frame holding parameters and local variables. Depth equals the maximum number of simultaneous calls.
- Progress toward base case: recursive calls must reduce problem size (e.g., n → n-1 or n → n/2).
- Types of recursion: linear (single recursive call per activation), binary/multi-branch (multiple recursive calls, e.g., Fibonacci), and tail recursion (recursive call is the function's last action).
- Divide and conquer: algorithms split the input into subproblems, solve them recursively, then combine results (e.g., merge sort, quick sort).
Why use recursion? It gives clear, concise solutions to problems that are naturally self-similar: tree traversals, combinatorial generation (permutations, subsets), mathematical definitions (factorial, Fibonacci), and divide-and-conquer algorithms.
Practical pointers: ensure base case correctness, check that each recursive call makes progress, beware of exponential time when calls branch heavily (use memoization or convert to iterative if necessary), and be mindful of recursion depth (stack overflow).
- Factorial (n!): factorial(n) = n * factorial(n-1) with base factorial(0)=1. Time: O(n). Space (recursion depth): O(n).
- Fibonacci (naive recursive): fib(n) = fib(n-1) + fib(n-2), base fib(0)=0, fib(1)=1. Naive time: O(φ^n) (exponential). Use memoization or iterative DP to get O(n).
- Euclidean GCD: gcd(a,b) = gcd(b, a mod b) with base gcd(a,0)=a. Very efficient; time O(log min(a,b)).
- Binary Search (on sorted array): recur on left or right half. Recurrence T(n)=T(n/2)+O(1) → O(log n). Space: O(log n) recursion depth.
- Merge Sort (divide and conquer): split array in two, recursively sort halves, then merge. Recurrence T(n)=2T(n/2)+O(n) → O(n log n). Space: O(n) for merging (plus recursion depth O(log n)).
- Tower of Hanoi: move n disks in 2^n − 1 moves using recursion (move n-1 to aux, move largest, move n-1). Time: O(2^n).
- \[Factorial recurrence: F(n) = n * F(n-1)\]\[F(0)=1 => closed form: n! = 1·2·...·n\]
- \[Fibonacci recurrence: F(n) = F(n-1) + F(n-2)\]\[F(0)=0\]\[F(1)=1 => closed form (Binet): F(n) = (φ^n - ψ^n)/√5 where φ=(1+√5)/2, ψ=(1-√5)/2\]
- \[Binary search: T(n) = T(n/2) + O(1) => T(n) = O(log n)\]
- \[Merge sort: T(n) = 2 T(n/2) + O(n) => T(n) = O(n log n)\]
- \[Naive Fibonacci: T(n) = T(n-1) + T(n-2) + O(1) => T(n) = O(φ^n) (exponential)\]
- \[Factorial (time): T(n) = T(n-1) + O(1) => T(n) = O(n)\]
Converting Recursion to Iteration and When to Use Which
Converting Recursion to Iteration and When to Use Which
Key Point: Tail recursion conversion concept: if f(params) returns g(f(new_params)), transform into while loop updating params and accumulator so that recursion depth becomes O(1).
Overview
Recursion is a technique where a function calls itself to solve a smaller instance of the same problem. Iteration uses loops (for/while) to repeat steps. Converting recursion to iteration means replacing self-calls with loops and, if needed, an explicit stack or accumulator variables so the algorithm produces the same output without using the language call stack.
Why convert?
- Recursion is often simpler and clearer for divide-and-conquer, tree/graph traversal, and backtracking problems.
- Iteration is usually more memory-efficient (no call stack overhead) and sometimes faster in practice — important when n is large or recursion depth is limited.
General conversion methods
- Tail recursion → Loop: If the last action of a function is a recursive call (tail recursion) you can replace the call with an update of parameters inside a loop and use an accumulator to carry results. Many compilers/languages can optimize tail calls, but some (e.g. Java, Python) do not, so manual conversion may be needed.
- Non-tail recursion → Explicit stack: For general recursion (e.g. tree traversals, DFS) simulate the call stack with your own stack data structure and loop while the stack is not empty. Push state frames (parameters/local variables) onto the stack to emulate recursive behavior.
- Memoization → Iterative DP: If recursion recomputes overlapping subproblems (e.g. naive Fibonacci), convert to bottom-up dynamic programming (iteration) to compute and store results once.
Steps to convert a recursive function to iterative
- Identify the state: parameters and local variables needed for each call.
- For tail recursion: convert into a loop by updating state variables and using an accumulator for the result.
- For non-tail recursion: create a stack of frames. Each frame contains the same state a recursive call would have. Use loop + push/pop to process frames in the correct order.
- Ensure you reproduce the base case behavior with conditional checks in the loop or when pushing frames.
Complexity considerations
- Time complexity often remains the same when converting recursion to iteration (you still perform the same operations), but constant factors may improve.
- Space complexity: recursion uses extra space on the call stack equal to recursion depth. Iteration with an explicit stack still uses space but gives control over storage and may allow constant-space solutions (e.g. tail recursion → O(1) space).
When to use which
- Use recursion when it makes code clearer and when problem structure is naturally recursive: tree algorithms, divide-and-conquer (merge sort, quicksort), backtracking (permutations, N-queens).
- Use iteration when you need better memory efficiency, when recursion depth might overflow the call stack, or when performance / tight resource constraints matter.
- If recursion causes excessive repeated work, prefer iterative dynamic programming or add memoization; sometimes an iterative DP is clearer and faster.
Small examples (explanatory)
1) Factorial (tail-call-friendly):
Recursive (tail):
int fact(int n, int acc) {
if (n == 0) return acc;
return fact(n-1, acc * n);
}
Iterative:
int factIter(int n) {
int acc = 1;
while (n > 0) {
acc *= n;
n--;
}
return acc;
}
2) Fibonacci — naive recursion vs iteration / DP:
Naive recursion:
int fib(int n) {
if (n <= 1) return n;
return fib(n-1) + fib(n-2); // exponential time
}
Iterative DP (bottom-up):
int fibIter(int n) {
if (n <= 1) return n;
int a = 0, b = 1;
for (int i = 2; i <= n; ++i) {
int c = a + b;
a = b; b = c;
}
return b;
}
3) Binary tree inorder traversal — recursion vs explicit stack:
Recursive:
void inorder(Node root) {
if (root == null) return;
inorder(root.left);
visit(root);
inorder(root.right);
}
Iterative with stack:
void inorderIter(Node root) {
Stack st = new Stack<>();
Node cur = root;
while (cur != null || !st.isEmpty()) {
while (cur != null) { st.push(cur); cur = cur.left; }
cur = st.pop();
visit(cur);
cur = cur.right;
}
}
Practical notes for Class 12 students
- Understand how the call stack grows and why recursion can cause stack overflow.
- Practice converting simple tail-recursive functions to loops first, then try non-tail examples using an explicit stack.
- Recognize patterns: tree → stack; divide-and-conquer → recursion unless resource limits force iteration.
- Factorial: tail-recursive version fact(n, acc) converts directly to a loop using acc as an accumulator. Iterative version uses O(1) extra space and O(n) time.
- Fibonacci: naive recursion fib(n) results in exponential time O(φ^n). Convert to iterative DP to get O(n) time and O(1) space (two variables), or use matrix exponentiation for O(log n) time.
- Binary tree inorder traversal: recursive calls can be replaced by an explicit stack. The iterative version pushes nodes while going left, pops to visit, then moves right.
- Depth-first search (graph): recursive DFS can be converted to an iterative DFS using a stack. Use an explicit visited set to avoid cycles.
- Backtracking (e.g., generating permutations): recursion is clearer; converting to iteration is possible but complex—use iteration only if necessary for performance or stack limits.
- \[Tail recursion conversion concept: if f(params) returns g(f(new_params))\]\[transform into while loop updating params and accumulator so that recursion depth becomes O(1).\]
- \[Recurrence for linear recursion (e.g.\]\[factorial): T(n) = T(n-1) + O(1) => T(n) = O(n).\]
- \[Recurrence for naive Fibonacci: T(n) = T(n-1) + T(n-2) + O(1) => T(n) = Θ(φ^n) where φ ≈ 1.618 (exponential).\]
- \[Space usage: recursion depth d uses O(d) call stack space\]\[Tail recursion can be converted to O(1) space by iteration.\]
- \[Dynamic programming bottom-up eliminates repeated work: if subproblem count is m and each computed once\]\[time = O(m).\]
Good Practices and Common Errors
Good Practices and Common Errors
Key Point: Recurrence forms and time complexities: T(n) = T(n-1) + O(1) => O(n) (e.g., linear recursion like factorial or linear sum)
Overview: Functions and recursion are fundamental tools for decomposition and repeated computation. Good practices make recursive programs correct, readable and efficient; common errors cause wrong results, infinite loops, or runtime crashes (stack overflow).
Good practices:
- Define a clear base case: Always identify and test the stopping condition(s). The base case must be reachable and correct.
- Ensure progress toward the base case: Each recursive call must operate on a strictly smaller/simpler input so recursion terminates (e.g., n-1, n/2, smaller subarray length).
- Keep functions short and single-purpose: One responsibility per function improves readability and testing.
- Use meaningful names and parameter lists: Name parameters to reflect their role (e.g., index, n, start, end) and avoid many hidden/global dependencies.
- Avoid unnecessary work: Cache or memoize repeated subproblems (e.g., Fibonacci) or convert to iterative when recursion causes repeated recomputation.
- Prefer tail recursion (when available): Tail-recursive functions can be optimized into loops by some compilers/interpreters, reducing stack use. If not available, convert to an iterative approach.
- Document preconditions and side-effects: State whether a function modifies its inputs, uses globals, or relies on invariants.
- Test small and edge inputs: Test base cases, very small inputs (0,1), largest expected inputs, and invalid inputs to ensure robustness.
Common errors:
- Missing or incorrect base case: Leads to infinite recursion and stack overflow.
- Wrong progress step: Recursing without reducing the problem correctly (for example, using n+1 instead of n-1 or wrong index update) prevents termination.
- Off-by-one errors: Incorrect bounds for indices (start/end) cause missing elements or infinite recursion when indexes never converge.
- Excessive recomputation: Naive recursive solutions (e.g., naive Fibonacci) repeat subproblems exponentially; use memoization or dynamic programming.
- Modifying shared/mutable state: Changing global variables or mutable arguments inside recursion can produce hard-to-find bugs.
- Stack overflow due to deep recursion: Using recursion for very deep problems can exceed the call stack; convert to iterative or use tail recursion optimization if supported.
- Wrong return values: Forgetting to return the recursive result or combining results improperly (e.g., summing wrong subresults) yields incorrect outputs.
- Language-specific pitfalls: Examples: default mutable arguments in Python, forgetting const correctness in C++, or not handling pass-by-reference vs pass-by-value as intended.
Checklist before finalizing a recursive function:
- Is there a clear base case and does it return the correct value?
- Does each recursive call move input closer to the base case?
- Are you avoiding repeated work (use memoization if needed)?
- Does the function have a single, well-documented purpose and no unexpected side-effects?
- Have you tested edge cases (0, 1, empty arrays) and large inputs?
Short example (pseudo-code):
// Correct factorial
function fact(n):
if n == 0: // base case
return 1
else:
return n * fact(n-1) // progress: n decreases
Incorrect variant (common error):
// Missing base case or wrong step — will not terminate
function badFact(n):
return n * badFact(n) // never reduces n -> infinite recursion
- Factorial of n (math/arrangements) — good practice: clear base case n==0; common error: missing or wrong base case.
- Fibonacci numbers (models population growth) — good practice: use memoization or iterative method to avoid exponential recomputation; common error: naive recursion causing very slow performance.
- Sum of elements in an array — good practice: use indices (start, end) and reduce the range each call; common error: off-by-one index mistakes that lead to infinite recursion or wrong sum.
- Directory traversal (real-life file system) — good practice: treat each subdirectory with a recursive call and handle empty directories as base case; common error: not handling symbolic links or cycles leading to infinite loops.
- Binary search (searching in sorted array) — good practice: ensure mid calculation and updating bounds reduce the search space; common error: incorrect mid or bounds causing no progress and infinite recursion.
- \[Recurrence forms and time complexities: T(n) = T(n-1) + O(1) => O(n) (e.g.\]\[linear recursion like factorial or linear sum)\]
- \[T(n) = T(n/2) + O(1) => O(log n) (e.g.\]\[binary search)\]
- \[T(n) = T(n-1) + T(n-2) + O(1) => O(φ^n) ≈ O(1.618^n) (naive Fibonacci\]\[exponential)\]
- \[Space used by recursion ≈ O(d) where d = maximum recursion depth (stack frames).\]
- \[Number of calls (upper bound) often equals number of nodes in recursion tree\]\[analyze tree height and branching factor to compute this.\]
Key Concepts
- Function
- A named block of code that performs a specific task and can return a value.
- Parameter
- A variable listed in a function definition that receives values when the function is called.
- Argument
- The actual value passed to a function parameter when the function is called.
- Return value
- The result a function sends back to the caller using a return statement.
- Function definition
- The code that declares a function name, parameters, and the body (implementation).
- Function call (Invocation)
- Executing a function by using its name followed by arguments in parentheses.
- Recursive function
- A function that calls itself directly or indirectly to solve a problem by breaking it into smaller subproblems.
- Base case
- A condition in a recursive function that stops further recursion and returns a simple, direct result.
- Recursive case
- Part of a recursive function that reduces the problem and makes the recursive call(s).
- Call stack (Stack frame)
- The runtime structure that stores information (parameters, local variables, return address) for each active function call.
- Tail recursion
- A form of recursion where the recursive call is the last operation in the function, allowing certain optimizations.
- Mutual recursion
- When two or more functions call each other in a cycle to solve a problem.
- Pass by value
- A parameter passing method where a copy of the argument's value is passed; changes do not affect the original variable.
- Pass by reference
- A parameter passing method where a reference to the original variable is passed; changes affect the original.
- Local variable
- A variable declared inside a function; its scope and lifetime are limited to that function call.
- Global variable
- A variable declared outside functions, accessible throughout the program (subject to scope rules).
- Scope
- The region of the program where a name (variable or function) is visible and can be used.
- Lifetime
- The period during program execution when a variable exists in memory and holds a value.
- Memoization
- An optimization technique that stores results of expensive function calls to avoid repeated computation.
- Function prototype (declaration)
- A forward declaration of a function (its name, return type and parameters) used in languages like C/C++ so it can be called before its definition.
Practice Questions
-
Define a recursive function and state its two essential components. / पुनरावर्ती (recursive) फलन की परिभाषा दीजिए तथा इसके दो आवश्यक घटक बताइए।
Show answer
A recursive function is one that calls itself to solve smaller subproblems; it must have a base case (stopping condition) and a recursive case (which reduces the problem). / पुनरावर्ती फलन वह है जो छोटी उपसमस्याओं को हल करने हेतु स्वयं को बुलाता है; इसमें आधार स्थिति (रुकने की शर्त) तथा पुनरावर्ती स्थिति (जो समस्या घटाती है) होनी चाहिए।
-
Differentiate between an argument and a parameter. / तर्क (argument) और प्राचल (parameter) में अंतर बताइए।
Show answer
A parameter is the variable named in the function definition (e.g., x, y in def add(x,y)), while an argument is the actual value passed during the call (e.g., 2, 3 in add(2,3)). / प्राचल फलन परिभाषा में नामित चर है (जैसे def add(x,y) में x,y), जबकि तर्क बुलाते समय पारित वास्तविक मान है (जैसे add(2,3) में 2,3)।
-
Write a recursive Python function to compute the factorial of n. / n का क्रमगुणित परिकलित करने हेतु पुनरावर्ती पायथन फलन लिखिए।
Show answer
def factorial(n):\n if n<=1:\n return 1\n return n*factorial(n-1) # base case n<=1 returns 1. / def factorial(n): if n<=1: return 1; return n*factorial(n-1) — आधार स्थिति n<=1 पर 1 लौटाता है।
-
Why does naive recursive Fibonacci have exponential time complexity, and how can it be improved? / सरल पुनरावर्ती फिबोनाची की समय जटिलता चरघातांकी क्यों होती है और इसे कैसे सुधारा जा सकता है?
Show answer
Because it recomputes overlapping subproblems giving T(n)=T(n-1)+T(n-2)+O(1) ≈ O(φ^n); using memoization or iterative DP reduces it to O(n). / क्योंकि यह अतिव्यापी उपसमस्याओं की पुनर्गणना करता है, T(n)=T(n-1)+T(n-2)+O(1) ≈ O(φ^n); मेमोआइज़ेशन या पुनरावृत्त DP से इसे O(n) किया जा सकता है।
-
Explain the difference between local and global scope, and the use of the 'global' keyword. / स्थानीय और वैश्विक क्षेत्र (scope) में अंतर तथा 'global' कीवर्ड के उपयोग को समझाइए।
Show answer
Local variables exist only inside the function where defined; global variables exist outside functions; the 'global' keyword lets a function modify a global variable. / स्थानीय चर केवल उस फलन के भीतर रहते हैं जहाँ परिभाषित हैं; वैश्विक चर फलनों के बाहर रहते हैं; 'global' कीवर्ड फलन को वैश्विक चर बदलने देता है।
-
Define the call stack and explain its role in recursion. / कॉल स्टैक की परिभाषा दीजिए तथा पुनरावर्तन में इसकी भूमिका समझाइए।
Show answer
The call stack stores an activation record (parameters, locals, return address) for each active call; in recursion each call pushes a new frame, and frames pop in reverse as base cases return. / कॉल स्टैक प्रत्येक सक्रिय कॉल हेतु सक्रियण अभिलेख (प्राचल, स्थानीय चर, वापसी पता) रखता है; पुनरावर्तन में हर कॉल नया फ्रेम जोड़ती है और आधार स्थिति लौटने पर फ्रेम उल्टे क्रम में हटते हैं।
-
How can a Python function return multiple values? Illustrate with min_max. / पायथन फलन एक से अधिक मान कैसे लौटा सकता है? min_max से दर्शाइए।
Show answer
By returning them as a tuple, e.g., def min_max(a,b,c): return min(a,b,c), max(a,b,c); the caller unpacks: mn, mx = min_max(4,1,9). / उन्हें टपल के रूप में लौटाकर, जैसे return min(...), max(...); बुलाने वाला अनपैक करता है: mn, mx = min_max(4,1,9)।
-
What is tail recursion, and why is converting it to iteration beneficial? / पुच्छ पुनरावर्तन (tail recursion) क्या है और इसे पुनरावृत्ति में बदलना लाभकारी क्यों है?
Show answer
Tail recursion is when the recursive call is the function's last operation; converting it to a loop reduces call-stack space from O(d) to O(1), avoiding stack overflow. / पुच्छ पुनरावर्तन तब है जब पुनरावर्ती कॉल फलन की अंतिम क्रिया हो; इसे लूप में बदलने से कॉल-स्टैक स्थान O(d) से O(1) हो जाता है, जिससे स्टैक ओवरफ्लो टलता है।
Related Laws & Principles
Explore allFoundational laws & principles connected to this chapter — tap to open in the Laws Explorer.