C · Beginner

Functions in C

10 min readUpdated September 24, 2026Every example verified

In short: A C function is a named block with a return type, a parameter list and a body, such as int area(int w, int h) { return w * h; }. A call copies the argument values into the parameters, runs the body and hands back the return value; changes to a parameter never reach the caller's variable. A function must be declared, by a prototype or its full definition, before the line that calls it.

Naming a piece of work

main can hold an entire program, but a program that does three things in one long block is hard to read, hard to test and impossible to reuse. A function packages one job under a name: what goes in (the parameters), what comes out (the return value) and how it is done (the body). The caller only needs to know the first two.

A definition has four parts: the return type, the name, the parameter list in parentheses and the body in braces. int fare_pence(int zones, int peak) returns an int and takes two int parameters. Inside the body, return expression; ends the function and delivers the value; a function may have several return statements, and returning early from a guard clause is often clearer than nesting the rest in an else. A function that produces nothing has return type void and either ends at its closing brace or uses a bare return;. A function that takes nothing is written with (void) in its parameter list, as main is.

Calling a function is writing its name with arguments in parentheses: fare_pence(2, 1). C copies each argument value into the corresponding parameter, in order, and the parameters are ordinary local variables of the function. This is pass by value: assigning to a parameter changes the copy and nothing else. To give the caller a result, return it; to let a function change the caller's variable directly, you pass its address, which is the subject of the pointers lesson. Arrays are the exception you meet first; see arrays.

C reads a file top to bottom, and it must know a function's return type and parameter types before it can compile a call. Either define the function above the call, or put a prototype above it: the first line of the definition followed by a semicolon, int fare_pence(int zones, int peak);. Prototypes let main sit at the top where readers look for it, and let two functions call each other. gcc 13 warns about a call to an undeclared function and gcc 14 refuses to compile it.

Variables declared inside a function, parameters included, are local: they exist only during the call, and two functions may use the same name for different variables without conflict. Variables declared outside all functions are global, visible everywhere after their declaration, and worth avoiding until you have a reason: they make it impossible to tell from a call what a function touches.

Well-chosen functions read like a description of the program: read_readings, average, print_report. When a name needs the word "and", the function is doing two jobs; split it.

Syntax

 C · syntax
return_type name(type param1, type param2);   /* prototype: ends with ; */

return_type name(type param1, type param2)    /* definition */
{
    body;
    return value;                             /* required unless return_type is void */
}

void name(void);                              /* takes nothing, returns nothing */

result = name(arg1, arg2);                    /* call: arguments are copied in */

Parameter names in a prototype are optional (int fare_pence(int, int);) but keeping them documents the call.

Parts of a function

PartExampleMeaning
Return typeinttype of the value the call produces; void for none
Namefare_pencehow the function is called
Parameters(int zones, int peak)local variables filled from the arguments
Body{ ... }the statements that run on each call
returnreturn pence;ends the call and delivers the value
Prototypeint fare_pence(int, int);tells the compiler the signature before the definition

Prototype, definition and call

Two functions are declared above main and defined below it; main calls them to price three bus tickets.

 C
#include <stdio.h>

int fare_pence(int zones, int peak);
void print_fare(int zones, int peak, int pence);

int main(void)
{
    print_fare(2, 0, fare_pence(2, 0));
    print_fare(2, 1, fare_pence(2, 1));
    print_fare(5, 1, fare_pence(5, 1));
    return 0;
}

int fare_pence(int zones, int peak)
{
    int pence = 150 + zones * 45;
    if (peak) {
        pence += 80;
    }
    return pence;
}

void print_fare(int zones, int peak, int pence)
{
    printf("%d zone(s), %s: %d.%02d\n", zones, peak ? "peak" : "off-peak", pence / 100, pence % 100);
}

Output

2 zone(s), off-peak: 2.40
2 zone(s), peak: 3.20
5 zone(s), peak: 4.55

The two prototypes let main call fare_pence and print_fare although their definitions come later. Each call to fare_pence copies its arguments into fresh zones and peak variables, computes a price in pence and returns it; the result is passed straight on as the third argument of print_fare, which returns nothing and only prints. Keeping money in whole pence and splitting it with / 100 and % 100 avoids floating-point rounding; %s prints the string chosen by the conditional operator.

Arguments are copied

One function tries to change its parameter; the other returns a new value instead. Watch what happens to the caller's variable.

 C
#include <stdio.h>

void try_to_double(int n)
{
    n = n * 2;
    printf("inside: n = %d\n", n);
}

int doubled(int n)
{
    return n * 2;
}

int main(void)
{
    int crates = 8;
    try_to_double(crates);
    printf("after call: crates = %d\n", crates);
    crates = doubled(crates);
    printf("after assignment: crates = %d\n", crates);
    return 0;
}

Output

inside: n = 16
after call: crates = 8
after assignment: crates = 16

try_to_double doubles its own n and prints 16, but crates in main is still 8 afterwards: the function received a copy. The second function returns the doubled value instead, and main stores it with an assignment, which is the normal way to get a result out of a function.

Early returns and reuse

One function answers a question that has several cases; main calls it twice with different arguments. The input is 9 2024.

 C
#include <stdio.h>

int days_in_month(int month, int year)
{
    if (month == 2) {
        int leap = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
        return leap ? 29 : 28;
    }
    if (month == 4 || month == 6 || month == 9 || month == 11) {
        return 30;
    }
    return 31;
}

int main(void)
{
    int month, year;
    if (scanf("%d %d", &month, &year) != 2) {
        return 1;
    }
    printf("%d days\n", days_in_month(month, year));
    printf("February %d has %d days\n", year, days_in_month(2, year));
    return 0;
}

Input given to the program: 9 2024

Output

30 days
February 2024 has 29 days

Each return ends the function immediately, so the checks read as a list of special cases followed by the general answer, with no else chains. The second call shows the payoff of a function: the leap-year logic is written once and used for any year. leap is an int holding 1 or 0 from the logical expression, then used by the conditional operator. Because the function is defined above main, no prototype is needed.

Common mistakes

  • Calling a function before the compiler has seen it

    Why it goes wrong: With the definition below main and no prototype, gcc 13 reports implicit declaration of function and assumes it returns int, so a function that really returns double produces garbage; gcc 14 refuses to compile it.

    Fix: Add a prototype above the first call, or move the definition above main.

     C · fix
    double average(int total, int count);   /* prototype */
    
    int main(void)
    {
        printf("%.1f\n", average(7, 2));
        return 0;
    }
    
    double average(int total, int count)
    {
        return (double) total / count;
    }
  • Expecting a function to change its argument

    Why it goes wrong: Parameters are copies. void halve(int n) { n = n / 2; } halves its own n and returns; the caller's variable keeps its value.

    Fix: Return the new value and assign it in the caller, or pass a pointer once you have met them.

     C · fix
    int halved(int n)
    {
        return n / 2;
    }
    /* in the caller: */
    stock = halved(stock);
  • A non-void function without a return on some path

    Why it goes wrong: If execution reaches the closing brace of a function that promises an int, the value the caller receives is undefined. gcc -Wall reports control reaches end of non-void function.

    Fix: Make sure every path returns, usually by ending with an unconditional return.

     C · fix
    int sign(int x)
    {
        if (x > 0) {
            return 1;
        }
        if (x < 0) {
            return -1;
        }
        return 0;
    }
  • Prototype and definition that disagree

    Why it goes wrong: int scale(int v); above and double scale(double v) { ... } below are two different functions to the compiler, and it stops with conflicting types for 'scale'.

    Fix: Copy the definition's first line exactly, add a semicolon, and change both when the signature changes.

Splitting an exercise into steps

A typical exercise reads some numbers, computes something and prints a result. Written as separate functions, say read_values, compute and print_result, each piece can be checked on its own: call compute with values you typed in and print what it returns before wiring up the input. The compute function is also where the reasoning lives, free of scanf and printf clutter, which makes it easy to reread when the output is wrong. Later lessons on arrays and recursion rely on this habit: a function that takes an array and a length, or that calls itself, is only manageable when its job is small and named.

 C · in practice
int shipping_pence(int weight_grams)
{
    if (weight_grams <= 500) {
        return 150;
    }
    return 150 + (weight_grams - 500 + 249) / 250 * 40;
}

Key points

  • Definition: return type, name, parameters, body. Call: name(arguments).
  • Arguments are copied into the parameters; a function cannot change the caller's variables through them.
  • Return a value with return expr;; use void when there is nothing to return.
  • The compiler must see a prototype or the definition before the first call.
  • Parameters and variables declared inside the body are local to each call.
  • Several early returns are fine and often clearer than nested else blocks.
  • Give each function one job and a name that says what it returns or does.

Try it yourself

Complete round_up_to so it returns the smallest multiple of step that is greater than or equal to value. main reads two integers, value and step (step is at least 1). For the input 47 10 the program prints 50.

Your program
#include <stdio.h>

int round_up_to(int value, int step)
{
    /* return the smallest multiple of step that is >= value */
    return 0;
}

int main(void)
{
    int value, step;
    if (scanf("%d %d", &value, &step) != 2) {
        return 1;
    }
    printf("%d\n", round_up_to(value, step));
    return 0;
}
Input the program receives: 47 10
Expected output: 50

Practise this

Exercises for this lesson are in the C practice set.

Open the C playground

Frequently asked questions

Why does C need function prototypes?

The compiler works through a file from top to bottom and generates code for a call as soon as it meets one. To do that correctly it must already know the return type and the parameter types, so that arguments are converted properly and the result is used with the right type. A prototype supplies that information without the body, which lets you order the file for readers and lets functions refer to each other. Header files such as stdio.h are largely collections of prototypes.

Can a C function return two values?

Not directly: a function has one return value. The usual routes are to return a struct that bundles the values (see structures), or to pass the addresses of variables the function should fill in, which is how scanf delivers several values at once and is covered in the pointers lesson.

Can I define a function inside another function?

Not in standard C. gcc accepts nested functions as an extension, but the code will not compile elsewhere, so keep every definition at file level. If you want a helper that only one function uses, define it above that function and add static in front to keep it private to the file.

Progress is stored only in this browser.

How this page was checked. Every program on it was run with GCC 13 (C11) at build time by the publishing checks, and the output shown is what it printed. Running C inside the browser is not available yet, so the Run button is absent rather than pretending; copy the code and run it with GCC 13 (C11) locally.