Scala's object Keyword
Understand Scala's object keyword — Scala for Python Developers.
Focus: understand scala's object keyword
You've mastered Scala's classes, case classes, and traits — but there's a strange keyword lurking in every Scala codebase that looks like a class, acts like a singleton, and yet has no Python equivalent. When you first see object Main extends App, your Python instincts scream 'that's just a class with static methods!' — and that instinct will lead you astray. Scala's object keyword creates a singleton object: a type with exactly one instance, lazily initialized on first access, that serves as your home for static-like methods, factory methods, and the entry point of your entire program. Without understanding object, you'll find yourself writing unnatural Scala code, fighting the compiler, and missing out on one of the language's most elegant design patterns.
The problem this lesson solves
In Python, you have several ways to organize code that doesn't belong to a specific instance. You create a class with @staticmethod, use module-level functions, or define a module-level singleton with a custom __new__ override. Each approach has trade-offs, but none is canonical — your codebase might mix all three, and reviewers will debate which is 'Pythonic' at every code review.
Scala solves this problem decisively with the object keyword. An object is a singleton: a type with exactly one instance, automatically created and managed by the JVM. You never call new on it — you just reference it by name, and the Scala compiler ensures only one instance exists across your entire application.
Here's the pain point for Python developers: you cannot translate object to any single Python concept. It's simultaneously a static method holder, a class with a pre-created instance, a module-level function namespace, and a lazy-initialized singleton. If you try to map it to your Python mental model, you'll write code that compiles but feels wrong. This lesson gives you the correct mental model so you write idiomatic Scala from day one.
Core concept / mental model
The simplest way to think about object in Scala is this: an object is a class that Scala has already instantiated for you, exactly once, and named the same as the class.
Think of a Python module-level singleton:
class _DatabaseConnection:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.connected = False
return cls._instance
database = _DatabaseConnection()
Scala's object gives you this pattern built into the language, with lazy initialization (the instance is created on first access, not at program start) and thread safety (the JVM guarantees only one instance, even under concurrent access).
But object goes beyond singletons. It also serves three distinct roles:
- A namespace for static-like methods — the Scala equivalent of Python's
@staticmethodand module-level functions. When you seeMath.sqrt(2)in Scala,Mathis anobject. - A companion to a class — an
objectwith the same name as aclassshares private access with that class. This is where factory methods likeapply()live. - A program entry point — your
mainmethod lives inside anobject, not a class.
Pro tip: If a method doesn't need instance state, it should probably live in an
object— not a class. This is idiomatic Scala, and it's how you keep your class definitions focused on state and behavior that genuinely requires an instance.
How it works step by step
Let's trace exactly what happens when the JVM encounters a Scala object:
- Compilation: The Scala compiler generates a Java class with a
privateconstructor and apublic static finalfield holding the single instance. This is the JVM-level singleton pattern, generated automatically. - Lazy initialization: The instance is not created at class-load time. Instead, the static field is initialized on first access, using a thread-safe lazy initialization mechanism (simplified: a static inner class holding the instance, or a synchronized check).
- First access: When your code references the
objectby name (e.g.,Greeting.sayHello()), the JVM triggers the static initializer, creates the single instance, and assigns it to the static field. All subsequent references reuse that same instance. - Usage: Methods on the
objectare essentially static methods under the hood, but they're called with instance-method syntax —Greeting.sayHello()looks like a class method call, but it's really a static call on the singleton.
Here's what this means in practice:
# Python: careful about when your singleton is created
import datetime
class Config:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.load()
return cls._instance
def load(self):
self.timeout = 30
// Scala: lazy, thread-safe, automatic
object Config {
val timeout: Int = 30
}
// Config is created only when you first mention it:
// println(Config.timeout)
Hands-on walkthrough
Let's build a complete example that exercises all the roles of object. We'll create a temperature converter — something you might build in Python as a module of utility functions.
Step 1: Create a simple object with methods.
Create a file TempConverter.scala:
object TempConverter {
def celsiusToFahrenheit(c: Double): Double = c * 9.0 / 5.0 + 32.0
def fahrenheitToCelsius(f: Double): Double = (f - 32.0) * 5.0 / 9.0
val boilingPointC: Double = 100.0
}
// In a separate file, or in the REPL:
println(TempConverter.celsiusToFahrenheit(0)) // 32.0
println(TempConverter.fahrenheitToCelsius(212)) // 100.0
println(TempConverter.boilingPointC) // 100.0
Run it with scala TempConverter.scala (or paste into the Scala REPL). You'll see:
32.0
100.0
100.0
Step 2: Use a companion object for factory methods.
The most common Scala pattern is a class and a companion object with the same name. The companion can access the class's private members:
class User private (val name: String, val age: Int) {
// private constructor — only the companion can call it
}
object User {
def apply(name: String, age: Int): User = new User(name, age)
def fromJson(json: String): User = {
// simplified: parse name and age from a JSON string
User("parsed-name", 25)
}
val anonymous: User = new User("guest", 0)
}
// Usage — note: no `new` keyword!
val alice = User("Alice", 30)
val bob = User.fromJson("{\"name\":\"Bob\",\"age\":40}")
val guest = User.anonymous
Step 3: Create an application entry point.
Every Scala program needs a main method, and it must live in an object:
object MainApp {
def main(args: Array[String]): Unit = {
val name = if (args.length > 0) args(0) else "world"
println(s"Hello, $name!")
}
}
Compile and run with scala MainApp.scala Scala — you'll see Hello, Scala!.
Pro tip: The syntax
User("Alice", 30)works because the companion object defines anapplymethod. This is so common that Scala has a shorthand:case classautomatically generates a companionobjectwith anapplymethod. That's why you can writecase class Person(name: String)and thenPerson("Alice")withoutnew.
Compare options / when to choose what
As a Python developer, you have multiple ways to achieve similar results. Here's a comparison table to help you map your instincts:
| What you want | Python approach | Scala approach | When it's the right choice |
|---|---|---|---|
| Utility functions without state | Module-level functions or @staticmethod |
object with methods |
Almost always — it's the idiomatic Scala way |
| A single global instance | __new__ singleton pattern, module-level variable |
object |
Always — it's built-in and thread-safe |
| Factory methods for a class | @classmethod like from_json |
Companion object with apply or named factory methods |
When you need controlled instance creation |
| Constants | Module-level constants | object with val fields |
When constants are related, group them in an object |
| A class with only static behavior | Class with all static methods | object (not a class!) |
Always — you don't need a class if you never instantiate it |
| Thread-safe lazy singleton | Custom __new__ + lock |
object |
Always — the JVM handles it |
The key decision rule: If you would write a Python class with only @staticmethod and @classmethod methods, or a module full of functions and constants, then in Scala you should write an object. If you need multiple instances with state, use a class. If you need both, use a class and a companion object.
There's also an alternative Scala pattern worth knowing: package objects (deprecated in Scala 3 in favor of top-level definitions). In Scala 2, you could write package object mypkg to hold functions and constants at the package level. In Scala 3, you simply write top-level def and val in a file — no enclosing object needed. This works well for small utility functions, but for anything cohesive, an object is still more discoverable and testable.
Troubleshooting & edge cases
Error: object cannot be extended (no extends). An object is final — it cannot be subclassed. If you see a compiler error like illegal inheritance from final object, you're trying to extend an object. Instead, extract the shared behavior into a trait and have both the object and any classes extend it:
trait Formatter {
def format(s: String): String
}
object UpperFormatter extends Formatter {
def format(s: String): String = s.toUpperCase
}
Gotcha: object initialization order. An object is initialized lazily on first access, so if your object's initialization reads configuration or connects to a database, that work happens when the object is first touched — not at program start. This is usually fine, but it can cause surprising lag in the first call that uses the object.
Gotcha: circular references between objects. Two objects that reference each other during initialization can cause NullPointerException if A's initialization uses B before B is fully initialized. Solution: initialize dependencies lazily inside methods rather than in val field declarations.
Gotcha: main must be in an object. A common mistake is placing main inside a class:
class MainApp { // WRONG
def main(args: Array[String]): Unit = println("Hi")
}
This compiles but doesn't run as an application — scala MainApp will tell you that it found no main method. The main must be in an object, or you can extend App (though App has its own quirks with argument handling).
Gotcha: object vs. val singleton comparison. If you need a singleton that's part of a larger inheritance hierarchy, a val in a companion object might be easier to work with:
class Database(host: String)
object AppDatabase {
val main: Database = new Database("localhost")
}
// vs.
object MainDatabase extends Database("localhost")
The val approach lets you refer to the singleton as AppDatabase.main and swap implementations more easily.
What you learned & what's next
You now understand why every Scala codebase is full of object declarations. An object is Scala's built-in singleton — a type with exactly one lazily-initialized, thread-safe instance. You learned that it serves three critical roles: a home for static-like utility methods, a companion to classes for factory methods and private access, and the mandatory container for your main entry point. You can now map Python patterns to Scala idioms: Python module-level functions become object methods, Python singleton hacks become built-in objects, and Python class methods become companion object factory methods.
You're ready to apply this immediately: refactor a small Python utility module into idiomatic Scala using object. The next lesson in this track examines pattern matching — Scala's powerful replacement for Python's if/elif chains and match statements. You'll see how object and case classes work together to enable elegant, exhaustive pattern matching that the compiler verifies for you.
Practice recap
Create a Calculator object with add, subtract, multiply, and divide methods, mirroring a Python module you might write for arithmetic. Then define a companion class Calculator with a constructor field precision: Int and an apply method in the object that defaults to precision 2. Verify that Calculator(5, 3) and Calculator.apply(5, 3) behave identically, and confirm divide throws ArithmeticException on division by zero — the Scala way.
Common mistakes
- Treating
objectlike a class and trying to extend it —objectis final and cannot be subclassed; extract shared behavior into atraitinstead. - Placing the
mainmethod inside aclass— the JVM entry point must live in anobject(or viaextends App, with its own caveats). - Using
newwith an object:new MyObjectfails — the singleton already exists; reference it by name directly. - Ignoring lazy initialization and assuming an
objectis created at program start — initialization happens on first access, which can cause surprising delays or ordering issues.
Variations
- Use
extends Appfor a quick main —object Hello extends App { println(\"Hi\") }— but beware thatargsaccess differs from a custommainmethod, especially with JVM flags. - Scala 3 allows top-level definitions without an enclosing
objectfor utility functions, though a cohesiveobjectremains the clearer pattern in many cases. - Package objects in Scala 2 serve a similar role for package-level utilities, but are deprecated in Scala 3 in favor of top-level definitions.
Real-world use cases
- A global configuration holder in a microservice — e.g.,
object Settingsloads environment variables and provides typed vals used across the service. - A companion object for a case class to provide
apply,fromJson, and other factory methods without exposing the constructor — common in domain models. - A utility library for logging or math operations where no state is needed — e.g.,
object Loggeraggregating log methods, similar to Python'sloggingmodule functions.
Key takeaways
- An
objectis a singleton — the Scala compiler enforces exactly one instance, created lazily on first access with thread safety built in. - Use
objectfor static-like utility methods and constants; useclassfor anything that requires multiple instances with state. - A companion
objectshares the same name and private access with itsclass, enabling factory methods likeapplythat hide constructors. - Every Scala application entry point —
def main(args: Array[String])— must live inside anobject. - If you'd write a Python class with only static/class methods, or a module full of functions and constants, the idiomatic Scala equivalent is an
object.
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.