Scala Default Parameters

Default parameters let you define a function where some parameters have pre-set values. When you call the function and omit those parameters, Scala automatically uses the defaults. This reduces the number of overloaded functions you need to write and makes function calls more concise.

Basic Default Parameter

def greet(name: String, greeting: String = "Hello"): String =
  s"$greeting, $name!"

println(greet("Alice"))             // Hello, Alice!
println(greet("Bob", "Good morning"))  // Good morning, Bob!
println(greet("Carol", "Hi"))       // Hi, Carol!

When you call greet("Alice"), Scala fills in greeting = "Hello" automatically. When you provide a second argument, it overrides the default.

Multiple Default Parameters

def createUser(
  name: String,
  role: String = "viewer",
  active: Boolean = true,
  maxSessions: Int = 1
): String =
  s"User: $name | Role: $role | Active: $active | Sessions: $maxSessions"

println(createUser("Priya"))
// User: Priya | Role: viewer | Active: true | Sessions: 1

println(createUser("Ravi", "admin"))
// User: Ravi | Role: admin | Active: true | Sessions: 1

println(createUser("Deepa", "editor", false))
// User: Deepa | Role: editor | Active: false | Sessions: 1

println(createUser("Arun", "admin", true, 5))
// User: Arun | Role: admin | Active: true | Sessions: 5

Default Parameter Rules


Rule 1: Defaults go AFTER required parameters
─────────────────────────────────────────────
def f(required: Int, optional: String = "default")  ✓ Correct
def f(optional: String = "default", required: Int)  ✗ Confusing


Rule 2: You can skip middle defaults using named arguments
──────────────────────────────────────────────────────────
createUser("Sam", maxSessions = 3)   // skips role and active defaults

Default Parameters vs Overloading

// Without default params — need many overloads
def sendMessage(to: String): Unit = sendMessage(to, "No subject", "")
def sendMessage(to: String, subject: String): Unit = sendMessage(to, subject, "")
def sendMessage(to: String, subject: String, body: String): Unit =
  println(s"To: $to | Subject: $subject | Body: $body")

// With default params — one clean definition
def sendMessage(
  to: String,
  subject: String = "No subject",
  body: String = ""
): Unit =
  println(s"To: $to | Subject: $subject | Body: $body")

sendMessage("alice@example.com")
// To: alice@example.com | Subject: No subject | Body:

sendMessage("bob@example.com", "Hello")
// To: bob@example.com | Subject: Hello | Body:

Expressions as Defaults

Defaults can be any expression, not just literals. They are evaluated each time the function is called without that argument:

def logEvent(
  message: String,
  timestamp: Long = System.currentTimeMillis(),
  level: String = "INFO"
): Unit =
  println(s"[$level] [$timestamp] $message")

logEvent("Server started")
logEvent("User logged in", level = "DEBUG")
logEvent("Error occurred", level = "ERROR")

Each call to logEvent without a timestamp gets the current time at the moment of that call. The default expression re-evaluates every time.

Default Parameters in Case Classes

case class Config(
  host: String = "localhost",
  port: Int = 8080,
  timeout: Int = 30,
  maxRetries: Int = 3
)

val devConfig  = Config()                    // all defaults
val prodConfig = Config("prod.server.com", 443, 60, 5)
val testConfig = Config(port = 9090)          // only port changed

println(devConfig)
// Config(localhost,8080,30,3)

println(testConfig)
// Config(localhost,9090,30,3)

Default Parameters with Overriding

class EmailService:
  def send(
    to: String,
    subject: String = "(no subject)",
    cc: List[String] = List(),
    bcc: List[String] = List(),
    htmlBody: Boolean = false
  ): Unit =
    println(s"Sending to $to | Subject: $subject | CC: $cc | HTML: $htmlBody")

val service = new EmailService()
service.send("a@b.com")
// Sending to a@b.com | Subject: (no subject) | CC: List() | HTML: false

service.send("a@b.com", "Welcome!", htmlBody = true)
// Sending to a@b.com | Subject: Welcome! | CC: List() | HTML: true

Practical: HTTP Request Builder

def httpRequest(
  url: String,
  method: String = "GET",
  headers: Map[String, String] = Map("Content-Type" -> "application/json"),
  timeout: Int = 5000,
  retries: Int = 1
): String =
  s"$method $url | timeout:${timeout}ms | retries:$retries"

println(httpRequest("https://api.example.com/users"))
// GET https://api.example.com/users | timeout:5000ms | retries:1

println(httpRequest("https://api.example.com/data", "POST", timeout = 10000))
// POST https://api.example.com/data | timeout:10000ms | retries:1

Limitations

// Cannot use a parameter as the default for another in the same list
def f(x: Int, y: Int = x + 1): Int = x + y   // Error in some contexts

// Workaround: use multiple parameter lists or a different approach
def f(x: Int)(y: Int = x + 1): Int = x + y
println(f(5)())    // 11
println(f(5)(10))  // 15

Summary


With defaults                     Without defaults
──────────────────────────────    ────────────────────────────────
One function definition           Multiple overloaded definitions
Callers choose what to specify    Callers must provide all values
Clear parameter names visible     Intent less obvious
Defaults documented in signature  Defaults hidden in overloads

Default parameters make APIs friendlier. Callers can start with simple calls and gradually use more options as needed, without being overwhelmed by required arguments for every use case.

Leave a Comment

Your email address will not be published. Required fields are marked *