Functions: input, output, a name
A function is like a coffee machine: you put something in (water, beans, the size you want), press the button, and something comes out (a coffee). The machine has a name (“espresso”), it expects certain inputs, and it always gives back the same kind of result.
In code, the inputs are parameters, the output is the return value, and you can use the function as often as you like, from anywhere, without repeating its code.
The differences between the four languages are mostly at the call site: Swift’s argument labels are part of the function’s name; Kotlin and TypeScript (via object parameters) get named and default arguments; Java has neither and relies on overloads, builders or parameter objects. Semantically, all four pass arguments by value, where the value of an object is a reference; Swift adds inout for explicit write-back.
func price(base: Double, quantity: Int = 1, discount: Double = 0) -> Double {
base * Double(quantity) * (1 - discount)
}
let a = price(base: 3.5) // 3.5
let b = price(base: 3.5, quantity: 4, discount: 0.1) // 12.6fun price(base: Double, quantity: Int = 1, discount: Double = 0.0): Double =
base * quantity * (1 - discount)
val a = price(3.5) // 3.5
val b = price(base = 3.5, quantity = 4, discount = 0.1) // 12.6double price(double base, int quantity, double discount) {
return base * quantity * (1 - discount);
}
double price(double base) { // Java has no defaults: overload instead
return price(base, 1, 0);
}
double a = price(3.5); // 3.5
double b = price(3.5, 4, 0.1); // 12.6function price(base: number, quantity = 1, discount = 0): number {
return base * quantity * (1 - discount);
}
const a = price(3.5); // 3.5
const b = price(3.5, 4, 0.1); // 12.6- Parameters are listed with their types. Default values (
quantity = 1) let callers skip them. - The return type comes after
->(Swift),:(Kotlin, TypeScript), or before the name (Java). A function that returns nothing hasVoid/Unit/void. - Named arguments make calls readable:
price(base: 3.5, quantity: 4)says what 4 means. Swift requires the names (argument labels); Kotlin lets you choose; Java and TypeScript only have positions, which is whyprice(3.5, 4, 0.1)is harder to read.
A function gets a copy of the value
Tap the line with the bug.
func applyDiscount(_ price: Double) {
var price = price
price = price * 0.9
}
var total = 100.0
applyDiscount(total)
print(total)fun applyDiscount(price: Double) {
var discounted = price
discounted = discounted * 0.9
}
var total = 100.0
applyDiscount(total)
println(total)void applyDiscount(double price) {
price = price * 0.9;
}
double total = 100.0;
applyDiscount(total);
System.out.println(total);function applyDiscount(price: number): void {
price = price * 0.9;
}
let total = 100.0;
applyDiscount(total);
console.log(total);Hint: It prints 100.0. Which variable does the function change: total, or its own copy?
The function receives a copy of total’s value and changes only that copy, which disappears when the function ends. total is still 100. (Swift and Kotlin don’t even let you change a parameter directly, which is a hint: the code above had to make its own local copy.)
Return the new value, and let the caller decide what to do with it:
func discounted(_ price: Double) -> Double { price * 0.9 }
total = discounted(total)fun discounted(price: Double) = price * 0.9
total = discounted(total)double discounted(double price) { return price * 0.9; }
total = discounted(total);function discounted(price: number): number { return price * 0.9; }
total = discounted(total);A function that takes input and returns output, without changing anything else, is the easiest kind to test and reuse.
Functions that return several things
When a function needs to give back more than one value, return a small type with named fields instead of an array or a list:
func minMax(_ numbers: [Int]) -> (min: Int, max: Int)? {
guard let first = numbers.first else { return nil }
return numbers.reduce((min: first, max: first)) { (min($0.min, $1), max($0.max, $1)) }
}
if let range = minMax([4, 9, 1]) { print(range.min, range.max) } // 1 9data class Range(val min: Int, val max: Int)
fun minMax(numbers: List<Int>): Range? =
if (numbers.isEmpty()) null else Range(numbers.min(), numbers.max())
minMax(listOf(4, 9, 1))?.let { (min, max) -> println("$min $max") } // 1 9record Range(int min, int max) {}
Optional<Range> minMax(List<Integer> numbers) {
if (numbers.isEmpty()) return Optional.empty();
return Optional.of(new Range(Collections.min(numbers), Collections.max(numbers)));
}function minMax(numbers: number[]): { min: number; max: number } | undefined {
if (numbers.length === 0) return undefined;
return { min: Math.min(...numbers), max: Math.max(...numbers) };
}
const range = minMax([4, 9, 1]);
if (range) console.log(range.min, range.max); // 1 9An empty list has no minimum, so the function says so with an optional result instead of inventing a number. See Null safety.
Designing a function’s signature
- Long parameter lists of the same type (
createUser(String, String, String, boolean)) invite swapped arguments. Swift labels and Kotlin named arguments help at the call site; in Java and TypeScript, introduce a parameter object (a record, an options object) or a builder. - Boolean flags usually mean two functions:
send(message, true)issendUrgent(message). - Defaults across languages: Kotlin defaults aren’t visible to Java callers without
@JvmOverloads; Swift defaults are evaluated in the callee’s context on each call; TypeScript defaults apply toundefined, not tonull. inoutin Swift is copy-in copy-out, not a reference: the caller’s variable is written back when the function returns, and overlapping access (passing the same variable twice) is a compile-time or runtime exclusivity error.
Deep diveWhy Swift's argument labels are part of the nameInterview favorite
In Swift, move(from:to:) and move(to:) are different functions, and the labels are written at every call. That comes from Objective-C, where moveFrom:to: was literally the selector. The API design guidelines build on it: labels should make a call read like an English phrase (insert(x, at: 0), remove(at: 3)), and the first label is omitted when the base name already says what the argument is (append(x)). It’s why Swift APIs rarely need named-argument conventions or parameter objects that other languages use for readability.
- All four pass by value; for objects the value is a reference. Swift
inoutis copy-in copy-out with exclusivity checks. - Readability at the call site: Swift labels (part of the name), Kotlin named/default args (
@JvmOverloadsfor Java callers), TypeScript options objects, Java overloads/parameter objects/builders. - Pure functions (input → output, no side effects) are the easiest to test; return new values instead of mutating parameters.
func send(_ message: String, to user: User, urgent: Bool = false) { /* … */ }
send("Hi", to: ana, urgent: true)fun send(message: String, to: User, urgent: Boolean = false) { /* … */ }
send("Hi", to = ana, urgent = true)record SendOptions(User to, boolean urgent) {}
void send(String message, SendOptions options) { /* … */ }
send("Hi", new SendOptions(ana, true));function send(message: string, { to, urgent = false }: { to: User; urgent?: boolean }) { /* … */ }
send("Hi", { to: ana, urgent: true });Check this part
A function changes its number parameter. What happens to the caller's variable?
What does a default parameter value do?
How does Swift's
inoutwork?Can Java callers use a Kotlin function's default arguments?Java has no named or default arguments. What keeps long signatures safe?When does a TypeScript default parameter apply?