Default and Named Arguments
Master Scala's default and named arguments for cleaner function calls, leveraging your Python knowledge. This lesson covers syntax, benefits, and practical examples, plus troubleshooting tips.
Focus: use default and named arguments in functions
You've been happily sprinkling default arguments through your Python functions for years—def fetch(url, timeout=5)—and now you sit down to write Scala. You type def fetch(url: String, timeout: Int = 5) = ..., call fetch("https://api.example.com"), and... it works. But then you try to call it with a custom timeout and skip a middle argument, and the compiler stares back at you. In this lesson, you'll learn how to use default and named arguments in functions the Scala way, and how to avoid the traps that trip up Python developers. By the end, you'll be writing flexible, readable Scala functions with confidence.
The problem this lesson solves
In Python, default arguments and keyword arguments are the Swiss Army knife of function design. They let you call connect(host, port=5432, ssl=True) and then override just ssl=False with connect(host, ssl=False). Piece of cake. In Scala, the same concepts exist—default parameter values and named arguments—but the syntax and rules differ just enough to cause friction.
The pain points you'll hit without this knowledge:
- You try to skip a default parameter and pass a later one positionally, and the compiler says "not enough arguments" or "unexpected argument".
- You write a default parameter that depends on an earlier parameter, and you're unsure if Scala allows it.
- You see
nullin your code because you think you need it to mean "use the default," when Scala has cleaner ways. - You mix up expression-based defaults with Python's evaluated-at-definition-time behavior.
Why it matters now: You're at step 28 of the Scala for Python Developers track. You've already learned functions, collections, and pattern matching. Default and named arguments are the glue that makes those functions ergonomic in real code—think configuration objects, API clients, or builder methods. Getting this right now saves you from writing overloaded methods or throwing a Map of options at every call site.
Core concept / mental model
Think of a function signature as a contract that lists parameters in a specific order. In Python, you can pass arguments out of order using keywords, and every parameter can have a default. In Scala, you get the same superpowers, but with a stricter type system and a different philosophy about how parameters are matched.
The mental model:
- Default parameter values are expressions evaluated every time the function is called without that argument. In Python, defaults are evaluated once at definition time (which is why
def f(x=[])is a classic bug). In Scala, defaults are re-evaluated per call, so you can safely use mutable or time-sensitive defaults. - Named arguments let you pass arguments by name, not position:
drawCircle(radius = 5, color = "red"). This works regardless of order, as long as you don't mix named and positional arguments in a way that breaks the sequence. - The rule of thumb: once you start using named arguments, any subsequent positional argument must still follow the original parameter order. In Python, you can do
foo(y=2, 1)? No—Python forbids that too. The rules are similar but the error messages differ.
Try this analogy:
Imagine you're ordering a pizza. The menu lists toppings in order: size, crust, cheese, sauce. Python lets you say "large, extra cheese" (positionally) or "sauce=bbq" (named) to skip ahead. Scala gives you the same menu, but insists that if you say "sauce=bbq" first, you must continue naming the rest—you can't suddenly shout "large" positionally after that. The waiter (compiler) wants clarity.
How it works step by step
Step 1: Define a function with default parameter values.
Write the default value as part of the parameter list, after an equals sign:
def greet(name: String, greeting: String = "Hello"): String = s"$greeting, $name!"
Step 2: Call it with fewer arguments.
greet("Ada") // uses default greeting
Step 3: Override a default using a named argument.
greet("Ada", greeting = "Hi")
Step 4: Skip middle defaults with named arguments.
def connect(host: String, port: Int = 5432, ssl: Boolean = true): String = s"Connecting to $host:$port ssl=$ssl"
connect(host = "db.example.com", ssl = false) // skips port
Step 5: Use defaults that depend on earlier parameters.
def createUser(name: String, admin: Boolean = false, role: String = if (admin) "admin" else "user"): String = s"$name is $role"
Step 6: Keep the call order in mind.
Once you use a named argument, all following arguments must also be named (or at least in the correct order). For example:
connect("db", ssl = false) // OK: first positional then named
connect(host = "db", 5433) // ERROR: positional after named
cause → effect: In Scala, the compiler processes arguments in the order they appear in the parameter list. A named argument that appears before a positional argument breaks the sequential logic, so it's rejected. This is a design choice to keep method resolves unambiguous.
Hands-on walkthrough
Let's build a small configuration-heavy example. You'll write a Scala script that models a server start-up with several options.
Example 1: Basic defaults and named arguments
// server.scala
def startServer(host: String = "localhost", port: Int = 8080, verbose: Boolean = false): String =
val mode = if (verbose) "verbose" else "quiet"
s"Starting at $host:$port ($mode mode)"
// Using defaults
println(startServer())
// Override second default by named
println(startServer(host = "0.0.0.0", verbose = true))
// Mix positional and named
println(startServer("127.0.0.1", verbose = true))
Expected output:
Starting at localhost:8080 (quiet mode)
Starting at 0.0.0.0:8080 (verbose mode)
Starting at 127.0.0.1:8080 (verbose mode)
Example 2: Defaults that depend on earlier parameters
def configure(timeout: Int, retries: Int = 3, backoff: Int = if (retries > 1) 2 else 0): String =
s"timeout=$timeout retries=$retries backoff=$backoff"
println(configure(1000))
println(configure(1000, retries = 5))
println(configure(1000, retries = 1))
Expected output:
timeout=1000 retries=3 backoff=2
timeout=1000 retries=5 backoff=2
timeout=1000 retries=1 backoff=0
Example 3: Named arguments in real-world-style API
case class HttpClient(baseUrl: String)
def fetch(client: HttpClient, endpoint: String, params: Map[String, String] = Map.empty, headers: Map[String, String] = Map("Accept" -> "application/json")): String =
s"GET ${client.baseUrl}/$endpoint?${params.mkString("&")} headers=${headers.mkString(",")}"
val client = HttpClient("https://api.example.com")
println(fetch(client, "users"))
println(fetch(client, "users", headers = Map("Authorization" -> "Bearer token")))
Expected output:
GET https://api.example.com/users? headers=Accept -> application/json
GET https://api.example.com/users? headers=Authorization -> Bearer token
Pro tip: If you're used to Python's
**kwargs, Scala doesn't have an exact equivalent. But you can emulate it with aMap[String, Any]parameter or by using default parameter values in a builder pattern. For most cases, named arguments are cleaner and type-safe.
Compare options / when to choose what
When you need flexible function calls, you have a few tools in Scala:
| Approach | Use case | Example | Trade-offs |
|---|---|---|---|
| Default parameter values | Most common: optional settings | def fetch(url: String, timeout: Int = 5) |
Simple, but can lead to many overloads if you have many optionals |
| Named arguments | When you have many optional parameters and want readability | fetch("url", timeout = 10) |
Skips middle args, but you must follow order after a positional; verbose if too many |
| Overloaded methods | When defaults aren't enough (e.g., different types) | def send(msg: String); def send(msg: String, retries: Int) |
Boilerplate, but gives you distinct signatures |
| Builder pattern (with case class copy) | Complex configuration | Config().withTimeout(10).withSSL(true) |
More code but highly readable and extensible |
| Option parameters | When "no value" is meaningful | def f(x: Option[Int] = None) |
Forces caller to think about absence |
Variations to consider:
- Parameter groups (currying): You can define multiple parameter lists:
def foo(a: Int)(b: Int = 2). This lets you partially apply the function and can sometimes make defaults more natural. - Using
Optionwith defaults: If you need to distinguish "not provided" fromnull, useOption[T] = None. This is more idiomatic than acceptingnull. - Type ascription: When passing a default value that could be ambiguous, you can use
default: String = "x": String(though rarely needed).
When to choose what:
- Start with default parameters for 2-3 optional arguments.
- If you need to skip middle args or improve readability, use named arguments at the call site.
- If the parameter set grows beyond 4-5 optionals, consider a case class with
copyor a builder to avoid unwieldy signatures. - If you need multiple ways to construct the same function (e.g., different types like
IntvsString), overloading might be clearer.
Troubleshooting & edge cases
"Positional after named" compile error
Symptom:
def f(a: Int, b: Int = 2, c: Int = 3) = a + b + c
f(1, c = 3, 4) // error
Cause: After a named argument, you cannot pass a positional argument because it would break the order.
Fix:
f(1, 2, c = 3) // or f(a = 1, b = 2, c = 4)
"Repeated default parameter" error
Symptom: You define def f(x: Int = 1, y: Int = x + 1) and then call f(2, 3) — that's fine. But if you call f(x = 2), it works. The error comes when you try to reference a default that isn't yet defined in the scope of the default expression.
Example of error:
def f(a: Int = b, b: Int = 2) = a + b // error: b is not defined
Fix: Order parameters so that dependencies come earlier: def f(b: Int = 2, a: Int = b + 1).
Default expression evaluated each call
Symptom: You expect a mutable default to be shared across calls (like Python's def f(x=[])), but in Scala each call gets a fresh Nil or List.empty.
Cause: Scala evaluates default expressions per invocation.
Fix: Embrace it! This is safer than Python's behavior. If you need shared state, use a var or a global, but that's usually a design smell.
Using null as a default
Symptom: You write def f(x: String = null) to indicate "no value".
Risk: null can cause NPEs later.
Fix: Use Option[String] = None instead.
Named arguments with overloaded methods
Symptom: You have two overloads with the same parameter name but different types, and the compiler complains about ambiguity.
Fix: Keep parameter names distinct or avoid overloading when you can use defaults.
Recorded vs live defaults
Symptom: With implicit or varargs, defaults sometimes behave unexpectedly. For varargs, you cannot have a default for the vararg itself; you need to pass an empty sequence.
Fix: For def f(xs: String* = List.empty), this won't compile. Instead, define def f(xs: Seq[String] = Seq.empty) if you need a default collection.
What you learned & what's next
You've now mastered using default and named arguments in functions in Scala. Specifically, you learned to:
- Explain the core idea: default parameter values give optional arguments, and named arguments let you pass them by name, skipping any in between.
- Apply this in a hands-on exercise: you built functions with defaults, used named arguments to override specific ones, and handled order rules.
- Connect this to the broader track: you're now ready to apply these skills to more advanced function patterns—like currying, partially applied functions, and higher-order functions—which rely on the same parameter flexibility.
What's next: In the next lesson, you'll likely explore partially applied functions and currying, where you'll see how parameter lists and defaults interact with function values. The skills from this lesson—knowing how to define flexible signatures and call them cleanly—are the foundation for that. Keep practicing with your own examples: define a function with three defaults, call it in every possible valid way, and watch the compiler guide you.
Pro tip: In Scala, prefer named arguments in public APIs for readability. Your future self (and your teammates) will thank you at the call site.
Practice recap
Write a Scala function connect(host: String, port: Int = 5432, ssl: Boolean = true) and call it in at least three ways: using all defaults, overriding ssl with a named argument, and overriding both port and ssl with named arguments. Then, add a dependent default parameter (e.g., timeout: Int = if (ssl) 10 else 30) and verify it works. This exercise will cement the ordering and evaluation rules you just learned.
Common mistakes
- Passing positional arguments after a named argument causes a compile error; keep all subsequent arguments named or reorder.
- Using
nullas a default whenOption[T] = Noneis more idiomatic and safer. - Expecting Python-like shared mutable defaults; Scala re-evaluates defaults per call, so use a mutable variable if you truly need shared state.
- Trying to use a default for a varargs parameter; instead, use
Seq[T] = Seq.empty.
Variations
- Parameter groups (currying):
def foo(a: Int)(b: Int = 2)enables partial application and distinct default scopes. - Using
Option[T] = Noneto represent absent values instead ofnullor sentinel values. - Builder pattern with case class
copyfor complex configurations with many optional fields.
Real-world use cases
- API client methods where endpoint and headers are optional but you need to skip the middle parameter:
fetch(client, "users", headers = authHeaders). - Configuration factory functions with defaults for port, timeout, and logging, called with named arguments in tests to override specific settings.
- UI widget constructors that accept many options (size, color, label) with defaults, allowing callers to specify only what they need via named arguments.
Key takeaways
- Default parameter values in Scala are expressions evaluated at each call, unlike Python's definition-time defaults.
- Named arguments let you skip default parameters and improve call-site readability.
- Once you use a named argument, all following arguments must be named or in the correct positional order.
- Use
Option[T] = Noneinstead ofnullfor optional values. - Defaults can depend on earlier parameters, but keep dependencies ordered.
- For many optional parameters, consider a builder pattern or case class over a long parameter list.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.