Technology  /  Scala

🔴 Scala 21 guides · updated 2026

The JVM's hybrid OOP/functional language — immutability, pattern matching, and the type system that powers Spark and Akka, from first principles to production.

Implicits in Scala: Implicit Parameters, Conversions and Classes Explained

Implicits are the single most polarizing feature in Scala’s history — praised for enabling elegant, boilerplate-free APIs, criticized for making code where “where did this value come from?” requires IDE support to answer at all. Scala 3 replaced most of this mechanism with clearer, more restricted given/using syntax (its own dedicated guide). But an enormous amount of existing Scala 2 code — including much of Spark’s and Akka’s ecosystem — still relies on classic implicits, so understanding them is necessary for reading real codebases, even in new Scala 3 projects.


Implicit Parameters — Values Supplied Automatically

def greet(name: String)(implicit greeting: String): String = s"$greeting, $name!"
implicit val defaultGreeting: String = "Hello"
greet("Alice") // "Hello, Alice!" — the implicit parameter is filled in automatically

The greeting parameter is marked implicit, meaning the compiler will search the current scope for a value marked implicit with a matching type (String here) and supply it automatically — you never have to pass it explicitly at the call site, as long as exactly one matching implicit value is in scope. This is the mechanism behind Scala’s ExecutionContext (needed implicitly by every Future, covered in its own guide) and countless library APIs that need contextual configuration without forcing every call site to thread it through manually.

// You CAN still supply it explicitly if needed
greet("Bob")("Hey") // "Hey, Bob!" — overrides the implicit value for this one call

exactly one found

zero found

more than one found

greet('Alice')

Compiler searches current scope

for an implicit String value

defaultGreeting used automatically

Compile error: could not find implicit value

Compile error: ambiguous implicit values


The Danger of “Where Did This Value Come From?”

// Somewhere in file A.scala
implicit val timeout: Int = 30
// Somewhere else entirely, in file B.scala
def fetchData()(implicit timeoutSeconds: Int): String = s"fetched with ${timeoutSeconds}s timeout"
fetchData() // "fetched with 30s timeout" — but WHERE does 30 come from, reading this line alone?

This is the legitimate core criticism of implicit parameters: reading fetchData() in isolation gives you no visual indication that a value is being supplied from somewhere else entirely — you need either deep familiarity with the codebase or IDE tooling (which can show inferred implicit values inline) to know what’s actually happening. This exact readability concern is what motivated Scala 3’s given/using redesign, which makes the type being requested more prominent and restricts where implicit values can be defined, reducing the “which of dozens of implicit values in scope actually got picked” ambiguity.


Implicit Conversions — Automatic Type Transformation

implicit def intToString(x: Int): String = x.toString
def printName(name: String): Unit = println(name)
printName(42) // Compiles! 42 is implicitly converted to "42" via intToString

An implicit conversion (an implicit def taking one argument and returning another type) is inserted automatically by the compiler wherever a value of the wrong type is used but a matching conversion exists — printName(42) compiles because the compiler silently rewrites it to printName(intToString(42)). This is, frankly, the most dangerous form of implicits: it can make genuinely nonsensical code compile without any visible indication that a conversion happened at all, which is why implicit conversions specifically require an explicit import scala.language.implicitConversions in modern Scala — the language deliberately makes this feature opt-in and visible at the import site, precisely because of how easily it can be misused.

// A real, legitimate use: converting between compatible numeric-like domain types
case class Celsius(value: Double)
case class Fahrenheit(value: Double)
implicit def celsiusToFahrenheit(c: Celsius): Fahrenheit = Fahrenheit(c.value * 9 / 5 + 32)
def printTemp(f: Fahrenheit): Unit = println(s"${f.value}°F")
printTemp(Celsius(20)) // implicitly converted — prints "68.0°F"

Even in this legitimate-looking example, most modern Scala style guides recommend an explicit conversion method or a dedicated extension method instead — implicit conversions are now considered a legacy pattern to avoid in new code, kept in this guide specifically because you will encounter it reading existing Scala 2 codebases.


Implicit Classes — Extension Methods, Scala 2 Style

implicit class StringOps(s: String) {
def shout: String = s.toUpperCase + "!"
def isPalindrome: Boolean = s == s.reverse
}
"hello".shout // "HELLO!"
"racecar".isPalindrome // true

Unlike the broadly-criticized implicit conversions above, implicit classes are a genuinely well-regarded, still-idiomatic pattern — they let you add new methods to a type you don’t own (here, the built-in String) without modifying its source or wrapping it manually every time. The compiler automatically wraps "hello" in a StringOps instance whenever .shout is called on a String, then unwraps the result — this is exactly the “extension method” concept from other languages (C#‘s extension methods, Kotlin’s extension functions), implemented in Scala 2 via implicit classes specifically.

// A more realistic, domain-specific example
implicit class RichOrder(order: Order) {
def isLarge: Boolean = order.amount > 1000
def withDiscount(percent: Double): Order = order.copy(amount = order.amount * (1 - percent / 100))
}
case class Order(id: String, amount: Double)
val order = Order("A1", 1500.0)
order.isLarge // true
order.withDiscount(10).amount // 1350.0

This pattern — adding domain-specific convenience methods to a case class you already own, purely for readability at call sites — remains common and genuinely useful in modern Scala 2 codebases, and Scala 3’s extension keyword (covered in the Scala 3 features guide) does the identical job with clearer, more explicit syntax.


Implicit Resolution Order — Where the Compiler Looks

object Defaults {
implicit val defaultTimeout: Int = 30
}
import Defaults.defaultTimeout // explicitly imported implicit
def fetch()(implicit timeout: Int): String = s"timeout: $timeout"
fetch() // "timeout: 30"

When resolving an implicit parameter, the compiler searches, roughly in order: implicits declared or imported directly in the current local scope, implicits in the enclosing object/class, implicits in companion objects of the parameter’s type (a special, often-overlooked rule — a companion object automatically contributes its implicits without needing an explicit import), and implicits in outer scopes. If more than one candidate implicit of the same type is found at the same priority level, compilation fails with an “ambiguous implicit values” error rather than silently picking one — a deliberate safety measure, though the underlying search order across all these levels is genuinely one of the more confusing parts of the classic implicits system to reason about precisely.


Implicits as the Foundation for Type Classes

trait Show[T] {
def show(value: T): String
}
implicit val showInt: Show[Int] = (value: Int) => s"Int: $value"
implicit val showString: Show[String] = (value: String) => s"String: $value"
def display[T](value: T)(implicit s: Show[T]): String = s.show(value)
display(42) // "Int: 42"
display("hello") // "String: hello"

This pattern — an implicit parameter supplying a trait instance describing “how to do X for type T” — is precisely the mechanism behind the type class pattern, covered in full in its own dedicated guide. Understanding implicit parameters here is a direct prerequisite for that guide, since type classes in Scala 2 are built entirely from implicits with no additional language feature needed.


Context Bounds — Shorthand for a Common Implicit Pattern

// Explicit implicit parameter
def maxOf[T](list: List[T])(implicit ord: Ordering[T]): T = list.max(ord)
// Context bound — identical meaning, more concise syntax
def maxOfShort[T: Ordering](list: List[T]): T = list.max

T: Ordering is a context bound — shorthand for “there must be an implicit Ordering[T] available,” expanded by the compiler into exactly the explicit (implicit ord: Ordering[T]) parameter shown above. This shorthand is extremely common in real Scala code specifically because “requires a type class instance for T” is such a frequent pattern — sorting, equality, JSON encoding, and numeric operations across generic code virtually all use this exact shape, and the type classes guide covers the full pattern this shorthand is built on.

// Accessing the implicit value inside a context-bound method requires implicitly[T] or the summon-style helper
def maxOfExplicit[T: Ordering](list: List[T]): T = {
val ord = implicitly[Ordering[T]] // retrieves the implicit value the context bound brought into scope
list.max(ord)
}

implicitly[T] is the standard way to retrieve a context-bound’s implicit value by name when you need to use it directly rather than relying on a method (like .max) that already knows to look for it implicitly on its own.

Implicit Scope and “Orphan” Instances

// The idiomatic place to define a type class instance: the type class's OWN companion object
trait Show[T] {
def show(value: T): String
}
object Show {
implicit val showInt: Show[Int] = (v: Int) => v.toString // lives in Show's companion — found automatically
}
// An "orphan" instance — defined somewhere unrelated to either the type class or the type it's for
object SomeUnrelatedObject {
implicit val showBoolean: Show[Boolean] = (v: Boolean) => v.toString // requires an explicit import to use
}

Defining a type class instance inside the type class’s own companion object (Show’s companion, above) means it’s found automatically everywhere, with no import needed — this exploits the “companion objects automatically contribute implicits” rule mentioned earlier. An instance defined anywhere else (an “orphan” instance) requires an explicit import at every use site, which is both more error-prone and a common source of “why isn’t my implicit being found” confusion for newcomers who haven’t yet learned this specific placement rule.

Common Pitfalls

SymptomLikely cause
”could not find implicit value for parameter”No matching implicit is in scope — check whether it needs an explicit import, especially for an “orphan” instance not in a relevant companion object
”ambiguous implicit values”Two or more implicits of the same type are visible at the same priority level — remove or rename one, or make the search more specific
An implicit conversion “silently” changes behavior unexpectedlyImplicit conversions require import scala.language.implicitConversions to even compile — if this surprises you, search for that import and consider whether the conversion is genuinely needed
An implicit class’s extension method isn’t foundThe implicit class itself needs to be in scope (imported, or in an object already imported) — the extension method mechanism still requires the class definition to be visible
Context bound (T: Ordering) fails to compile with “not found: type Ordering”Missing import of scala.math.Ordering (or the specific type class trait) itself, separate from the implicit instance

Frequently Asked Questions

Are implicits deprecated in Scala 3? Not entirely removed, but Scala 3 introduces given/using as the clearer, recommended replacement for implicit parameters and implicit values specifically — classic implicit def/implicit val syntax still compiles (with warnings in some configurations) for migration compatibility, but new Scala 3 code should use given/using, covered in the next guide.

Why are implicit conversions considered more dangerous than implicit parameters? Implicit parameters are visible in a function’s signature ((implicit x: T)) even if the value supplied isn’t visible at the call site — you at least know a parameter is being implicitly filled. Implicit conversions can silently transform a value’s entire type with zero syntactic hint anywhere that a conversion occurred at all, which is a much easier way to introduce genuinely confusing, hard-to-trace behavior.

Is the implicit class pattern still recommended in new Scala 2 code? Yes — unlike implicit conversions, implicit classes for extension methods remain a well-regarded, idiomatic pattern with no serious criticism attached, and they continue to work identically in Scala 3 (though the newer extension keyword is the more modern equivalent there).

What happens if the compiler finds zero matching implicits for a required parameter? A compile error: “could not find implicit value for parameter …”. This is a compile-time failure, not a runtime one — the missing dependency is caught before the program ever runs, which is one of the genuine safety benefits of the whole implicits mechanism despite its readability criticisms.

Do I need to master classic implicits if I’m only writing new Scala 3 code? You should still recognize the syntax and reasoning, since a huge amount of existing library code (Spark, Akka, and older codebases generally) uses it, and Scala 3’s given/using is explicitly designed as “implicits, but clearer” rather than a completely unrelated mechanism — understanding one makes the other significantly easier to learn.


What’s Next

Implicits are Scala 2’s mechanism for contextual values, extension methods, and type classes — powerful, if occasionally opaque. Next: Given and Using in Scala 3, covering how Scala 3 redesigned this exact mechanism for clarity, and how to translate between the two styles when working across both versions.