A return value is the result that a function passes back to the code that called it when the function finishes executing. In programming fundamentals, the return value is the main way a function delivers its output: the caller supplies inputs through parameters, and the function answers with a value that can be stored in a variable, passed to another function or used in an expression.
Return values make functions composable, because the output of one call can become the input of the next. How a language types, transfers and signals failure through return values strongly shapes the style and reliability of the code written in it.
How a function returns a value
A function returns a value by executing a return statement, which ends the function immediately and transfers both control and the result to the caller. Any code after an executed return statement in the same path does not run, which is why an early return is a common way to handle special cases at the top of a function.
At the machine level, the calling convention of the platform decides where the result travels. Small values such as integers and pointers usually pass through a CPU register, for example RAX on x86-64 or x0 on 64-bit ARM, while larger structures are written to memory reserved by the caller. The function’s stack frame is then removed, so a function must never return a pointer or reference to its own local variable in languages with manual memory management.
Languages differ in what happens when no explicit value is returned. The table below summarizes the default behavior in several common languages.
| Language | Default result | Notes |
|---|---|---|
| Python | None | Every function returns something |
| JavaScript | undefined | Arrow functions with an expression body return it implicitly |
| C and C++ | Nothing for void functions | Missing return in a non-void function is a serious error |
| Rust | Value of the final expression | Unit type () when there is no final expression |
| Java | Nothing for void methods | Compiler rejects a missing return in non-void methods |
Forms of return values
Return values come in several forms that differ in what they communicate to the caller. Choosing the right form is part of designing a clear function interface.
| Form | Example | Typical use |
|---|---|---|
| Single value | An integer, string or object | Ordinary computation results |
| Multiple values | A tuple, as in Python or Go | Result plus extra information |
| Optional value | Option or Optional type | Result that may legitimately be absent |
| Result or error type | Result in Rust, value and error pair in Go | Operations that can fail |
| Status code | 0 for success, nonzero for failure | C libraries and process exit codes |
| Future or promise | A value delivered later | Asynchronous operations |
In statically typed languages, the return type is part of the function signature and is checked by the compiler. In dynamically typed languages, type hints or annotations document the intended return type, and static analysis tools can verify them before the program runs.
Return values compared with other ways to deliver results
A return value is only one of several channels a function can use to produce an effect. Functions can also modify arguments, change shared state or raise an exception, and each channel has different consequences for readability and testing.
| Mechanism | How it works | Main drawback |
|---|---|---|
| Return value | Result handed back explicitly | Caller may ignore it |
| Output parameter | Function writes into a caller’s variable or reference | Less obvious data flow |
| Global or shared state | Function changes data visible elsewhere | Hidden coupling, hard to test |
| Exception | Function aborts and signals an error | Control flow jumps out of sight |
| Callback | Function passes the result to another function | Nesting and harder tracing |
Functions that communicate only through parameters and return values, with no side effects, are called pure functions. They always return the same value for the same inputs, which makes them easy to test, cache and run in parallel.
Designing good return values
A good return value is predictable, consistently typed and impossible to misunderstand. A function that sometimes returns a list and sometimes returns a number forces every caller to check the type before using it.
- Return one consistent type, using an optional or result type to express absence or failure.
- Prefer an empty collection to a null value when there are simply no results, so callers can loop without extra checks.
- Return named structures instead of long tuples when more than two or three values are involved.
- Avoid returning internal mutable state directly, because callers could modify the object behind the function’s back.
- Mark important results as must-use where the language allows it, for example with the nodiscard attribute in C++ or must_use in Rust.
Common mistakes with return values
Most defects related to return values come from results that are ignored, missing or ambiguous. The table lists frequent symptoms and their causes.
| Symptom | Cause | Fix |
|---|---|---|
| Unexpected None or undefined | A code path has no return statement | Return explicitly on every path |
| Silent failure | Error code returned but never checked | Use result types or must-use warnings |
| Crash on dangling pointer | Reference to a local variable returned | Return by value or allocate safely |
| Wrong sentinel interpreted as data | Value such as -1 is also a valid result | Use an optional type instead |
| Original list unchanged | In-place method returns None, as with sort in Python | Use the returning variant, such as sorted |
The short version
A return value is the answer a function gives back. You hand a function some information, it does its job, and it gives you a result.
It works like ordering at a counter. You give the order, the kitchen prepares it, and the meal handed back to you is the return value. You can eat it, share it or use it as part of something bigger. If the kitchen hands back nothing, you have nothing to work with.
Where it makes a difference
A developer writes a function that looks up a customer’s discount in a database. The first version returns -1 when the customer is not found and the discount percentage otherwise. Months later, another part of the system applies a returned value of -1 as a negative discount and overcharges several orders.
The team redesigns the function to return an optional discount: either a percentage or an explicit absence. The type checker now forces every caller to handle the missing case, and a test confirms that unknown customers receive no discount.
The outcome is that the same mistake can no longer compile or pass static checks. A single change in the return value design removed a whole category of silent pricing errors.