CTCaliberTek
Course: Scala 3 Developer Path: From First Program to a Tested Project · Lesson 3 of 8Log in to track your progress and earn a certificate when you finish the course.
Programming

Scala Functions, Lambdas, and Higher-Order Functions

Published November 9, 2026·Updated November 9, 2026·10 min read

Edited by , Senior Data Analyst. How we make our content

Like SaveLog in to like, save, and track progress.

Introduction

Functions are central to Scala. You can define them with names, write them inline without names, store them in values, pass them to other functions, and return them from functions. Mastering this gives you the vocabulary for the collection operations in the next lesson, where you will transform data with map, filter, and fold instead of writing loops.

Prerequisites

  • Values, types, and control flow from the previous lesson

Step-by-Step Guide

Step 1: Define a method with def

A method is defined with def, a name, typed parameters in parentheses, a return type after a colon, and a body after an equals sign. The value of the last expression is the result, so there is no return keyword in normal Scala code. Parameters must always have types, but the return type can often be inferred. Writing it anyway on public methods is good practice, because it documents the contract and keeps the compiler's inference from changing the type when you edit the body.

def add(a: Int, b: Int): Int = a + b

def greet(name: String): String =
  val greeting = s"Hello, $name"
  greeting.toUpperCase

println(add(2, 3))        // 5
println(greet("Asha"))    // HELLO, ASHA

Step 2: Use default and named arguments

Parameters can have default values, so callers can leave them out. Callers can also name the arguments, in any order, which makes calls with several parameters easier to read and prevents mixing up two values of the same type. Together they replace the many overloaded versions and builder classes that other languages need. When a function has a boolean flag, naming it at the call site makes the meaning obvious.

def connect(host: String, port: Int = 5432, secure: Boolean = true): String =
  val scheme = if secure then "https" else "http"
  s"$scheme://$host:$port"

connect("db.example.com")                          // https://db.example.com:5432
connect("db.example.com", secure = false)          // http://db.example.com:5432
connect(port = 8080, host = "localhost")

Step 3: Write anonymous functions

A function literal, also called a lambda, is written with parameters, an arrow, and a body. It has a function type such as Int => Int, which you can store in a val and call like a method. When the compiler already knows the expected parameter type, you can leave the type out. The underscore shorthand makes short lambdas even terser, where each underscore stands for the next parameter, but it is best kept for very simple expressions because heavy use hurts readability.

val double = (x: Int) => x * 2
val isEven: Int => Boolean = n => n % 2 == 0
println(double(21))        // 42
println(isEven(4))         // true

List(1, 2, 3).map(_ * 10)  // List(10, 20, 30)

Step 4: Pass functions to other functions

A higher-order function takes a function as a parameter or returns one. This lets you separate what to do from how to repeat it. In the example, applyTwice does not know whether it is doubling or adding, and the caller decides. A method can also be used wherever a function is expected, because Scala converts it automatically. This pattern underlies the collection methods you use daily, such as map and filter, which accept a function that is applied to each element.

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

println(applyTwice(_ + 3, 10))     // 16
println(applyTwice(double, 5))     // 20

def isPositive(n: Int): Boolean = n > 0
List(-2, 5, 0, 8).filter(isPositive)   // List(5, 8)

Step 5: Return functions and capture variables

A function can build and return another function. The returned function remembers the values it used from the scope where it was created, which is called a closure. In the example, makeMultiplier produces a function that keeps its own factor. This is a clean way to create configured behavior without a class. Because the captured value is a val, the closure stays predictable. Capturing a var is allowed, but then the function's behavior can change under you, which is a strong reason to prefer vals.

def makeMultiplier(factor: Int): Int => Int =
  x => x * factor

val triple = makeMultiplier(3)
println(triple(7))     // 21

Step 6: Use recursion with @tailrec

Functional code often uses recursion instead of loops. A plain recursive call uses stack space for each level, so a very deep recursion can throw a stack overflow. If the recursive call is the very last thing the function does, which is called tail position, the compiler can turn it into a loop. Adding the tailrec annotation makes the compiler check this for you, and fail to compile if the function is not actually tail recursive. An inner helper with an accumulator parameter is the standard way to get there.

import scala.annotation.tailrec

def factorial(n: Int): BigInt =
  @tailrec
  def loop(i: Int, acc: BigInt): BigInt =
    if i <= 1 then acc
    else loop(i - 1, acc * i)
  loop(n, BigInt(1))

println(factorial(20))   // 2432902008176640000

FAQs

What is the difference between a method and a function in Scala?
A method is defined with def and belongs to a class, object, or scope. A function is a value of a function type like Int => Int that you can store and pass around. Scala converts a method into a function automatically when a function is expected, so the difference rarely matters in practice.
Is the underscore shorthand good style?
It is fine for very short lambdas like _ * 2 or _.name. If you use the same underscore twice, or the expression is longer, name the parameter instead. Readability counts more than saving a few characters.
Why does Scala not have a return keyword in normal code?
Because the last expression is the result. Using return is allowed but discouraged, since in lambdas it can behave surprisingly. Structuring a function as a single expression is clearer and avoids the problem.

Summary

You can define methods with default and named parameters, write lambdas, pass and return functions, understand closures, and write safe tail recursion. These ideas are the foundation for working with collections, where a small function describes the work and a collection method applies it to every element. That is the topic of the next lesson.

Related tutorials

Comments

Log in or create a free account to join the discussion.

  • No comments yet. Be the first to comment.