Technology  /  Scala

๐Ÿ”ด Scala 21 guides ยท updated 2026

The JVM's hybrid OOP/functional language โ€” immutability, pattern matching, and the type system that powers Spark and Akka, from first principles to production.

Functions in Scala: Higher-Order Functions, Currying and Anonymous Functions

Scala treats functions as values you can store in a variable, pass as an argument, and return from another function โ€” the same way youโ€™d treat an Int or a String. This isnโ€™t a minor convenience; itโ€™s the foundation the entire functional-programming half of the language is built on. This guide covers function definitions, anonymous functions, higher-order functions, and currying โ€” the vocabulary for treating behavior itself as data.


Defining Functions With def

def add(a: Int, b: Int): Int = a + b
def greet(name: String): String = {
val greeting = "Hello"
s"$greeting, $name!"
}

def declares a method. For a single-expression body, the = form (def add(a: Int, b: Int): Int = a + b) is idiomatic โ€” no braces needed, since the function body is just one expression. A multi-line body uses a { } block, whose value (per the previous guideโ€™s coverage of block expressions) becomes the methodโ€™s return value automatically, with no return keyword needed.

// Default parameter values
def greet(name: String, greeting: String = "Hello"): String = s"$greeting, $name!"
greet("Alice") // "Hello, Alice!"
greet("Alice", "Hey") // "Hey, Alice!"
greet(name = "Alice", greeting = "Hi") // named arguments โ€” order doesn't matter with names
greet(greeting = "Hi", name = "Alice") // also valid โ€” same result

Named arguments let you call a function specifying which parameter each value belongs to, independent of declaration order โ€” genuinely useful once a function has several parameters of the same type, where positional arguments alone become error-prone (swapping two same-typed arguments silently compiles but does the wrong thing).


Anonymous Functions (Function Literals)

val square = (x: Int) => x * x
println(square(5)) // 25
val add = (a: Int, b: Int) => a + b
println(add(2, 3)) // 5

The (params) => body syntax defines a function value directly, without a name โ€” assignable to a val like any other value, exactly as the earlier guideโ€™s โ€œeverything is an objectโ€ framing implied. square here has type Int => Int (read: โ€œa function from Int to Intโ€), which is a genuine, first-class type you can use anywhere a type is expected โ€” as a parameter type, a return type, or a field type.

// Passing an anonymous function directly as an argument โ€” extremely common
val numbers = List(1, 2, 3, 4, 5)
val doubled = numbers.map(x => x * 2)
val evens = numbers.filter(x => x % 2 == 0)
// Scala's underscore shorthand โ€” a common abbreviation once the intent is clear from context
val doubledShort = numbers.map(_ * 2)
val evensShort = numbers.filter(_ % 2 == 0)

The _ (underscore) shorthand stands in for โ€œthe argument,โ€ used when a function literalโ€™s parameter is referenced exactly once and its type is unambiguous from context โ€” _ * 2 means the same thing as x => x * 2. This is genuinely just abbreviation, not a different mechanism, and reaching for it is a matter of readability judgment: a short, single-use transformation reads cleanly with _, while anything with the parameter used more than once, or requiring a descriptive name for clarity, should use the explicit named form instead.


Higher-Order Functions โ€” Functions That Take or Return Functions

def applyTwice(f: Int => Int, x: Int): Int = f(f(x))
applyTwice(x => x + 3, 10) // 16 โ€” applies (x => x+3) twice: 10 -> 13 -> 16
// Returning a function from a function
def multiplier(factor: Int): Int => Int = {
(x: Int) => x * factor
}
val triple = multiplier(3)
triple(5) // 15

A higher-order function is simply a function that takes another function as a parameter, returns one, or both โ€” applyTwice takes a function; multiplier returns one. This is what makes .map(), .filter(), and every other collection transformation possible: map itself is a higher-order function, defined once on the collection type, parameterized by whatever specific transformation you pass it.

def applyTwice(f: Int => Int, x: Int)

f is a PARAMETER โ€”

any Int => Int function can be passed

applyTwice(x => x+3, 10)

applyTwice(x => x*x, 10)

16

100

Reusing applyTwice with entirely different behavior (x => x + 3 vs x => x * x) without touching applyTwiceโ€™s own definition is the whole point โ€” the what to do (the function passed in) is decoupled from the how to apply it twice (applyTwiceโ€™s own logic), which is the core reusability benefit higher-order functions provide over hardcoding one specific operation.


Currying โ€” Splitting Multiple Parameters Into Sequential Function Calls

// Ordinary multi-parameter function
def add(a: Int, b: Int): Int = a + b
add(2, 3) // 5
// Curried version โ€” parameters split into separate parameter LISTS
def addCurried(a: Int)(b: Int): Int = a + b
addCurried(2)(3) // 5 โ€” looks similar, but is structurally very different

addCurried(2) on its own is a valid, complete expression โ€” it returns a new function of type Int => Int (specifically, โ€œadd 2 to whatever comes nextโ€), which is then applied to 3 in the second set of parentheses. This is fundamentally different from add(2, 3), where both arguments must be supplied together, in one call, or not at all.

// Currying's actual practical value: creating specialized, partially-applied functions
def addCurried(a: Int)(b: Int): Int = a + b
val add5 = addCurried(5) _ // partially applied โ€” add5 is now Int => Int
add5(10) // 15
add5(20) // 25
val increment = addCurried(1) _
increment(41) // 42

This is where currying earns its keep: addCurried(5) _ fixes the first argument and produces a new, reusable, specialized function (add5) โ€” genuinely useful for building families of related functions from one general one, without writing add5 and increment as separate hand-written functions. The trailing _ here explicitly signals โ€œtreat this as a partially-applied function value,โ€ a Scala-specific syntax quirk needed because otherwise the compiler would expect a second argument list to be supplied immediately.

// Currying is exactly why some standard library methods take multiple parameter lists
List(1, 2, 3).foldLeft(0)((acc, x) => acc + x) // 6
// ^ ^
// first list second list โ€” this shape exists specifically to help type inference

foldLeftโ€™s multi-parameter-list shape isnโ€™t arbitrary โ€” splitting the initial value (0) from the combining function (acc, x) => acc + x into separate parameter lists lets Scalaโ€™s type inference use the first listโ€™s type to infer the second listโ€™s function parameter types automatically, without needing explicit type annotations on acc and x. This is a genuinely practical reason currying appears throughout Scalaโ€™s standard library beyond the โ€œpartial applicationโ€ use case shown above.


By-Name Parameters โ€” Deferred Evaluation

def logIfDebug(condition: Boolean, message: => String): Unit = {
if (condition) println(message)
}
logIfDebug(debugEnabled, expensiveMessageComputation())

message: => String (note the => before the type, not after) is a by-name parameter โ€” instead of evaluating expensiveMessageComputation() before calling logIfDebug (as a normal parameter would), the expression is passed unevaluated and only computed the moment message is actually referenced inside the function body. If condition is false, expensiveMessageComputation() never runs at all โ€” genuinely skipped, not just its result discarded.

// Contrast with an ordinary parameter, which is always evaluated eagerly, regardless of use
def logIfDebugEager(condition: Boolean, message: String): Unit = {
if (condition) println(message)
}
logIfDebugEager(false, expensiveMessageComputation())
// expensiveMessageComputation() STILL runs here, even though its result is thrown away โ€” wasted work

This distinction matters for exactly the case shown: expensive computations, logging thatโ€™s usually disabled, or lazy default values โ€” by-name parameters let the caller pass an expression whose cost is only paid if itโ€™s genuinely needed, which is a real, measurable difference in production logging code thatโ€™s disabled most of the time in a hot path.


Multiple Return Values via Tuples

def divideWithRemainder(a: Int, b: Int): (Int, Int) = (a / b, a % b)
val (quotient, remainder) = divideWithRemainder(17, 5)
println(s"$quotient remainder $remainder") // "3 remainder 2"

Since Scala has no dedicated multiple-return-value syntax the way some languages do, returning a tuple (covered in the previous guide) and destructuring it at the call site is the standard idiom โ€” clean for a small, fixed number of related values, though a named case class (covered in its own guide) is the better choice once the returned values need clearer field names than _1/_2 would provide.


Recursion Instead of Loops

def factorial(n: Int): Int =
if (n <= 1) 1 else n * factorial(n - 1)
def sumList(numbers: List[Int]): Int = numbers match {
case Nil => 0
case head :: tail => head + sumList(tail)
}
// Tail recursion โ€” the compiler can optimize this into a loop internally, avoiding stack growth
import scala.annotation.tailrec
@tailrec
def factorialTailRec(n: Int, acc: Int = 1): Int =
if (n <= 1) acc else factorialTailRec(n - 1, n * acc)

Idiomatic Scala reaches for recursion over manual mutable loops more often than Java or Python code typically does, aligning with the immutability-first style covered in depth in a later guide. The @tailrec annotation is worth knowing specifically because itโ€™s a compile-time check, not just documentation: if the annotated function isnโ€™t actually written in a tail-recursive form (the recursive call must be the very last operation, with nothing left to do after it returns), compilation fails with an explicit error โ€” catching a function that would otherwise silently blow the stack on large inputs, before that ever happens in production.


Frequently Asked Questions

When should I use the underscore shorthand (_) instead of a named parameter in a lambda? When the transformation is short and the parameter is used exactly once โ€” _ * 2 reads cleanly. Once a lambdaโ€™s parameter is used more than once, or a descriptive name would clarify what it represents, switch to the explicit x => ... form.

Is currying actually used in real Scala code, or is it mostly a functional-programming curiosity? Itโ€™s genuinely practical โ€” beyond the partial-application pattern shown above, many standard library methods (foldLeft, foldRight) use multiple parameter lists specifically to improve type inference, and itโ€™s common in configuration/builder-style APIs where fixing some parameters upfront and supplying the rest later is a natural fit.

Whatโ€™s the actual performance cost of a by-name parameter? By-name parameters are implemented as a small wrapper function under the hood, evaluated each time theyโ€™re referenced โ€” if referenced multiple times inside the function body, the underlying expression is recomputed each time, unlike a normal (eager) parameter, which is computed once upfront. If you need โ€œcompute once, but only if actually used,โ€ a lazy val inside the function body wrapping the by-name parameterโ€™s single use is the correct combination.

Why does addCurried(5) _ need a trailing underscore? Without it, Scalaโ€™s parser expects the second parameter list to follow immediately โ€” the underscore explicitly tells the compiler โ€œI want the partially-applied function value itself, not an error about a missing second argument list.โ€

Should I prefer recursion or a while loop for iterative logic? For anything reasonably transformable into a @tailrec-checked recursive function, recursion is the more idiomatic Scala style and pairs naturally with immutable data. For genuinely performance-critical hot loops, or logic thatโ€™s naturally imperative (polling, retry loops with side effects), a while loop with var state remains a legitimate, unapologetic choice โ€” Scalaโ€™s hybrid design (from the first guide in this series) means neither is โ€œwrong.โ€


Whatโ€™s Next

Functions as values, higher-order functions, and currying are the functional-programming building blocks this series will keep returning to. Next: Classes and Objects in Scala, where we shift to the object-oriented half of the language โ€” constructors, fields, and methods โ€” and start building toward traits and case classes.