C++ Functions, Recursion & Storage Classes Interview Questions
C++ Functions, Recursion, and Storage Classes
Basic Level (15 Questions)
What is a function in C++ and what are its key components?
A function in C++ is a named block of code that performs a specific task and can be called multiple times from different parts of a program.
Key components of a function:
- Function declaration/prototype: Specifies function name, return type, and parameters
- Function definition: Contains the actual implementation of the function
- Function name: Identifier used to call the function
- Return type: Data type of value returned (void if no return)
- Parameters/arguments: Input values passed to the function
- Function body: Code statements that perform the task
- Return statement: Returns value to calling code (optional for void functions)
What are the different types of functions in C++?
Classification based on various criteria:
| Classification | Types | Description |
|---|---|---|
| By return type | Value-returning, Void | Returns a value or performs action only |
| By parameters | Parameterized, Parameterless | Accepts arguments or no arguments |
| By definition | Built-in, User-defined | Library functions or programmer-defined |
| By scope | Member, Non-member | Class member or standalone function |
| By call mechanism | Call by value, Call by reference, Call by pointer | How arguments are passed |
| Special types | Inline, Virtual, Pure virtual, Friend, Lambda (C++11) | Special function categories |
| By storage class | Static, Extern, Auto, Register | Storage duration and linkage |
What is the difference between function declaration and function definition?
| Aspect | Function Declaration (Prototype) | Function Definition |
|---|---|---|
| Purpose | Informs compiler about function existence | Provides actual implementation |
| Syntax | Return type, name, parameters (types only) | Return type, name, parameters, body |
| Memory | No memory allocation | Allocates memory for code |
| Location | Header files or top of source files | Source files (.cpp files) |
| Multiple declarations | Allowed (must be consistent) | Only one definition allowed |
| Example | int add(int a, int b); | int add(int a, int b) { return a + b; } |
| Required | When calling before definition | Required for function to exist |
- Declaration can appear multiple times, definition only once
- Declaration ends with semicolon, definition has body in braces
- Parameter names are optional in declaration but required in definition
- Default arguments can be specified in either declaration or definition, but not both
What are the different parameter passing mechanisms in C++?
| Mechanism | Syntax | Effect | Use Case |
|---|---|---|---|
| Pass by value | func(int x) | Creates copy, original unchanged | When input shouldn't be modified |
| Pass by reference | func(int &x) | Works on original variable | When modifying input or avoiding copy |
| Pass by const reference | func(const int &x) | Read-only access, no copy | Large objects, read-only access |
| Pass by pointer | func(int *x) | Works on original via address | C compatibility, optional parameters |
| Pass by const pointer | func(const int *x) | Read-only via pointer | C compatibility, read-only |
| Pass by rvalue reference (C++11) | func(int &&x) | For temporary objects | Move semantics, optimization |
- Use pass by value for primitive types and small objects
- Use pass by const reference for large objects and read-only access
- Use pass by reference when modification is needed
- Use pass by pointer for optional parameters or C compatibility
- Use rvalue references for move semantics optimization
What are default arguments in functions and what are the rules?
Default arguments allow parameters to have default values if not provided by caller.
Rules for default arguments:
- Default arguments must be specified only in function declaration, not definition
- Default values must be constant expressions or literals
- Default arguments must be at the end of parameter list (trailing arguments)
- Once a parameter has default value, all subsequent parameters must also have defaults
- Default arguments can be overridden by providing explicit values
- Default arguments are evaluated at call time, not declaration time
- In class member functions, default arguments can be specified in declaration or definition, but not both
- Provides flexibility in function calls
- Reduces number of overloaded functions
- Makes APIs more user-friendly
- Can make function signatures harder to read
- Default values become part of interface - changing them breaks binary compatibility
- Can cause confusion if overused
What is function overloading and what are its requirements?
Function overloading allows multiple functions with the same name but different parameters.
Requirements for overloading:
- Functions must have the same name
- Functions must differ in:
- Number of parameters, OR
- Type of parameters, OR
- Sequence/order of parameters
- Return type alone is NOT sufficient for overloading
- Constness can differentiate overloads for member functions
- Compiler performs name mangling to create unique names
- Overload resolution happens at compile time
- Compiler chooses best match based on argument types
- Provides intuitive function names for similar operations
- Reduces need for different function names
- Makes APIs cleaner and more readable
- Can cause ambiguity if overloads are too similar
- Return type cannot be used for differentiation
- Automatic type conversions can cause unexpected matches
What is recursion and what are its characteristics?
Recursion is a programming technique where a function calls itself directly or indirectly.
Characteristics of recursion:
- Base case: Condition that stops recursion (prevents infinite recursion)
- Recursive case: Part where function calls itself with modified parameters
- Progress toward base case: Each recursive call should move closer to base case
- Stack usage: Each call creates new stack frame (can cause stack overflow)
- Termination: Must eventually reach base case
- Direct recursion: Function calls itself directly
- Indirect recursion: Function calls another function which calls the original
- Tail recursion: Recursive call is last operation (can be optimized)
- Non-tail recursion: Operations after recursive call
- Mutual recursion: Two or more functions call each other
- Factorial calculation
- Fibonacci sequence
- Tower of Hanoi
- Binary tree traversals
- Divide and conquer algorithms (merge sort, quick sort)
- Backtracking algorithms
What is tail recursion and why is it important?
Tail recursion occurs when the recursive call is the last operation in the function.
Characteristics of tail recursion:
- No computation is performed after the recursive call
- Recursive call is in "tail position" (last operation before return)
- Function returns the value of the recursive call directly
- Tail Call Optimization (TCO): Compiler can optimize tail recursion to iteration
- No stack growth: Optimized version uses constant stack space
- Prevents stack overflow: For deep recursion
- Better performance: Eliminates function call overhead
- Add accumulator parameter to carry result
- Perform computation before recursive call
- Pass accumulated value to next call
- Base case returns accumulator
- Not all compilers perform TCO
- GCC and Clang have good TCO support with optimization flags
- C++ standard doesn't require TCO, but allows it
- For guaranteed optimization, use iteration instead
What are the advantages and disadvantages of recursion?
| Aspect | Advantages | Disadvantages |
|---|---|---|
| Readability | Elegant for recursive problems (trees, graphs) | Can be confusing for simple problems |
| Code simplicity | Often simpler than iterative solutions | Hidden complexity in function calls |
| Problem solving | Natural fit for divide-and-conquer | Not intuitive for all problems |
| Memory usage | - | High stack usage, risk of overflow |
| Performance | - | Function call overhead, slower |
| Debugging | - | Harder to debug (multiple stack frames) |
| Maintenance | Can be elegant and maintainable | Can become spaghetti if overused |
- Problems with recursive structure (trees, graphs, nested data)
- Divide and conquer algorithms
- Backtracking problems
- When recursive solution is significantly simpler
- When depth is limited and predictable
- Simple iterative solutions exist
- Deep recursion expected (risk of stack overflow)
- Performance-critical code
- Real-time systems with limited stack
- When tail recursion optimization not guaranteed
What are storage classes in C++ and what do they control?
Storage classes determine the scope, lifetime, and visibility of variables and functions.
Aspects controlled by storage classes:
- Scope: Where variable/function is accessible
- Lifetime: How long variable exists in memory
- Linkage: Whether variable/function can be accessed from other files
- Memory location: Where variable is stored (stack, heap, data segment)
- Initial value: Default initialization
- auto (C++11 meaning differs from C)
- register (deprecated in C++17)
- static
- extern
- mutable (for class members)
- thread_local (C++11)
- Local variables: auto (automatic storage)
- Global variables and functions: extern (external linkage)
What is the auto storage class in C++ (pre and post C++11)?
Pre C++11 (traditional meaning):
- auto indicated automatic storage duration
- Variables created on stack when block entered, destroyed when block exited
- Default for local variables (rarely explicitly used)
- Example: auto int x = 5; (redundant, as int x = 5; means the same)
- auto is a type specifier for type deduction
- Compiler deduces type from initializer
- Must have initializer (can't declare auto variable without initialization)
- Used to simplify complex type declarations
- Works with references (auto&), pointers (auto*), const (const auto)
- Common uses:
- Iterators: auto it = vec.begin();
- Lambda expressions: auto lambda = [](){ };
- Templates: auto result = function<T>(args);
- Range-based for loops: for(auto& element : container)
| Aspect | Pre C++11 | C++11+ |
|---|---|---|
| Purpose | Storage duration specifier | Type deduction specifier |
| Usage | auto int x; | auto x = expression; |
| Initialization | Optional | Required |
| Commonality | Rarely used explicitly | Commonly used |
| Compatibility | C compatibility | C++ specific feature |
What is the static storage class and its different uses?
The static storage class has different meanings based on context.
Static local variables:
- Initialized only once (first time function is called)
- Retains value between function calls
- Stored in data segment (not stack)
- Lifetime: entire program execution
- Scope: local to function
- Default initialization to zero
- Use case: Function that needs to remember state between calls
- Internal linkage (not accessible from other files)
- File scope only
- Avoids naming conflicts across files
- Use case: Helper functions/variables used only in one file
- Shared by all objects of the class
- Exists independently of any object
- Must be defined outside class (except const integral types)
- Can be accessed using class name (ClassName::staticMember)
- Use case: Class-wide counters, shared resources
- Can be called without object
- Can only access static members
- No this pointer
- Use case: Utility functions related to class
What is the extern storage class and when is it used?
The extern storage class is used to declare variables/functions that are defined elsewhere.
Purpose of extern:
- External linkage (accessible from other files)
- Declaration without definition
- Tells compiler "this exists somewhere else"
- Used for sharing variables/functions across multiple files
- Sharing global variables:
- File1.cpp: int globalVar = 42; (definition)
- File2.cpp: extern int globalVar; (declaration)
- Sharing functions:
- Default for functions (usually omitted)
- Explicit: extern void function();
- C linkage:
- extern "C" void c_function();
- Prevents C++ name mangling
- Used for C library compatibility
- extern declaration doesn't allocate memory
- Can have multiple extern declarations but only one definition
- Definition must occur somewhere in program
- Default for global variables and functions (but explicit extern clarifies intent)
- extern variables can be initialized only at definition, not declaration
- Use header files for extern declarations
- Minimize use of global variables (use alternatives when possible)
- Use namespaces to avoid naming conflicts
- Prefer static or anonymous namespaces for file-local variables
What are register and mutable storage classes?
Register storage class:
- register suggests compiler to store variable in CPU register
- Hint only - compiler may ignore
- Cannot take address of register variable (& operator not allowed)
- Deprecated in C++17 (register keyword removed)
- Modern compilers better at register allocation than programmers
- Historical use: Performance optimization for frequently accessed variables
- mutable applies to class data members only
- Allows modification even in const objects
- Useful for:
- Caching/memoization (storing computed results)
- Reference counting
- Debugging/tracing information
- Thread synchronization primitives (mutexes)
- Doesn't affect external const-ness (logical const-ness vs physical const-ness)
- Should be used judiciously (breaks const correctness)
| Aspect | register | mutable |
|---|---|---|
| Applicable to | Local variables | Class data members |
| Purpose | Performance hint | Allow modification in const objects |
| Current status | Deprecated (C++17) | Active feature |
| Common use | Rarely used, compiler optimizes better | Caching, mutexes in const methods |
| Can take address | No | Yes |
What is thread_local storage class (C++11)?
thread_local (C++11) specifies that a variable has thread storage duration.
Characteristics:
- Each thread has its own copy of the variable
- Created when thread starts, destroyed when thread ends
- Can be combined with static or extern
- Useful for thread-specific data (no need for thread-local storage APIs)
- Thread-local random number generators
- Thread-specific caches
- Error numbers (errno-like variables)
- Thread identification or context
- Performance counters per thread
- thread_local static: Thread-local with static initialization
- thread_local extern: Declaration of thread-local defined elsewhere
- Compiler/runtime manages thread-local storage
- May have performance overhead compared to automatic variables
- Portable alternative to platform-specific TLS APIs
- Dynamic initialization happens on first use per thread
| Storage | Lifetime | Per thread | Use case |
|---|---|---|---|
| auto | Block | No | Local variables |
| static | Program | No (shared) | Global/function state |
| thread_local | Thread | Yes | Thread-specific data |
Tricky Level (10 Questions)
What is inline functions and how do they differ from macros?
Inline functions:
Modern C++ alternatives:
- inline is a request to compiler to insert function code at call site
- Compiler may ignore inline request (especially for large functions)
- Reduces function call overhead
- Definitions must be available in every translation unit (usually in headers)
- Follows all C++ rules (type checking, scope, etc.)
| Aspect | Inline Functions | Macros (#define) |
|---|---|---|
| Type safety | Yes (full type checking) | No (text substitution) |
| Scope | Follows C++ scope rules | No scope (global text replacement) |
| Debugging | Easy (appears as function) | Hard (disappears after preprocessing) |
| Evaluation | Arguments evaluated once | Arguments may be evaluated multiple times |
| Syntax | Full C++ syntax | Preprocessor syntax |
| Error messages | Clear, at point of call | Obscure, at point of expansion |
| When expanded | Compile time | Preprocessing time |
- Compiler auto-inlining (modern compilers inline small functions automatically)
- constexpr functions (C++11): Evaluated at compile time when possible
- Template metaprogramming
- Lambda expressions for small, local operations
- Use inline for small, frequently called functions
- Place inline function definitions in header files
- Let compiler decide - modern compilers are good at inlining decisions
- Avoid macros for function-like behavior
- Use constexpr when compile-time evaluation is needed
What is function template and how does it relate to function overloading?
Function templates:
Template specialization:
- Blueprint for generating functions for different types
- Uses template parameters (typename T or class T)
- Compiler generates concrete functions (instantiations) as needed
- Provides generic programming
| Aspect | Function Overloading | Function Templates |
|---|---|---|
| Purpose | Different implementations for different types | Same algorithm for different types |
| Code | Multiple function definitions | Single template definition |
| Type handling | Explicit types for each overload | Generic type T |
| Compilation | All overloads compiled | Only used instantiations compiled |
| Flexibility | Limited to predefined types | Works with any compatible type |
| Specialization | Can have completely different implementations | Can have template specializations |
- Special implementation for specific types
- Can optimize or handle special cases
- Example: template<> void swap<MyClass>(MyClass&, MyClass&)
- Templates and overloading can work together
- Non-template functions are preferred over template instantiations
- More specialized templates are preferred over more general ones
- Overload resolution considers both templates and non-template functions
What are lambda expressions (C++11) and their characteristics?
Lambda expressions (C++11) are anonymous function objects.
Basic syntax:
- [capture](parameters) -> return_type { body }
- Can be stored in auto variables or std::function
- Can be invoked immediately or stored for later use
- []: Capture nothing
- [&]: Capture all by reference
- [=]: Capture all by value (deprecated in C++20)
- [var]: Capture specific variable by value
- [&var]: Capture specific variable by reference
- [this]: Capture class this pointer
- [=, &var]: Capture all by value, but var by reference
- Can have mutable specifier (allows modifying captured by-value variables)
- Can have noexcept specifier
- Can have constexpr (C++17)
- Can have trailing return type (auto or explicit)
- Can be generic with auto parameters (C++14)
- Can be templated (C++20)
- Lambdas are objects with overloaded operator()
- Can be stored, copied, passed as arguments
- Capture determines what variables are available
- By-value captures create copies (be careful with large objects)
- By-reference captures require captured variables to outlive lambda
- STL algorithms (sort, find_if, transform, etc.)
- Callback functions
- One-time simple operations
- Creating function objects on the fly
- Multithreading tasks
What are the memory locations for different storage classes?
| Storage Class | Memory Location | Lifetime | Initialization |
|---|---|---|---|
| auto (local) | Stack | Block | Garbage (unless initialized) |
| register | CPU Register (hint) | Block | Garbage (unless initialized) |
| static local | Data segment | Program | Zero (if not explicitly initialized) |
| static global | Data segment | Program | Zero (if not explicitly initialized) |
| extern | Data segment | Program | Zero (if not explicitly initialized) |
| thread_local | Thread-local storage | Thread | Zero (if static) or dynamic |
| dynamic (new) | Heap | Until delete | Garbage (unless initialized) |
- Stack: Automatic variables, function parameters, return addresses
- Heap: Dynamically allocated memory (new/delete, malloc/free)
- Data segment:
- .data: Initialized global/static variables
- .bss: Uninitialized or zero-initialized global/static variables
- Read-only data: String literals, const variables
- Text/Code segment: Executable code
- Thread-local storage: Per-thread data
- Stack: Fast allocation/deallocation, limited size
- Heap: Slower, flexible size, fragmentation possible
- Data segment: Fast access, fixed size at compile/link time
- Registers: Fastest, very limited
What are the best practices for functions, recursion, and storage classes in C++?
Function best practices:
- Keep functions small and focused (Single Responsibility Principle)
- Use descriptive function names that indicate purpose
- Limit number of parameters (consider struct for many parameters)
- Prefer pass by const reference for large objects
- Use noexcept for functions that don't throw
- Document preconditions, postconditions, and side effects
- Avoid functions with too many responsibilities
- Always define a clear base case
- Ensure progress toward base case
- Consider stack depth and potential overflow
- Use tail recursion when possible
- Consider iterative alternatives for performance-critical code
- Use memoization to optimize repeated calculations
- Document recursion depth expectations
- Minimize use of global variables (prefer local scope)
- Use static for file-local functions/variables
- Use const for variables that shouldn't change
- Use thread_local for thread-specific data in multithreaded programs
- Avoid register (deprecated, compiler optimizes better)
- Use mutable only when necessary and document why
- Prefer automatic variables (stack) for short-lived data
- Use dynamic allocation (heap) only when necessary
- Follow RAII (Resource Acquisition Is Initialization) for resource management
- Use auto for type deduction when type is obvious or verbose
- Use lambda expressions for small, local operations
- Use constexpr for compile-time evaluation when possible
- Use noexcept for exception specifications
- Use move semantics (&&) for efficient parameter passing
- Consider std::function for type-erased function objects
- Use templates for generic programming
- Prefer algorithms and lambdas over manual loops
What is tail recursion?
Recursive call is the final operation in the function. Compilers may optimize to iteration (tail call optimization), but not guaranteed in C++.
Why specify `noexcept` on move operations?
STL containers use move only when moves are noexcept; otherwise they copy for strong exception safety.
What is RVO (return value optimization)?
Compiler elides copy/move when returning a local object by value—mandatory in some C++17 cases (guaranteed RVO).
Difference between function pointer and `std::function`?
Function pointers are lightweight but fixed signature; std::function type-erases any callable with matching signature (heap may be used).
What is ADL (argument-dependent lookup)?
Unqualified calls also search namespaces associated with argument types—how swap and stream operators are found for user types.
Note: These questions cover core interview topics. Pair with the tutorial and MCQ quiz for this section. This page lists 15 basic and 10 tricky questions—use the tutorial and MCQ links above and below.