Functions
Functions
Master C Functions — what/why, types of functions, types of calling a user-defined function, and the function prototype — using LEARNCODEGURU’s 6-level learning model.
Level 1 Basics Content
C Functions Overview
A function is a named block of code that performs one task. You write it once and call it as many times as you need. This level covers what a function is, why functions matter, types of functions, the four types of calling a user-defined function, the function prototype, definition, and calling.
Every C program already uses functions: main() is a function, and
printf() / scanf() are library functions you call.
What is a function?
A function groups statements under a name, optionally accepts parameters (inputs) and optionally returns a value (output).
int add(int a, int b) { /* return type, name, parameters */
return a + b; /* body — the work */
}
- Return type — type of value sent back (
voidif none) - Name — identifier used to call it
- Parameters — inputs declared in the brackets
- Body — statements inside
{ } - return — sends a value back and ends the function
Why functions?
- Reusability — write once, call many times
- Readability —
main()becomes a short summary of steps - Divide and conquer — break a big problem into small tasks
- Easier testing / debugging — check one small function at a time
- Less duplication — fix a bug in one place, not ten
- Teamwork — different people can write different functions
Types of functions
C functions fall into two broad groups:
| # | Type | Who writes it | Where it lives | Examples |
|---|---|---|---|---|
| 1 | Library (built-in) functions | Provided by the C standard library | Declared in headers like <stdio.h>, <math.h>, <string.h> | printf, scanf, sqrt, strlen |
| 2 | User-defined functions | You, the programmer | In your own .c file(s) | add, findMax, printMenu |
User-defined functions are further grouped by arguments and return value:
| # | Category | Signature example | Use when |
|---|---|---|---|
| 1 | No arguments, no return | void greet(void); | Fixed action like printing a banner |
| 2 | With arguments, no return | void printArr(int a[], int n); | Needs input, produces only output on screen |
| 3 | No arguments, with return | int readNumber(void); | Gets data itself and hands back a value |
| 4 | With arguments, with return | int add(int a, int b); | Most common — compute a result from inputs |
Types of calling a user-defined function
Two things are commonly meant by “types of calling”: the four argument/return combinations above, and the mechanism used to pass data.
(a) Call by value — a copy is sent; the original is unchanged:
void change(int x) {
x = 100; /* only the copy changes */
}
int n = 5;
change(n); /* n is still 5 */
(b) Call by reference (via pointers) — an address is sent; the original changes:
void change(int *p) {
*p = 100; /* writes through the address */
}
int n = 5;
change(&n); /* n becomes 100 */
| Aspect | Call by value | Call by reference |
|---|---|---|
| What is passed | Copy of the value | Address of the variable |
| Caller variable | Unchanged | Can be modified |
| Syntax at call | f(n) | f(&n) |
| Parameter type | int x | int *p |
| Best for | Simple computations | Multiple results, in-place edits, large data |
Function prototype (declaration)
A prototype tells the compiler the function’s name, return type, and parameter types before it is used, so calls can be checked.
int add(int a, int b); /* prototype — ends with ; */
void greet(void); /* takes no arguments */
double area(double r); /* returns a double */
int add(int, int); /* names are optional */
| Part | Prototype | Definition | Call |
|---|---|---|---|
| Purpose | Announce the signature | Provide the body | Execute it |
| Ends with | ; | { ... } | ; (as a statement) |
| How many times | Many (identical) allowed | Exactly once | Any number of times |
| Example | int add(int, int); | int add(int a, int b) { ... } | s = add(2, 3); |
void in the brackets for “no parameters”:
void greet(void); — not void greet();.
Definition, call, and return
return_type name(parameter list) {
/* local declarations */
/* statements */
return value; /* omit for void */
}
- Actual arguments — values you pass at the call:
add(2, 3) - Formal parameters — names in the definition:
int a, int b - return — ends the function immediately and sends a value back
voidfunctions may use a barereturn;to exit early
s = add(2,3);), conditions (if (isEven(n))), or discard the result.
Local vs global — scope basics
- Local variables — declared inside a function; exist only during the call
- Parameters — behave like locals initialized from the arguments
- Global variables — declared outside all functions; visible everywhere (use sparingly)
- Two functions may use the same local name without any clash
Sample program — prototype, definition, and calls
/* Functions basics: prototype above main, definitions below */
#include <stdio.h>
void greet(void); /* no args, no return */
int add(int a, int b); /* args + return */
void doubleIt(int *p); /* call by reference */
int main(void) {
int x, y, sum;
greet();
printf("Enter two integers: ");
scanf("%d %d", &x, &y);
sum = add(x, y);
printf("Sum = %d\n", sum);
doubleIt(&x);
printf("x after doubleIt = %d\n", x);
return 0;
}
void greet(void) {
printf("=== LEARNCODEGURU C Functions ===\n");
}
int add(int a, int b) {
return a + b;
}
void doubleIt(int *p) {
*p = *p * 2;
}
greet takes nothing and returns nothing;
add uses call by value; doubleIt receives an address and changes
x in main.
Level 2 Advanced Content
Going deeper into functions
Level 1 covered what/why, types, calling styles, and prototypes. Level 2 explains the
stack frame, passing arrays and strings, returning values safely,
recursion, static / extern linkage,
multi-file organisation, and common mistakes.
How a call works — the stack frame
Each call pushes a frame holding the parameters, local variables, and the return location. When the function returns, its frame is popped — which is why locals do not survive the call.
- Deep call chains and recursion consume stack space
- Large local arrays live in the frame — huge ones can overflow the stack
- Return value travels back to the caller’s expression
Passing arrays, strings, and structs
int findMax(int a[], int n); /* 1D array + length */
void printMat(int m[][3], int rows); /* 2D: columns must be known */
int countVowels(const char s[]); /* string ends at '\0' */
- Arrays decay to pointers — the function can modify the caller’s data
- Strings need no length parameter (the
'\0'marks the end) - Structs are copied by value unless you pass
struct T * - Use
conston parameters you promise not to modify
Return values — rules and traps
| Situation | Allowed? | What to do |
|---|---|---|
| Return one value | Yes | Use return expr; |
| Return two values | Not directly | Use pointer parameters or return a struct |
| Return an array | No | Return a pointer to valid memory, or fill a caller buffer |
| Return address of a local | Undefined behavior | Use static, heap, or caller-provided storage |
| Non-void with no return | Undefined if the value is used | Always return on every path |
Recursion — a function calling itself
long fact(int n) {
if (n <= 1)
return 1; /* base case — stops recursion */
return n * fact(n - 1); /* recursive case */
}
- Every recursion needs a base case and progress toward it
- Each level uses its own stack frame — deep recursion can overflow
- Iteration is often faster; recursion is clearer for trees and divide-and-conquer
Storage & linkage — static, extern, inline
| Keyword | On a function | On a local variable |
|---|---|---|
static | Visible only in this file (internal linkage) | Keeps its value between calls |
extern | Declares a function defined elsewhere (default for functions) | Refers to a global defined in another file |
inline (C99) | Hint to expand the call in place | Not applicable |
int counter(void) {
static int count = 0; /* initialized once; survives calls */
count++;
return count;
}
Organising functions across files
- Put prototypes in a header (
.h) file - Put definitions in a source (
.c) file #include "myheader.h"wherever you call those functions- Mark file-private helpers
staticto avoid name clashes
Common mistakes
| Mistake | Why it hurts | Fix |
|---|---|---|
| Calling before declaring | No argument checking / invalid in modern C | Add a prototype above main |
| Semicolon after the definition header | int f(int a); { ... } — becomes a stray declaration | No ; before the body brace |
| Expecting call by value to change the caller | Original variable stays the same | Pass &x and use *p |
| Prototype and definition disagree | Type mismatch / conflicting types error | Keep return type and parameter types identical |
Using sizeof on an array parameter | Gives pointer size, not length | Pass the length explicitly |
| Recursion without a base case | Stack overflow at runtime | Handle the smallest case first |
Level 3 C Tricky Interview Points
Advanced tricky interview points on prototypes, call by value vs reference, array decay, return rules, linkage, and recursion.
Tricky Interview Point-1:
A prototype declares; a definition provides the body
int add(int, int); is a declaration (prototype) ending with a semicolon. int add(int a, int b) { ... } is the definition. You may declare many times but define exactly once in a program.
Tricky Interview Point-2:
Missing prototype before use is a real bug, not just a warning
Since C99, calling an undeclared function is invalid. Older compilers assumed int return and unchecked arguments, which silently broke float/double returns. Always declare before the first call or define above main.
Tricky Interview Point-3:
C is call by value — always
Arguments are copied into parameters. Changing a parameter inside the function does not affect the caller's variable. "Call by reference" in C is simulated by passing addresses (pointers) — the pointer itself is still passed by value.
Tricky Interview Point-4:
void f() and void f(void) are not the same in C
In C, f() means "unspecified parameters" (no argument checking in old rules), while f(void) means "takes no arguments." Use (void) explicitly. In C++ they are equivalent — a common cross-language trap.
Tricky Interview Point-5:
Arrays passed to a function decay to pointers
void f(int a[]) is really void f(int *a). sizeof(a) inside gives the pointer size, not the array size, so pass the length separately. Changes to a[i] inside DO affect the caller's array.
Tricky Interview Point-6:
Returning a pointer to a local variable is undefined behavior
The local's lifetime ends when the function returns, so the pointer dangles. Return by value, use a caller-provided buffer, use static storage, or allocate on the heap.
Tricky Interview Point-7:
Falling off the end of a non-void function is undefined use
If a function is declared int but reaches the closing brace without return, the value returned is indeterminate and using it is undefined. main is special: reaching the end of main implies return 0 since C99.
Tricky Interview Point-8:
Default argument promotions apply where no prototype is in scope
Without a prototype, float promotes to double and small integer types to int. That is why a printf-style call with mismatched types misbehaves — variadic functions rely on promotions and the format string.
Tricky Interview Point-9:
Parameter names in a prototype are optional and ignored
int add(int, int); and int add(int x, int y); declare the same function. Names in prototypes are documentation only; only the types (and order) matter for compatibility.
Tricky Interview Point-10:
A function cannot return an array, but it can return a pointer or struct
int f()[10] is invalid. You can return int * (pointing to valid memory) or return a struct by value that wraps the data. This is a favorite interview distinction.
Tricky Interview Point-11:
static on a function limits it to the current file
static int helper(void) has internal linkage — invisible to other translation units. Non-static functions are external by default and can clash across files if names repeat.
Tricky Interview Point-12:
Recursion needs a base case and consumes stack per call
Every recursive call adds a frame with its own parameters and locals. Without a base case (or with a wrong one) you get infinite recursion and stack overflow, not a compile error.
Level 4 Simple Programs
Short programs covering the four calling categories, call by value vs reference, arrays, and recursion.
Program 1 — All four argument/return categories
/* no-arg/no-return, arg/no-return, no-arg/return, arg/return */
#include <stdio.h>
void banner(void);
void showSquare(int n);
int readNumber(void);
int add(int a, int b);
int main(void) {
int a, b;
banner();
a = readNumber();
b = readNumber();
showSquare(a);
printf("a + b = %d\n", add(a, b));
return 0;
}
void banner(void) {
printf("--- Function Demo ---\n");
}
void showSquare(int n) {
printf("Square of %d = %d\n", n, n * n);
}
int readNumber(void) {
int v;
printf("Enter a number: ");
scanf("%d", &v);
return v;
}
int add(int a, int b) {
return a + b;
}
Program 2 — Call by value vs call by reference (swap)
/* Only the pointer version changes main's variables */
#include <stdio.h>
void swapValue(int x, int y);
void swapRef(int *x, int *y);
int main(void) {
int a = 10, b = 20;
swapValue(a, b);
printf("After swapValue: a=%d b=%d\n", a, b);
swapRef(&a, &b);
printf("After swapRef: a=%d b=%d\n", a, b);
return 0;
}
void swapValue(int x, int y) {
int t = x; x = y; y = t; /* copies only */
}
void swapRef(int *x, int *y) {
int t = *x; *x = *y; *y = t;
}
Program 3 — Functions with an array (read, max, print)
/* Array parameters always need the length */
#include <stdio.h>
void readArray(int a[], int n);
int findMax(int a[], int n);
void printArray(int a[], int n);
int main(void) {
int a[100], n;
printf("Enter n: ");
scanf("%d", &n);
readArray(a, n);
printArray(a, n);
printf("Max = %d\n", findMax(a, n));
return 0;
}
void readArray(int a[], int n) {
int i;
printf("Enter %d integers:\n", n);
for (i = 0; i < n; i++)
scanf("%d", &a[i]);
}
int findMax(int a[], int n) {
int i, max = a[0];
for (i = 1; i < n; i++)
if (a[i] > max)
max = a[i];
return max;
}
void printArray(int a[], int n) {
int i;
printf("Array: ");
for (i = 0; i < n; i++)
printf("%d ", a[i]);
printf("\n");
}
Program 4 — Factorial: loop vs recursion
/* Same result, two styles */
#include <stdio.h>
long factLoop(int n);
long factRec(int n);
int main(void) {
int n;
printf("Enter n: ");
scanf("%d", &n);
printf("Loop: %ld\n", factLoop(n));
printf("Recursive: %ld\n", factRec(n));
return 0;
}
long factLoop(int n) {
long f = 1;
int i;
for (i = 2; i <= n; i++)
f *= i;
return f;
}
long factRec(int n) {
if (n <= 1)
return 1;
return n * factRec(n - 1);
}
Level 5 Problem Solving Tricks
How to master functions while solving problems
Most function bugs come from missing prototypes, expecting call by value to change the caller, losing array lengths, and forgetting a return on some path. These habits keep your programs modular and predictable.
Decide the signature before writing the body
Ask: what does it need (parameters), what does it give back (return type), does it change caller data (pointers)? Writing the prototype first prevents half the bugs.
One function, one job
If you cannot name the function with a single verb phrase, split it. Small functions like readArray, findMax, printArray are easier to test than one giant main.
Use call by reference only when you must change the caller's data
Need two results, or must modify a variable in place? Pass &x and use *p. Otherwise pass by value — it is simpler and safer.
Always pass array length alongside the array
void printArr(int a[], int n). Inside the function sizeof cannot tell you the length. For 2D, pass the column count in the type and rows as a parameter.
Put prototypes above main, definitions below
This layout keeps main readable as a summary of your program and guarantees every call sees a declaration first. It also makes ordering of definitions irrelevant.
Validate inputs at the top of the function
Check n > 0, pointer != NULL, divisor != 0 and return an error code early. Guard clauses keep the main logic flat and avoid crashes deep inside.
Return a status, output through parameters when needed
A common C pattern: int parse(const char *s, int *out) returns 0/1 for success and writes the value through out. It separates "did it work" from "what is the value".
Trace one call by hand — draw the stack frame
Write the parameter values, locals, and the return value for a single call. For recursion, draw two or three levels. Most logic errors become obvious on paper.
Quick success checklist
- Before run: prototype above main? types match definition? every path returns?
- Debug order: print arguments on entry → check the return value → trace one frame on paper
- Design habit: one job per function → pass length with arrays → use pointers only to modify
Level 6 Practice Programs List
Programming questions only — focused on functions: prototypes, the four calling categories, call by value vs reference, array parameters, and recursion.
- 1. Write a C program with a function add(int, int) that returns the sum of two integers read in main.
- 2. Write a C program with a void function that takes no arguments and prints a welcome banner (function with no args, no return).
- 3. Write a C program with a function isEven(int) that returns 1 or 0, and use it in main to classify n numbers.
- 4. Write a C program with a function swapValue(int, int) and swapRef(int *, int *) to demonstrate call by value vs call by reference.
- 5. Write a C program with a function that computes the factorial of n, first using a loop and then using recursion.
- 6. Write a C program with functions readArray(int[], int), findMax(int[], int), and printArray(int[], int) called from main.
- 7. Write a C program with a function that returns the area of a circle as a double, using a prototype declared above main.
- 8. Write a C program with a function countVowels(char[]) that returns how many vowels a string contains.