When I write programs, one thing matters more than most: clarity. That’s where subroutines come into play. They allow me to structure code into reusable blocks. But using them correctly means knowing how to pass data in—and how to get results out. This is where many beginners get stuck. So in this article, I’ll walk you through how subroutines are done in several real-world programming languages.
Why Subroutines Need Inputs and Outputs
Let’s start with the basics. A subroutine—also called a function or method—isn’t useful unless I can feed it data. I also expect a result in return. That’s why every subroutine has a way to receive parameters and, optionally, return a value.
There are two main techniques:
- Register-based – used in low-level systems for speed.
- Stack-based – used in higher-level programming for flexibility.
Both approaches serve the same purpose: they let the subroutine perform its job using the right information and return the correct result.
Subroutines across popoular languages
C and C++: Power with Precision
In C and C++, call subroutines functions. They are defined with a return type, a name, and a list of parameters.
int add(int a, int b) {
return a + b;
}
By default, parameters are passed by value. If I want to pass something by reference—so the function can modify the original value—I use pointers in C or references in C++.
C and C++ use the stack to store return addresses and function frames. Libraries like <math.h>
or <iostream>
are full of subroutines I can call without writing them myself.
Python: Flexibility Without the Fuss
In Python, I use the def
keyword to define a subroutine. I can pass arguments in many ways: positionally, by name, with default values, or even arbitrarily using *args
and **kwargs
.
def greet(name="world"):
return f"Hello, {name}"
The return statement sends the result back. Python handles parameter passing under the hood using references. Its standard library includes countless built-in subroutines like len()
, open()
, and sum()
.
Java: Object-Oriented and Strict
Java enforces structure. All subroutines must exist inside a class. These methods can accept parameters and return values.
public int multiply(int x, int y) {
return x * y;
}
Java always passes by value—but for objects, that means the reference is copied, not the object itself. This can cause confusion, so I stay alert.
Java libraries like java.util
or java.lang
provide extensive sets of ready-to-use subroutines.
Assembly: Manual but Powerful
In assembly, everything is manual. I pass data using CPU registers or the stack.
A typical subroutine call involves:
- Saving the return address using
CALL
. - Passing parameters through registers or pushing them to the stack.
- Returning using
RET
.
This method gives me full control, but I have to manage the memory and flow myself. Efficient and precise—but unforgiving if I make a mistake.
JavaScript: Dynamic and First-Class
JavaScript treats functions as first-class citizens. I can store them in variables, pass them around, and return them from other functions.
function square(n) {
return n * n;
}
JavaScript lets me pass any type of data—numbers, strings, arrays, even other functions. Libraries like Lodash and jQuery are packed with reusable subroutines that simplify frontend work.
Rust: Safety Meets Performance
In Rust, functions work just like in C or Python but with extra rules to ensure memory safety.
fn subtract(a: i32, b: i32) -> i32 {
a - b
}
Parameters are passed by value by default, but I can use references (&
) for borrowing and mut
for mutable references. This makes Rust both safe and fast.
The std
library offers efficient subroutines for common tasks like string manipulation, file I/O, and data structures.
Go (Golang): Simple and Efficient
Go emphasizes clarity. I define subroutines with the func
keyword. Parameters are always passed by value, but I can use pointers to simulate pass-by-reference.
func add(a int, b int) int {
return a + b
}
Go’s libraries—like fmt
, math
, and net/http
—include many prebuilt subroutines that make web servers, formatting, and data handling a breeze.
C# and .NET: Structured and Powerful
In C#, methods live inside classes or structs. Parameters can be passed by value, by reference (ref
), or as output-only (out
). I can also use default and named parameters.
public int Divide(int x, int y) {
return x / y;
}
C# methods return values using return
, and I can define them to return anything from a simple integer to complex objects. .NET libraries like System.IO
or System.Text
give me robust subroutines for everything from file handling to text encoding.

Other Languages Worth Mentioning
Let me not forget a few more:
- MATLAB / Octave: Subroutines (called functions) return one or more results using
[out1, out2] = func(inputs)
. - R: Everything is a function; arguments can be named, and return values are automatic.
- Fortran: Supports
FUNCTION
andSUBROUTINE
constructs with explicit parameter passing. - Pascal: Uses
procedure
andfunction
for subroutines, with parameters passed by value or by reference. - Haskell: In a functional style, all operations are expressions (subroutines). Input and output are passed through function chaining.
Are Subroutines and Functions the Same?
This is a question I get all the time: Are subroutines and functions the same thing? The short answer is yes, but also no. Let me explain. In many contexts, the terms subroutine and function are used interchangeably. Both refer to a named block of code that can be called from another part of a program to perform a task.
However, there are subtle differences—mainly in terminology and usage across languages:
- Subroutines usually emphasize the action being taken. They may or may not return a value. For example, in Fortran and Pascal, subroutines are defined to perform actions without necessarily returning results.
- Functions, on the other hand, always return a value. That’s why languages like Python, C, or JavaScript only use the term “function”—because a return value is expected.
Final Thoughts
Subroutines are the heart of clean, modular code. But understanding how to pass values in and get results out is where their true power lies. Whether I’m coding in C, Python, Java, or Go, I always rely on parameter handling and return logic to make my subroutines do the right thing at the right time.
Every language offers a slightly different take. Some use strict typing. Others allow more flexibility. But they all follow the same principle: input goes in, work happens, and output comes out.
Now that you’ve seen the big picture, try writing subroutines in a few of these languages. You’ll not only get better at programming—you’ll also gain a deeper understanding of how your code really works.
Credits: Photo by Christina Morillo from Pexels