A function in C is a named, self-contained block of code that performs a specific task and can be called from anywhere in your program. Functions are the building blocks of every C program — they let you organize code into logical units, avoid repetition, and break large problems into smaller, manageable pieces.
This guide covers functions in C from the ground up: how to declare, define, and call them; the difference between passing arguments by value and by reference; recursion; function pointers; variadic functions; and inline functions.
All code examples in this guide are written in standard C and compile cleanly under the C11 standard (GCC, -Wall -Wextra), tested to produce the output shown.
Table of Contents
- What Is a Function in C?
- Structure of a Function
- Function Declaration, Definition, and Call
- Why Use Functions?
- A Simple Function Example
- Passing Arguments to a Function
- Returning Values from a Function
- Call by Value vs Call by Reference
- Types of Functions in C
- Recursive Functions
- Function Pointers
- Variadic Functions
- Inline Functions
- Common Mistakes with Functions
- Frequently Asked Questions
- Conclusion
What Is a Function in C?
A function groups a number of program statements into a single unit and gives it a name, so that unit can be run from other parts of the program just by calling its name. Instead of writing the same code repeatedly, you write it once inside a function and call it whenever you need it.
You pass information into a function through arguments, and the function can return a value back to the place it was called from — or return nothing at all. Every C program has at least one function: main(), where execution always begins.
Structure of a Function
A C function has two main parts: the function header and the function body.
int sum(int x, int y) // function header
{ // function body starts
int ans = x + y;
return ans; // returns the result
}
The function header (int sum(int x, int y)) has three parts:
- The return type — the data type of the value the function returns (
inthere) - The function name — a unique name used to call it (
sum) - The parameters — the inputs the function accepts, in parentheses (
int x, int y)
The function body is the code inside the curly braces {}. It contains the statements that do the work and, optionally, a return statement that sends a value back to the caller.
Function Declaration, Definition, and Call
Before you can use a function, the compiler needs to know about it. There are three related concepts:
The function prototype (declaration) tells the compiler the function’s name, return type, and parameters, so it can check that you call it correctly. It’s the header followed by a semicolon, usually placed near the top of the file:
int sum(int x, int y); // prototype — note the semicolon
The function definition is the full function, including its body — the actual code that runs.
The function call is how you run the function, by using its name with arguments in parentheses:
int result = sum(5, 3); // call — result becomes 8
The prototype and the definition must agree: same name, same return type, and the same parameter types in the same order.
Why Use Functions?
Functions exist for three main reasons:
- Avoid repetition. Any sequence of statements used more than once is a candidate to become a function. You write the code once and call it as many times as needed, and it’s stored in memory only once.
- Organize and simplify. Breaking a large program into small, single-purpose functions makes it far easier to write, read, test, and debug. Each function can be understood and checked independently.
- Reusability. A well-written function can be reused across different parts of a program — or even across different programs.
A Simple Function Example
Here’s a complete program with a simple function that prints a line of asterisks. If you’re brand new to writing and running C programs, start with your first C program, then come back — this builds on it. Notice the prototype at the top, the call inside main(), and the definition below.
#include <stdio.h>
void starline(void); // prototype
int main(void)
{
starline();
printf("Data Type Range\n");
starline();
printf("signed char -128 to 127\n");
printf("int system dependent\n");
printf("double very large range\n");
starline();
return 0;
}
// function definition
void starline(void)
{
for (int j = 0; j < 40; j++)
putchar('*');
putchar('\n');
}
Output:
****************************************
Data Type Range
****************************************
signed char -128 to 127
int system dependent
double very large range
****************************************
The void return type means starline() doesn’t return a value, and the empty (void) parameter list means it takes no arguments.
Passing Arguments to a Function
Arguments let a function work with different data each time it’s called. This version takes a character and a count, so it can print any character any number of times:
#include <stdio.h>
void repchar(char ch, int n); // prototype
int main(void)
{
repchar('-', 43);
printf("Data Type Range\n");
repchar('=', 23);
repchar('-', 43);
return 0;
}
void repchar(char ch, int n)
{
for (int j = 0; j < n; j++)
putchar(ch);
putchar('\n');
}
Output:
-------------------------------------------
Data Type Range
=======================
-------------------------------------------
The values you pass in the call ('-' and 43) are the arguments; the variables that receive them inside the function (ch and n) are the parameters. Their types must match what the prototype declares.
Returning Values from a Function
A function can return a single value using the return statement. This example converts pounds to kilograms and returns the result:
#include <stdio.h>
float lbs_to_kg(float pounds);
int main(void)
{
float weight = 150.0f;
float kg = lbs_to_kg(weight);
printf("%.1f lbs = %.2f kg\n", weight, kg);
return 0;
}
float lbs_to_kg(float pounds)
{
return 0.453592f * pounds;
}
Output:
150.0 lbs = 68.04 kg
The return type (float) is specified before the function name in both the prototype and the definition. A function that returns nothing uses the return type void.
Call by Value vs Call by Reference
This is one of the most important concepts with C functions. How you pass arguments determines whether the function can change the original variables.
Call by value copies the argument’s value into the parameter. Changes inside the function affect only the copy, not the original:
#include <stdio.h>
void swap_by_value(int x, int y)
{
int t = x;
x = y;
y = t;
printf("Inside function: x = %d, y = %d\n", x, y);
}
int main(void)
{
int a = 10, b = 20;
swap_by_value(a, b);
printf("After call: a = %d, b = %d\n", a, b);
return 0;
}
Output:
Inside function: x = 20, y = 10
After call: a = 10, b = 20
Notice that a and b are unchanged — the swap only affected the local copies.
Call by reference passes the addresses of the variables (using pointers), so the function can modify the originals:
#include <stdio.h>
void swap_by_reference(int *x, int *y)
{
int t = *x;
*x = *y;
*y = t;
}
int main(void)
{
int a = 10, b = 20;
swap_by_reference(&a, &b);
printf("After call: a = %d, b = %d\n", a, b);
return 0;
}
Output:
After call: a = 20, b = 10
This time the swap works, because the function received the addresses of a and b (via &a and &b) and modified the values at those addresses directly. C technically always passes by value — “call by reference” simply means passing pointers by value and dereferencing them.

Types of Functions in C
C functions fall into two broad categories:
- Library functions are built into the C standard library, ready to use after including the right header. Examples include
printf()andscanf()(from<stdio.h>),strlen()(from<string.h>), andsqrt()(from<math.h>). - User-defined functions are the functions you write yourself to perform specific tasks — like
sum(),repchar(), andswap_by_reference()above.
A few important rules about C functions:
- Every C program must have a
main()function; execution begins there. - A function can be called any number of times, from any other function.
- The order functions are defined in doesn’t have to match the order they’re called (as long as prototypes are declared).
- A function cannot be defined inside another function — all functions are defined at the top level.
- A function can call itself, which is called recursion.
Recursive Functions
A recursive function is one that calls itself to solve a problem by breaking it into smaller versions of the same problem. Every recursion needs a base case that stops it, or it will run forever.
Factorial is the classic example — n! = n × (n-1)!:
#include <stdio.h>
long factorial(int n)
{
if (n <= 1) // base case
return 1;
return n * factorial(n - 1); // recursive case
}
int main(void)
{
printf("factorial(5) = %ld\n", factorial(5));
return 0;
}
Output:
factorial(5) = 120
Fibonacci numbers are another common recursive example, where each number is the sum of the previous two:
#include <stdio.h>
int fibonacci(int n)
{
if (n < 2) // base cases: fib(0)=0, fib(1)=1
return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
int main(void)
{
printf("fibonacci(10) = %d\n", fibonacci(10));
return 0;
}
Output:
fibonacci(10) = 55
Recursion is elegant for problems that are naturally self-similar — factorials, tree traversal, and divide-and-conquer algorithms. Just remember: the recursive Fibonacci above is simple but slow for large n, because it recalculates the same values many times. For performance-critical code, an iterative version or memoization is better.
Function Pointers
A function pointer stores the address of a function, letting you pass functions as arguments, store them in arrays, or choose which function to call at runtime. This is the foundation of callbacks in C.
#include <stdio.h>
int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }
// takes a pointer to a function as its first parameter
int apply(int (*op)(int, int), int a, int b)
{
return op(a, b);
}
int main(void)
{
printf("apply(add, 4, 5) = %d\n", apply(add, 4, 5));
printf("apply(multiply, 4, 5) = %d\n", apply(multiply, 4, 5));
return 0;
}
Output:
apply(add, 4, 5) = 9
apply(multiply, 4, 5) = 20
The syntax int (*op)(int, int) declares op as a pointer to a function that takes two ints and returns an int. Function pointers power callbacks, event handlers, and pluggable behavior — for example, the standard library’s qsort() uses a function pointer to know how to compare elements.
Variadic Functions
A variadic function accepts a variable number of arguments — just like printf() does. You build one using the macros in <stdarg.h>:
#include <stdio.h>
#include <stdarg.h>
int sum_all(int count, ...)
{
va_list args;
va_start(args, count);
int total = 0;
for (int i = 0; i < count; i++)
total += va_arg(args, int);
va_end(args);
return total;
}
int main(void)
{
printf("Sum of 10, 20, 30, 40 = %d\n", sum_all(4, 10, 20, 30, 40));
return 0;
}
Output:
Sum of 10, 20, 30, 40 = 100
The ... in the parameter list means “any number of additional arguments.” va_start initializes access, va_arg retrieves each argument in turn, and va_end cleans up. The first fixed parameter (count) tells the function how many extra arguments to expect.
Inline Functions
An inline function is a hint to the compiler to insert the function’s code directly at each call site, rather than making a separate function call. This can eliminate the small overhead of a call for tiny, frequently-used functions:
#include <stdio.h>
static inline int square(int x)
{
return x * x;
}
int main(void)
{
printf("square(6) = %d\n", square(6));
return 0;
}
Output:
square(6) = 36
The inline keyword (standardized in C99) is only a suggestion — the compiler decides whether to actually inline. Use it for very small functions where call overhead matters; for larger functions it offers no benefit and can increase code size.
Common Mistakes with Functions
- Forgetting the prototype. Calling a function before the compiler has seen its prototype or definition causes errors (or, in older C, silent bugs). Declare prototypes near the top of the file.
- Mismatched types. The argument types in a call must match the parameter types in the prototype and definition.
- Expecting call-by-value to change the original. Passing a plain variable can’t modify the caller’s copy — you need pointers (call by reference) for that.
- Missing base case in recursion. A recursive function without a proper stopping condition runs until it crashes (stack overflow).
- Forgetting
returnin a non-void function. A function declared to return a value should always return one on every path.
Frequently Asked Questions
Conclusion
Functions are the foundation of well-structured C programs. They let you break large problems into small, named, reusable pieces — and mastering them means understanding not just how to declare, define, and call them, but the deeper concepts: how call by value differs from call by reference, how recursion solves self-similar problems, and how function pointers make callbacks possible.
The habits that matter most are simple: always declare prototypes, match your argument types, give every recursion a base case, and reach for pointers when a function needs to modify its caller's data. Master those, and you have the core skill that everything else in C builds on. To keep learning, explore our complete C programming tutorials, or see how functions handle files in our guide to file handling in C.

