Unit 2: Introduction to Programming in Scala - Subjective Questions
INT315 — Cluster Computing • Practice Questions with Detailed Answers
20 questions
Explain the major features of Scala and discuss how Scala combines object-oriented and functional programming paradigms.
Scala is a modern, statically typed programming language that runs on the Java Virtual Machine (JVM). Its major features include:
- Object-oriented programming: Every value in Scala is an object, and concepts such as classes, objects, inheritance, and traits are supported.
- Functional programming: Scala supports higher-order functions, immutability, pattern matching, closures, and expressions that return values.
- Static typing with type inference: The compiler determines many types automatically while still checking type correctness at compile time.
- JVM interoperability: Scala can use existing Java libraries and frameworks.
- Concise syntax: Semicolons are usually optional, and many language constructs require less code than equivalent Java constructs.
- Concurrency support: Immutable data structures, futures, actors, and other abstractions help developers write concurrent programs.
Scala combines both paradigms by allowing developers to define classes and objects while also treating functions as first-class values. For example, a class can contain immutable fields and methods that accept functions as arguments. This combination is useful for building scalable and maintainable software.
Describe the basic data types and literals used in Scala with suitable examples.
Scala provides several basic data types. Since Scala is object-oriented, these types are represented as objects even though some may be optimized internally.
- Integer types:
Byte,Short,Int, andLongstore whole numbers. Example:val count: Int = 25. - Floating-point types:
FloatandDoublestore decimal values. Example:val price: Double = 99.50. - Character type:
Charstores a single character, such asval grade: Char = 'A'. - Boolean type:
Booleanstorestrueorfalse. - String type:
Stringstores a sequence of characters, such asval name: String = "Scala". - Unit type:
Unitrepresents the absence of a meaningful return value and is similar tovoidin Java. - Any and AnyVal:
Anyis the root type of all values, whileAnyValis the root type of value types. - Null and Nothing:
Nullis the type ofnullreferences, andNothingrepresents a value that never successfully returns.
Common literals include integer literals such as 10, floating-point literals such as 3.14, character literals such as 'x', string literals such as "hello", Boolean literals such as true, and the unit literal ().
Explain operators in Scala and describe how operators are implemented as methods. Illustrate your answer with examples.
In Scala, operators are not special language symbols in the same way they are in some other languages. Most operators are method calls with a convenient infix notation.
For example, the expression a + b is interpreted approximately as a.+(b). Similarly, x == y invokes an equality method.
Operators can be classified as follows:
- Arithmetic operators:
+,-,*,/, and%. - Relational operators:
<,>,<=,>=,==, and!=. - Logical operators:
&&,||, and!. - Bitwise operators:
&,|,^,<<, and>>. - Assignment operators:
=,+=,-=, and similar operators.
A programmer can define an operator-like method because method names may contain symbols. For example:
class NumberBox(val value: Int) {
def +(other: NumberBox): NumberBox =
new NumberBox(value + other.value)
}
val result = new NumberBox(4) + new NumberBox(6)Here, + is a method defined by NumberBox. Scala also supports prefix and postfix method notation in appropriate cases, which provides concise and expressive code.
What is type inference in Scala? Explain its advantages and limitations with examples.
Type inference is the ability of the Scala compiler to determine the type of an expression without requiring the programmer to write the type explicitly.
For example:
val age = 20
val message = "Hello"
val numbers = List(1, 2, 3)The compiler infers the types as Int, String, and List[Int], respectively.
Advantages:
- Reduces repetitive type declarations.
- Makes programs shorter and easier to read.
- Preserves static type checking and compile-time safety.
- Works effectively with generic collections and functions.
Limitations:
- The inferred type may be more general than expected.
- Public methods should often specify return types for clarity and stable APIs.
- Recursive methods generally require an explicit result type.
- Type inference does not eliminate the need to understand type compatibility.
For example, the following method explicitly declares its return type:
def square(x: Int): Int = x * xAlthough Scala may infer the return type in many local definitions, explicit annotations improve documentation and prevent accidental changes in public interfaces.
Distinguish between mutable and immutable collections in Scala. Discuss the benefits and drawbacks of each approach.
The main difference is whether a collection can be changed after it has been created.
Immutable collections:
- Cannot be modified in place.
- Operations such as
:+,::, orupdatedreturn a new collection. - The original collection remains unchanged.
- They are easier to share between threads and reduce accidental side effects.
Example:
val values = List(1, 2, 3)
val newValues = values :+ 4Here, values is unchanged and newValues contains the additional element.
Mutable collections:
- Can be changed after creation.
- Operations such as
+=,-=, orupdatemodify the existing collection. - They may be useful for algorithms that require frequent updates.
Example:
import scala.collection.mutable.ArrayBuffer
val values = ArrayBuffer(1, 2, 3)
values += 4Immutable collections provide safety, predictability, and better support for concurrency. Mutable collections can be more efficient for repeated in-place changes but require careful control of shared state. Scala generally encourages immutable collections unless mutation provides a clear performance or algorithmic benefit.
Define a function in Scala and explain the difference between a function definition, a function value, and a method.
A function in Scala is a value that can be stored in a variable, passed as an argument, or returned from another function.
A function value can be created as follows:
val add: (Int, Int) => Int = (a, b) => a + bThe type (Int, Int) => Int means that the function accepts two Int values and returns an Int.
A method is defined inside a class, object, or trait using def:
def multiply(a: Int, b: Int): Int = a * bA method is not automatically a function value, although it can often be converted to one when passed as an argument.
A function definition using a function literal creates a function value directly:
val double = (x: Int) => x * 2The key distinctions are:
- A method belongs to a class, object, or trait.
- A function value is an object that represents executable behavior.
- A function value can be assigned to variables and manipulated like other values.
- Methods may use features such as overloading and type parameters differently from function values.
Explain higher-order functions and closures in Scala. Provide examples showing how they are used.
A higher-order function is a function that accepts another function as an argument, returns a function, or does both.
For example:
def applyOperation(x: Int, y: Int, operation: (Int, Int) => Int): Int =
operation(x, y)
val sum = applyOperation(4, 5, (a, b) => a + b)Here, applyOperation accepts the function operation as a parameter.
A closure is a function that captures variables from the surrounding scope. The function can use those variables even when it is executed later.
var factor = 3
val multiply = (x: Int) => x * factor
val result = multiply(4)The function multiply refers to factor, which is defined outside the function body. Therefore, it forms a closure over factor.
Higher-order functions support reusable and composable logic. Closures are useful when behavior depends on configuration or surrounding state. However, capturing mutable variables can make code harder to reason about, so immutable captured values are generally preferred.
Describe lists in Scala and explain important list operations such as construction, access, concatenation, mapping, filtering, and folding.
A List in Scala is an ordered, immutable collection. Lists are commonly used for functional programming because elements can be added at the beginning efficiently and operations produce new lists.
Construction:
val numbers = List(1, 2, 3)
val another = 1 :: 2 :: 3 :: NilNil represents the empty list, and :: is the list construction operator.
Important operations:
headreturns the first element.tailreturns all elements except the first.lengthreturns the number of elements.++concatenates two lists.mapapplies a function to every element.filterretains elements satisfying a predicate.foldLeftorfoldRightcombines elements into a single result.
Example:
val numbers = List(1, 2, 3, 4)
val squares = numbers.map(x => x * x)
val even = numbers.filter(x => x % 2 == 0)
val total = numbers.foldLeft(0)((sum, x) => sum + x)Lists provide convenient operations for transforming data without modifying the original collection.
Compare lists and arrays in Scala with respect to mutability, access time, memory behavior, and typical use cases.
Lists and arrays are both sequence collections, but they have different internal structures and usage patterns.
| Feature | List | Array |
|---|---|---|
| Structure | Linked list | Contiguous indexed storage |
| Default mutability | Immutable | Mutable elements |
| Access by index | Generally | Generally |
| Adding at the front | Efficient | Usually requires shifting or a new array |
| Memory usage | Stores links between nodes | Stores elements in indexed positions |
| Best use | Recursive processing and sequential traversal | Frequent indexed access and updates |
A list can be created as follows:
val values = List(10, 20, 30)An array can be created as follows:
val values = Array(10, 20, 30)
values(1) = 25Lists are appropriate when immutability and functional transformations are important. Arrays are preferable when the size is fixed or when fast indexed access and in-place updates are required. Choosing between them depends on the access pattern and mutation requirements.
Explain maps in Scala and distinguish between immutable and mutable maps. Include examples of insertion, lookup, update, and traversal.
A Map stores key-value pairs, where each key is associated with at most one value. Maps are useful for dictionaries, indexes, and representing relationships between identifiers and data.
An immutable map can be created as follows:
val studentMarks = Map("Asha" -> 85, "Ravi" -> 78)
val marks = studentMarks.get("Asha")
val updated = studentMarks + ("Mina" -> 91)The expression get returns an Option, such as Some(85) or None, which avoids failures caused by missing keys. A direct lookup using studentMarks("Asha") returns the associated value but may throw an exception for an absent key.
A mutable map can be used when in-place modification is required:
import scala.collection.mutable.Map
val counts = Map("red" -> 1)
counts("blue") = 2
counts("red") = counts("red") + 1Maps can be traversed with foreach, map, or a for expression:
for ((name, mark) <- studentMarks) {
println(s"$name: $mark")
}Immutable maps favor safety and predictable behavior, while mutable maps may be convenient for frequently changing state.
What are streams in Scala? Explain how streams support lazy evaluation and compare them with strict collections.
A stream is a sequence whose elements are evaluated lazily. In a lazy collection, elements are computed only when they are requested. This makes it possible to represent very large or conceptually infinite sequences.
For example, a lazy sequence can be defined using LazyList in modern Scala:
val naturalNumbers: LazyList[Int] = LazyList.from(1)
val firstFive = naturalNumbers.take(5).toListThe expression LazyList.from(1) does not calculate all natural numbers immediately. Only the elements required by take(5) are evaluated.
Strict collections:
- Compute their elements immediately.
- Store the complete result in memory.
- Are suitable for finite collections whose elements are inexpensive to calculate.
Streams or lazy collections:
- Delay computation until values are needed.
- Can represent infinite sequences.
- May reduce memory usage and avoid unnecessary calculations.
- Can introduce overhead because values may need to be evaluated and cached.
Lazy evaluation is especially useful when processing pipelines contain operations such as map, filter, and take. Only the portion of the pipeline needed to produce the final result is evaluated.
Write and explain a Scala program that uses a list, a higher-order function, and immutable transformations to calculate the sum of squares of even numbers.
A suitable program is:
val numbers = List(1, 2, 3, 4, 5, 6)
val result = numbers
.filter(x => x % 2 == 0)
.map(x => x * x)
.sum
println(result)The execution proceeds as follows:
filterselects the even numbers:List(2, 4, 6).maptransforms each selected number into its square:List(4, 16, 36).sumadds the values: .
The functions x => x % 2 == 0 and x => x * x are function values passed to collection methods. The list is immutable, so none of these operations changes numbers. Instead, each transformation produces an intermediate result. This style is declarative because it expresses what should be computed rather than specifying each mutation and loop step.
Explain the difference between val, var, and def in Scala. Discuss their effect on immutability, evaluation, and repeated execution.
Scala uses val, var, and def for different kinds of bindings and definitions.
val: Defines an immutable reference. Once assigned, it cannot be reassigned.
val x = 10The reference x cannot later point to another value. If the referenced object is mutable, however, the object's internal state may still change.
var: Defines a mutable variable that can be reassigned.
var counter = 0
counter = counter + 1def: Defines a method. Its body is evaluated each time the method is called.
def currentTime = System.currentTimeMillis()A val is normally evaluated once when it is initialized. A parameterless def is evaluated whenever it is invoked. A var stores a changeable reference.
Using val and immutable objects is generally safer because it reduces side effects and makes concurrent programs easier to reason about. var is useful when state must change, while def is appropriate for reusable computation.
Describe pattern matching in Scala and explain how it can be used to process lists and other values.
Pattern matching is a Scala construct used to compare a value against a set of patterns and execute the corresponding expression. It is more powerful than a simple switch statement because it can inspect types, constants, tuples, and collection structures.
Example:
def describe(value: Any): String = value match {
case 0 => "zero"
case text: String => s"text: $text"
case number: Int => s"number: $number"
case _ => "other"
}Lists can also be processed through structural patterns:
def listSize(values: List[Int]): Int = values match {
case Nil => 0
case _ :: tail => 1 + listSize(tail)
}In this example:
Nilmatches an empty list._ :: tailmatches a nonempty list and separates its head from the remaining elements._is a wildcard pattern.
Pattern matching improves readability and is particularly useful with algebraic data types, recursive structures, and safe decomposition of complex values. A wildcard or complete set of cases should be included to avoid non-exhaustive matching.
Explain recursion and tail recursion in Scala. Derive a tail-recursive method for calculating the factorial of a non-negative integer.
Recursion occurs when a method calls itself to solve smaller instances of the same problem. A recursive method must include a base case to stop the recursion.
The factorial function is defined mathematically as:
with the base case:
A straightforward recursive implementation is:
def factorial(n: Int): BigInt = {
if (n == 0) 1
else n * factorial(n - 1)
}A tail-recursive implementation performs the recursive call as its final operation:
import scala.annotation.tailrec
def factorial(n: Int): BigInt = {
require(n >= 0)
@tailrec
def loop(current: Int, result: BigInt): BigInt = {
if (current == 0) result
else loop(current - 1, result * current)
}
loop(n, 1)
}The accumulator result stores the partial product. Scala can optimize a verified tail-recursive call into a loop, reducing the risk of stack overflow. The @tailrec annotation asks the compiler to confirm that the recursive method is genuinely tail recursive.
Compare map, flatMap, and for comprehensions in Scala. Explain their relationship using examples.
The methods map and flatMap transform collections and other monadic types such as Option.
map: Applies a function to every element and returns one result for each input element.
val values = List(1, 2, 3)
val squares = values.map(x => x * x)flatMap: Applies a function that returns a collection and then flattens the resulting collections.
val values = List(1, 2, 3)
val expanded = values.flatMap(x => List(x, x * 10))The result is List(1, 10, 2, 20, 3, 30) rather than a nested list.
A for comprehension provides readable syntax for combinations of map, flatMap, and withFilter:
val result = for {
x <- List(1, 2, 3)
if x % 2 == 1
} yield x * xThis is conceptually similar to applying a filter followed by a map. Multiple generators generally correspond to flatMap, while the final yield corresponds to map. These operations support concise data-processing pipelines without explicit mutation.
Explain the role of Option, Some, and None in Scala map lookups and compare this approach with returning null.
Option represents a value that may or may not exist. It has two common forms:
Some(value)indicates that a value is present.Noneindicates that no value is available.
For example:
val prices = Map("book" -> 250, "pen" -> 20)
val price: Option[Int] = prices.get("book")A lookup for "book" returns Some(250), while a missing key returns None.
The result can be processed safely using pattern matching or collection-style methods:
val message = prices.get("pencil") match {
case Some(value) => s"Price: $value"
case None => "Item not found"
}It can also be handled with getOrElse:
val price = prices.get("pencil").getOrElse(0)Returning Option is safer than returning null because the possibility of absence is represented in the type system. The caller is encouraged to handle both cases, reducing null-pointer errors. It also composes naturally with map, flatMap, and for comprehensions.
Explain lazy evaluation in Scala and show how a lazy collection can improve efficiency in a sequence of transformations.
Lazy evaluation delays the computation of an expression until its result is required. In Scala, lazy behavior can be expressed using lazy val and lazy collections such as LazyList.
Consider this example:
val result = LazyList.from(1)
.map(x => x * x)
.filter(x => x % 2 == 0)
.take(3)
.toListThe result is List(4, 16, 36). The infinite source is not fully generated, and the entire mapped or filtered sequence is not stored. Evaluation continues only until three matching values have been found.
With a strict collection, intermediate results may be created after each transformation. A lazy collection can therefore:
- Avoid computing unused elements.
- Process conceptually infinite sequences.
- Reduce the number of intermediate collections.
- Improve efficiency when a consumer requests only a small prefix.
Lazy evaluation also has costs. Computation may be repeated or delayed unexpectedly, and debugging can be less direct. It is most useful when the data source is large or infinite and only part of the result is needed.
Discuss function parameters in Scala, including default parameters, named arguments, variable-length parameters, and call-by-name parameters.
Scala provides several parameter features that make methods and functions flexible.
Default parameters provide a value when the caller omits an argument:
def greet(name: String, punctuation: String = "!"): String =
"Hello, " + name + punctuationNamed arguments allow arguments to be supplied by parameter name, improving readability:
greet(name = "Asha", punctuation = ".")Variable-length parameters accept zero or more values using *:
def total(values: Int*): Int = values.sumThe method can be called as total(2, 4, 6).
Call-by-name parameters are written with => before the type:
def choose(condition: Boolean, whenTrue: => String, whenFalse: => String): String =
if (condition) whenTrue else whenFalseA call-by-name argument is evaluated only when it is used, which is useful for conditional execution and custom control structures. In contrast, ordinary parameters are evaluated before the method is called. These features support expressive APIs while retaining static type checking.
Explain traits and objects in Scala and discuss their importance in designing Scala programs.
A trait is a reusable abstraction that can contain abstract members, concrete methods, and fields. A class can extend one or more traits, allowing behavior to be composed without relying on multiple class inheritance.
trait Printable {
def printInfo(): String
}
class Report(val title: String) extends Printable {
def printInfo(): String = title
}An object defines a singleton instance. It is commonly used for utility methods, shared state, and program entry points.
object MathUtil {
def cube(x: Int): Int = x * x * x
}The method can be called as MathUtil.cube(3) without creating an instance.
Scala also supports a companion object, which has the same name as a class. The class and companion object can access each other's private members. Companion objects are often used for factory methods and constants.
Traits encourage modular design by separating reusable behavior from data representation. Objects provide controlled access to singleton functionality. Together, they support abstraction, code reuse, and a clean combination of object-oriented and functional techniques.
Explain the major features of Scala and discuss how Scala combines object-oriented and functional programming paradigms.
Scala is a modern, statically typed programming language that runs on the Java Virtual Machine (JVM). Its major features include:
- Object-oriented programming: Every value in Scala is an object, and concepts such as classes, objects, inheritance, and traits are supported.
- Functional programming: Scala supports higher-order functions, immutability, pattern matching, closures, and expressions that return values.
- Static typing with type inference: The compiler determines many types automatically while still checking type correctness at compile time.
- JVM interoperability: Scala can use existing Java libraries and frameworks.
- Concise syntax: Semicolons are usually optional, and many language constructs require less code than equivalent Java constructs.
- Concurrency support: Immutable data structures, futures, actors, and other abstractions help developers write concurrent programs.
Scala combines both paradigms by allowing developers to define classes and objects while also treating functions as first-class values. For example, a class can contain immutable fields and methods that accept functions as arguments. This combination is useful for building scalable and maintainable software.
Did this save you a night before the exam?
LPU Notes is free, and it stays free. Ads cover part of the server bill. The rest comes out of a student's own pocket: the domain, the storage, and keeping the site up through the weeks everyone needs it at once.
The payment button didn't load. An ad blocker or a filtered network is the usual reason. to try again.
Nothing here is ever locked, and nothing unlocks. Chip in only if it was worth it. What it pays for →