Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Scala 3  Module 2 – Basics: values, numbers, text and control flow

Methods, parameters and @main programs

Write Scala 3 methods with default, named, repeated and by-name parameters and several parameter lists, and a command-line program that exits 2 on bad input.

  • Beginner
  • 15 minutes
  • Examples run with scala 3.9.0, Java 25.0.4.1 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 15 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Define methods with default, named and repeated parameters, and splice a sequence with xs*
  • Use a by-name parameter to build a retry helper, and predict how often its argument runs
  • Use several parameter lists, including a type parameter clause between them
  • Build a command-line program that reports bad input with a non-zero exit status

Before you start

What you’ll learn · Before you start

What you will learn

  • Define methods with default, named and repeated parameters, and splice a sequence with xs*
  • Use a by-name parameter to build a retry helper, and predict how often its argument runs
  • Use several parameter lists, including a type parameter clause between them
  • Build a command-line program that reports bad input with a non-zero exit status

Before you start

On this page

retry(5)(flakyFetch()) in this lesson ran flakyFetch() three times, once for each attempt, although the call names it once. That is a by-name parameter at work, one of five ways a Scala method can take its arguments: with defaults, by name in the call, as many as you like, unevaluated, and in several parameter lists.

This lesson writes each of them on small fee and settings methods, then builds a command-line program on top: a tax splitter whose @main method does nothing but hand its arguments to a plain run method and exit with the status it returns, 2 for bad input, which a script or a CI job can test.

Default and named arguments make calls readable

A fee of 2 % with a minimum of 500 paise fits most payments, and a few need other numbers. Defaults keep the common call short, and names keep the rare one clear:

params/Fees.scala Scala · params/Fees.scala
def fee(amount: Long, percent: Int = 2, minimum: Long = 500): Long =
  math.max(amount * percent / 100, minimum)

def total(items: Long*): Long = items.sum

@main def fees(): Unit =
  println(s"fee(100_000)                    = ${fee(100_000)}")
  println(s"fee(10_000)                     = ${fee(10_000)}")
  println(s"fee(100_000, minimum = 5_000)   = ${fee(100_000, minimum = 5_000)}")
  println(s"fee(percent = 3, amount = 100_000) = ${fee(percent = 3, amount = 100_000)}")
  println(s"total()                 = ${total()}")
  println(s"total(250, 400, 350)    = ${total(250, 400, 350)}")
  val basket = List(250L, 400L, 350L)
  println(s"total(basket*)          = ${total(basket*)}")
  println(s"total(basket: _*)       = ${total(basket: _*)}")
Defaults, names and a repeated parameter Python · params_session.py
from session import run

# Default and named arguments, a repeated parameter, and the old and the new way to pass a list to it.
run("""
cd params
scala run Fees.scala --server=false
""")

Output

$ cd params
$ scala run Fees.scala --server=false
-- Warning: Fees.scala:15:54
15 |  println(s"total(basket: _*)       = ${total(basket: _*)}")
   |                                                      ^
   |The syntax `x: _*` is no longer supported for vararg splices; use `x*` instead
   |This construct can be rewritten automatically under -rewrite -source 3.4-migration.
1 warning found
fee(100_000)                    = 2000
fee(10_000)                     = 500
fee(100_000, minimum = 5_000)   = 5000
fee(percent = 3, amount = 100_000) = 3000
total()                 = 0
total(250, 400, 350)    = 1000
total(basket*)          = 1000
total(basket: _*)       = 1000

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 params_session.py

The first four lines are calls of the same method. fee(100_000) used both defaults, 2 % and a minimum of 500, so the fee is 2,000; on 10,000 paise 2 % is only 200, so the minimum, 500, applied. fee(100_000, minimum = 5_000) skipped percent, which kept its default, and named the argument after it. fee(percent = 3, amount = 100_000) gave every argument by name, in a different order from the parameters. The Scala 3 Book points out where names help most: when several parameters have the same type, such as two Longs, a call with names cannot swap them by accident.

Name the arguments a reader cannot guess

fee(100_000, 3, 1_000) compiles, but a reader must look up which number is which. fee(100_000, percent = 3, minimum = 1_000) says it in the call, and it still works when a parameter is added later with a default.

Checkpoint

Check: Default and named arguments

  1. Question 1 of 2 def fee(amount: Long, percent: Int = 2, minimum: Long = 500): Long = math.max(amount * percent / 100, minimum). What is fee(50_000)?

    Type a number.

    A hint

    Work out 2 % of the amount, then compare it with the minimum.

    Show the answer: Question 1 of 2

    Answer: 1000 paise

    Both defaults apply: 2 % of 50,000 is 1,000, which is more than the minimum of 500, so the fee is 1,000.

  2. Question 2 of 2 Which call charges 3 % and keeps the default minimum of 500?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: fee(100_000, percent = 3)

    A named argument can set any parameter, in any order, and every parameter not given takes its default. A parameter without a default, such as amount, must always be given.

A repeated parameter takes any number of arguments

total(items: Long*) has a repeated parameter: The last parameter of a list, declared with * after its type, such as items: Long*. It takes any number of arguments as a Seq, and xs* passes a whole sequence.: it takes zero, one or many Longs, and inside the method items is a Seq[Long]. The last four lines of the same output show it: total() is 0, total(250, 400, 350) is 1,000, and a list that is already built goes in whole with basket*, which the Scala 3 Reference calls a vararg splice.

The older way to write the splice, basket: _*, still compiled and gave the same 1,000, but Scala 3.9 warned: The syntax x: _* is no longer supported for vararg splices; use x* instead. The warning also names the compiler option that rewrites old code for you, -rewrite -source 3.4-migration. A repeated parameter must be the last one of its parameter list, which is why fee keeps its own parameters before any list of items.

Checkpoint

Check: Repeated parameters and splices

  1. Question 1 of 2 val basket = List(250L, 400L, 350L) is already built. How do you pass it to def total(items: Long*) in Scala 3?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: total(basket*)

    A repeated parameter takes separate arguments. To pass a whole sequence instead, splice it with xs*.

  2. Question 2 of 2 Inside def total(items: Long*): Long, what is the type of items?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Seq[Long]

    items: Long* is a Seq[Long] inside the method, empty when the call passes nothing, as total() gave 0.

A by-name parameter runs each time it is used

A parameter declared op: => A is a by-name parameter: A parameter declared with an arrow before its type, such as op: => A. The caller's argument is not evaluated before the call; it runs each time the method uses the parameter.: the caller’s argument is not evaluated before the call; the language specification says it is evaluated at each use within the method. That is what a retry helper needs, because each attempt must run the operation again. Here flakyFetch fails twice and then answers:

byname/Retry.scala Scala · byname/Retry.scala
var attempts = 0
def flakyFetch(): String =
  attempts += 1
  if attempts < 3 then throw RuntimeException(s"timeout $attempts")
  "200 OK"

def retry[A](times: Int)(op: => A): A =
  try op
  catch
    case e: RuntimeException if times > 1 =>
      println(s"failed: ${e.getMessage}, ${times - 1} left")
      retry(times - 1)(op)

def twiceByName(msg: => String): Unit = { println(msg); println(msg) }
def twiceByValue(msg: String): Unit = { println(msg); println(msg) }

@main def retries(): Unit =
  println(retry(5)(flakyFetch()))
  println(s"attempts: $attempts")
  var n = 0
  twiceByName { n += 1; s"by name $n" }
  twiceByValue { n += 1; s"by value $n" }
retry, and a by-name against a by-value parameter Python · byname_session.py
from session import run

# A retry helper whose by-name parameter runs again on every attempt, and a by-name against a by-value parameter.
run("""
cd byname
scala run Retry.scala --server=false
""")

Output

$ cd byname
$ scala run Retry.scala --server=false
failed: timeout 1, 4 left
failed: timeout 2, 3 left
200 OK
attempts: 3
by name 1
by name 2
by value 3
by value 3

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 byname_session.py

retry(5)(flakyFetch()) caught two timeouts, printed how many attempts were left, and returned 200 OK from the third attempt; attempts is 3. Step through the three attempts:

Interactive · message by message How retry runs its by-name argument on every attempt Step through retry(5)(flakyFetch()) and count how often the argument runs.
How retry runs its by-name argument on every attempt, step 7 of 7. Messages so far: the caller to retry: times = 5, op; retry to flakyFetch(): run op; retry to flakyFetch(): run op; retry to flakyFetch(): run op; retry to the caller: "200 OK".the callerretry(5)(flakyFetch())retryop: => AflakyFetch()attempts: 3times = 5, oprun oprun oprun op"200 OK"op: A would run it once, first

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. Nothing runs yet. Because op is declared => A, the caller passes the code flakyFetch() itself, not its result. Nothing has run when retry starts.
  2. Attempt 1. retry uses op inside its try, and only now does flakyFetch() run. It throws timeout 1.
  3. Catch and retry. The catch prints failed, timeout 1, 4 left, and retry calls itself with times = 4 and the same, still unrun, op.
  4. Attempt 2. Using op again runs flakyFetch() again. It throws timeout 2, and retry goes on with 3 left.
  5. Attempt 3. The third run of flakyFetch() returns 200 OK, so this try ends normally.
  6. The result. 200 OK goes back to the caller, and attempts is 3. The argument ran three times, once for each use of op.
  7. With a plain parameter. Had op been declared as op: A, the caller would run flakyFetch() once, before retry starts. Its exception would leave the call before retry could catch it.

The last four lines show the difference in one line each. twiceByName uses its by-name msg twice, so the block { n += 1; s"by name $n" } ran twice and printed by name 1 and by name 2. twiceByValue takes a plain String, so the same kind of block ran once, before the call, and both lines say by value 3.

A by-name argument with a side effect

A by-name argument runs once for every use inside the method, so an argument such as { n += 1; … } or a payment call can run more often than the call suggests. Keep by-name parameters for code that is meant to run again, such as a retry, or not at all, such as a log message that is only built when logging is on.

Checkpoint

Check: By-name parameters

  1. Question 1 of 2 def twiceByName(msg: => String): Unit = { println(msg); println(msg) } is called with { n += 1; s"by name $n" }. How many times does the block run?

    Type a number.

    A hint

    A by-name argument runs at every use of the parameter.

    Show the answer: Question 1 of 2

    Answer: 2 times

    The method uses msg twice, so the block runs twice, n goes up twice, and the lines say by name 1 and by name 2. With msg: String it would run once, before the call.

  2. Question 2 of 2 Why must retry's operation be declared op: => A and not op: A?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: With op: A, the operation runs once, before retry starts, so its exception leaves the call before retry can catch it

    retry needs to run the operation again on every attempt, inside its own try. A by-name parameter is evaluated at each use within the method, which gives exactly that.

Several parameter lists, and type parameters between them

retry(times: Int)(op: => A) has two parameter lists. The second one holds the operation alone, so a caller can pass it as a block in braces, retry(3) { … }, which reads like a built-in loop. A method can also be applied to its first list only, which gives a function that waits for the rest. And Scala 3 allows a type parameter clause between two parameter lists, which the Reference calls interleaving:

lists/Settings.scala Scala · lists/Settings.scala
trait Codec[V]:
  def decode(text: String): V

given Codec[Int] with
  def decode(text: String) = text.toInt
given Codec[Boolean] with
  def decode(text: String) = text.toBoolean

def setting[K](key: K)(settings: Map[K, String])[V](using codec: Codec[V]): V =
  codec.decode(settings(key))

def within(limit: Long)(amount: Long): Boolean = amount <= limit

@main def settings(): Unit =
  val conf = Map("port" -> "8080", "debug" -> "true")
  val port = setting("port")(conf)[Int]
  val debug = setting("debug")(conf)[Boolean]
  println(s"port + 1 = ${port + 1}, debug = $debug")
  val underCap = within(1_000)
  println(List(250L, 1_200L, 1_000L).map(underCap))
A type parameter clause between parameter lists Python · lists_session.py
from session import run

# Several parameter lists: a type parameter clause between them, and a method applied to its first list only.
run("""
cd lists
scala run Settings.scala --server=false
""")

Output

$ cd lists
$ scala run Settings.scala --server=false
port + 1 = 8081, debug = true
List(true, false, true)

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 lists_session.py

setting("port")(conf)[Int] reads the text "8080" and decodes it as an Int, so port + 1 is 8081, and setting("debug")(conf)[Boolean] gives true. The key’s type, K, is inferred from "port", while the result type, V, comes in its own clause after the settings, where the caller names it. The using clause then asks the compiler for a Codec[V], a given instance: A value that the compiler passes by itself to a parameter declared with using, chosen by its type. sorted, for example, needs a given Ordering for the element type. More in “Read Scala compiler errors and warnings” chosen by that type; givens have a module of their own later in this track. Without interleaving, the caller would have to name both types, setting[String, Int], in front. within(1_000) is the first list applied alone: it gave a function from an amount to a Boolean, which map used on three amounts, List(true, false, true).

Checkpoint

Check: Several parameter lists

  1. Question 1 of 2 In setting("port")(conf)[Int], what does the [Int] at the end do?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: It names the type V of the clause between the settings list and the using clause, so the text is decoded as an Int

    With interleaved clauses, each type clause sits where its information is available. K is inferred from the key, and the caller names V, the type the setting should have.

  2. Question 2 of 2 What does this give, with def within(limit: Long)(amount: Long): Boolean = amount <= limit?

    Read the code, then choose one answer.

    val atMost = within(500)
    List(100L, 600L).map(atMost)
    Show the answer: Question 2 of 2

    Answer: List(true, false)

    A method with several parameter lists can be applied to its first list alone; the result is a function of the remaining list, here a Long => Boolean. Settings.scala did the same with within(1_000), and map used it on three amounts.

A command-line program reports bad input with its exit status

The exit status: The number a program hands back to whatever started it when it ends: 0 for success, anything else for a failure. Shells and CI jobs decide by it. is how a program tells a script or a CI job whether it worked: 0 for success, anything else for a failure. An @main method with typed parameters converts its arguments for you, but the first programs of this track showed that on a bad argument it prints Illegal command line and still ends with status 0. This program does the checking itself:

cli/TaxSplit.scala Scala · cli/TaxSplit.scala
import scala.math.BigDecimal.RoundingMode

/** The whole program, without the JVM's exit: prints the split and returns the exit status. */
def run(args: Seq[String]): Int = args match
  case Seq(total, rate) =>
    (total.toLongOption, rate.toIntOption) match
      case (Some(t), Some(r)) if t >= 0 && r >= 0 =>
        val base = (BigDecimal(t) * 100 / (100 + r)).setScale(0, RoundingMode.HALF_UP).toLong
        println(s"base $base, tax ${t - base} (paise)")
        0
      case _ =>
        Console.err.println(s"error: expected whole numbers, got: ${args.mkString(" ")}")
        2
  case _ =>
    Console.err.println("usage: taxsplit <total in paise> <rate in per cent>")
    2

@main def taxsplit(args: String*): Unit = sys.exit(run(args))
cli/Typed.scala Scala · cli/Typed.scala
@main def typed(total: Long, rate: Int): Unit =
  println(s"total $total at $rate %")
Typed @main against taxsplit, with their exit statuses Python · cli_session.py
from session import run

# The typed @main program with a bad argument, then taxsplit with good, bad and missing arguments, and their exit
# statuses.
run("""
cd cli
scala run Typed.scala --server=false -- 11800 eighteen
scala run TaxSplit.scala --server=false -- 11800 18
!scala run TaxSplit.scala --server=false -- 11800 eighteen
!scala run TaxSplit.scala --server=false -- 11800
""")

Output

$ cd cli
$ scala run Typed.scala --server=false -- 11800 eighteen
Illegal command line after first argument: java.lang.NumberFormatException: For input string: "eighteen"
$ scala run TaxSplit.scala --server=false -- 11800 18
base 10000, tax 1800 (paise)
$ scala run TaxSplit.scala --server=false -- 11800 eighteen
error: expected whole numbers, got: 11800 eighteen
[exit status 2]
$ scala run TaxSplit.scala --server=false -- 11800
usage: taxsplit <total in paise> <rate in per cent>
[exit status 2]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 cli_session.py

The typed version printed Illegal command line after first argument: java.lang.NumberFormatException for eighteen, and the session shows no exit status after it: it was 0. taxsplit takes its arguments as String*, so nothing can fail before its own code runs, and it hands them to run, which matches on their number and converts them with toLongOption and toIntOption. Good input printed base 10000, tax 1800 (paise) and ended with status 0; a word and a missing argument each printed a message to the error stream and ended with [exit status 2]. Step through the run with the word:

Interactive · message by message How taxsplit turns a bad argument into exit status 2 Step through the run with the word eighteen in place of a number, from the shell to the status it sees.
How taxsplit turns a bad argument into exit status 2, step 6 of 6. Messages so far: the shell to @main taxsplit: "11800" "eighteen"; @main taxsplit to run(args): run(args); run(args) to @main taxsplit: 2; @main taxsplit to the shell: exit status 2.the shellor a CI job@main taxsplitsys.exit(run(args))run(args)rate: None"11800" "eighteen"run(args)2exit status 2[exit status 2]

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. The arguments. The shell starts the program with the two words after --. A repeated parameter, args: String*, receives them as text, so no conversion can fail here.
  2. Hand over to run. The @main method does nothing but call run with the arguments. All the logic is in run, a plain method that a test can call.
  3. Check the input. "11800".toLongOption is Some(11800), but "eighteen".toIntOption is None, so the case for two whole numbers does not apply.
  4. Report and return 2. run writes the problem to the error stream, error, expected whole numbers, and returns 2 instead of throwing.
  5. sys.exit(2). The main method passes the status to sys.exit, which ends the JVM with status 2.
  6. The caller can tell. The shell, or a CI job, sees status 2 and knows the run failed. The typed @main version printed Illegal command line and ended with status 0.

Keeping the work in run, a method that returns the status instead of calling sys.exit, has a second benefit: a test can call run(Seq("11800", "eighteen")) and check that it returns 2, without starting a JVM for every case. For programs with many options and flags, libraries such as mainargs parse them into typed parameters and print a help text.

Key idea

Put a command-line program’s logic in a plain method that takes the arguments as text and returns an exit status, and let the @main method do nothing but sys.exit(run(args)).

Checkpoint

Check: Exit statuses of a command-line program

  1. Question 1 of 2 The typed program @main def typed(total: Long, rate: Int) is run with 11800 eighteen. What exit status does the shell see?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 0, success, although the program printed Illegal command line

    On Scala 3.9.0 the typed @main method prints Illegal command line and ends with status 0, so a script cannot tell. A program that callers check must decide its own status.

  2. Question 2 of 2 scala run TaxSplit.scala -- 11800 gives taxsplit one argument instead of two. What happens?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: run prints the usage line to the error stream and returns 2, and sys.exit(2) ends the program with status 2

    The session printed usage, taxsplit <total in paise> <rate in per cent>, and [exit status 2]. The status comes from run, so a test can check the same case without starting the JVM.

Practise: a retry helper and a CLI wrapper

The program below checks a retry and a cli method. As given, retry tries its operation only once, and cli returns 0 whatever the arguments are:

practice/Practice.scala Scala · practice/Practice.scala
// Practise: make retry try op up to `times` times, and make cli return 0 for two
// whole numbers and 2 for anything else, as the checks below expect.
def retry[A](times: Int)(op: => A): A = op

def cli(args: Seq[String]): Int = 0

@main def checkPractice(): Unit =
  var calls = 0
  def flaky(failures: Int): String =
    calls += 1
    if calls <= failures then throw RuntimeException(s"failure $calls") else "done"
  def attempt(times: Int, failures: Int): String =
    calls = 0
    val result = try retry(times)(flaky(failures)) catch case e: RuntimeException => e.getMessage
    s"$result after $calls calls"
  val checks = List(
    "retry(3), first call works" -> (attempt(3, 0) == "done after 1 calls"),
    "retry(3), two failures" -> (attempt(3, 2) == "done after 3 calls"),
    "retry(2), always failing" -> (attempt(2, 9) == "failure 2 after 2 calls"),
    "cli(11800, 18)" -> (cli(Seq("11800", "18")) == 0),
    "cli(11800, eighteen)" -> (cli(Seq("11800", "eighteen")) == 2),
    "cli()" -> (cli(Seq()) == 2),
    "cli(11800, 18, 5)" -> (cli(Seq("11800", "18", "5")) == 2),
  )
  for (label, ok) <- checks do println(s"${if ok then "ok  " else "FAIL"} $label")
  println(s"${checks.count(_._2)} of ${checks.size} checks pass")
  if checks.exists(!_._2) then sys.exit(1)
The practice program as given Python · practice_session.py
from session import run

# The practice program as given: retry tries once, and cli always says 0.
run("""
cd practice
!scala run Practice.scala --server=false
""")

Output

$ cd practice
$ scala run Practice.scala --server=false
ok   retry(3), first call works
FAIL retry(3), two failures
FAIL retry(2), always failing
ok   cli(11800, 18)
FAIL cli(11800, eighteen)
FAIL cli()
FAIL cli(11800, 18, 5)
2 of 7 checks pass
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 practice_session.py

Two of the seven checks pass. Fill in both methods in a copy of practice/Practice.scala and run it again; the goal is 7 of 7 checks pass. First hint: op is already by-name, so retry only has to catch the exception and use op again while attempts are left; cli can match on Seq(a, b) and test a.toLongOption and b.toIntOption, as run does.

Last, answer the questions on parameters, parameter lists and exit statuses, which explain each choice as you make it:

Practice that adapts to you

Practise this lesson

10 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 10 With the same fee method, defaults 2 % and a minimum of 500, what is fee(10_000)?

    Type a number.

    Show the answer: Question 1 of 10

    Answer: 500 paise

    2 % of 10,000 is 200, which is below the minimum, so math.max gives 500, as the session printed.

  2. Question 2 of 10 A new parameter cap: Long = Long.MaxValue is added between percent and minimum. What does the old call fee(100_000, 3, 1_000) mean now?

    Choose one answer.

    Show the answer: Question 2 of 10

    Answer: 1,000 is now the cap, so the fee is capped at 1,000 instead of having a minimum of 1,000

    Named arguments, fee(100_000, percent = 3, minimum = 1_000), keep their meaning when parameters are added, which is why naming the arguments a reader cannot guess pays off.

  3. Question 3 of 10 What does Scala 3.9 do with the old splice syntax total(basket: _*)?

    Choose one answer.

    Show the answer: Question 3 of 10

    Answer: It compiles and runs, with a warning to write basket* instead

    The warning also names the rewrite, -rewrite -source 3.4-migration, which turns old splices into the new form across a code base.

  4. Question 4 of 10 Why does def note(items: Long*, label: String) not compile?

    Choose one answer.

    Show the answer: Question 4 of 10

    Answer: A repeated parameter must be the last parameter of its list

    Put the repeated parameter last, def note(label: String, items: Long*), or give it a parameter list of its own, def note(label: String)(items: Long*).

  5. Question 5 of 10 How is a by-name parameter declared?

    Choose one answer.

    Show the answer: Question 5 of 10

    Answer: With an arrow before its type, as in op: => A

    op: => A takes any expression of type A and runs it at each use. op: () => A also delays the work, but the caller must pass a function, such as () => fetch().

  6. Question 6 of 10 def log(enabled: Boolean, msg: => String) = if enabled then println(msg) is called as log(false, buildReport()). How often does buildReport() run?

    Choose one answer.

    Show the answer: Question 6 of 10

    Answer: Never

    This is why log methods take their message by name: a costly message is never built when logging is off.

  7. Question 7 of 10 Why does retry take its operation in a second parameter list, retry(times: Int)(op: => A)?

    Choose one answer.

    Show the answer: Question 7 of 10

    Answer: So that a caller can pass the operation as a block in braces, retry(3) { … }

    Several parameter lists let the last one hold a block. The same style appears in the standard library, for example in boundary and Using.

  8. Question 8 of 10 def setting[K](key: K)(settings: Map[K, String])[V](using codec: Codec[V]): V. In setting("port")(conf)[Int], which type does the compiler infer, and which does the caller give?

    Choose one answer.

    Show the answer: Question 8 of 10

    Answer: K is inferred from "port", and the caller gives V as Int

    Interleaved type clauses let each type parameter sit next to what decides it. Without them, naming V in the call would mean naming K too, setting[String, Int](…).

  9. Question 9 of 10 Why does taxsplit keep its logic in run(args: Seq[String]): Int instead of in the @main method?

    Choose one answer.

    Show the answer: Question 9 of 10

    Answer: A test can call run with any arguments and check the status it returns, without starting a JVM or exiting it

    The practice of this lesson does the same: its checks call cli(Seq(…)) directly and compare the status, 0 or 2.

  10. Question 10 of 10 A CI step runs a Scala tool on every file and must stop on the first file with bad data. What must the tool do?

    Choose one answer.

    Show the answer: Question 10 of 10

    Answer: End with a non-zero exit status when the data is bad, for example through sys.exit(run(args))

    The exit status is the contract between a program and whatever runs it. 0 means success; any other value, such as taxsplit's 2, means the run failed.

Interview questions

Fresher · What is a by-name parameter in Scala, and when would you use one?

A by-name parameter is declared with an arrow before its type, as in op: => A. The argument the caller writes is not evaluated before the call; it is evaluated each time the method uses the parameter, and not at all if the method never uses it. A normal parameter is evaluated exactly once, before the method starts. By-name parameters suit code that must run again or maybe not at all: a retry(times)(op) helper that runs op once per attempt, a timing helper that measures how long a block takes, or a log method that builds its message only when logging is switched on. Two cautions apply: an argument with a side effect runs once per use, and each use costs a call, so use the parameter once and keep its value in a val when you need it twice.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Mid to senior

    A retry helper took its operation as op: A and never retried. After a teammate changed it to op: => A, a payment call that timed out was retried and one customer was charged twice. Explain both bugs, and design a retry helper that a payments team can use safely.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Senior to staff

    A library adds a parameter with a default, minimum: Long = 500, to a public method fee(amount, percent). Every call in the source still compiles, yet a service that was not recompiled now fails with NoSuchMethodError when it calls fee. Explain why, and how library authors add parameters without breaking such users.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • Defaults keep common calls short, named arguments make the rest clear and may skip parameters or change their order, and both keep working when a parameter with a default is added.
  • A repeated parameter, items: Long*, takes any number of arguments as a Seq; pass a whole sequence with xs*, because xs: _* now warns.
  • A by-name parameter, op: => A, runs its argument at every use inside the method, which makes retry and logging helpers possible, and a plain parameter runs it once, before the call.
  • Several parameter lists let a caller pass a block in braces or apply the first list alone, and a type parameter clause between lists lets the caller name only the type that cannot be inferred.
  • A command-line program keeps its logic in a run method that returns an exit status, and its @main method ends with sys.exit(run(args)), so bad input gives a non-zero status that scripts and tests can check.

Terms in this lesson

by-name parameter
A parameter declared with an arrow before its type, such as op: => A. The caller's argument is not evaluated before the call; it runs each time the method uses the parameter.
exit status
The number a program hands back to whatever started it when it ends: 0 for success, anything else for a failure. Shells and CI jobs decide by it.
repeated parameter
The last parameter of a list, declared with * after its type, such as items: Long*. It takes any number of arguments as a Seq, and xs* passes a whole sequence.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.