Givens, implicits and type classes

Define and consume given instances, resolve extension methods, and implement a type class the way the libraries do.

giving and using

// a given is a value the compiler can supply implicitly
given intOrdering: Ordering[Int] = Ordering.Int

// an anonymous given: the name is not needed unless you import it explicitly
given Ordering[String] = Ordering.String

// a parameterised given: derived for any A that already has an Ordering
given [A](using ord: Ordering[A]): Ordering[List[A]] = Ordering.by(_.headOption)

// using clauses consume givens; the caller does not pass them
def largest[A](xs: List[A])(using ord: Ordering[A]): A =
  xs.max(ord)

// Scala 3 context bound syntax, and naming the instance when you need it
def smallest[A: Ordering as ord](xs: List[A]): A = xs.min(ord)

// summon retrieves the instance for a type
val o = summon[Ordering[Int]]
val oo = Ordering[Int]           // the companion object method, equivalent

// a given can be a class with parameters
final class Logger(prefix: String):
  def info(msg: String): Unit = println(s"[$prefix] $msg")

given Logger = Logger("app")

def run(using log: Logger): Unit = log.info("starting")
run                              // the given is supplied automatically
  • given replaces implicit val and implicit def; using replaces implicit parameter lists. The old syntax still compiles in Scala 3 with the compatibility flags.
  • Name a given when you might want to import it explicitly or override it in a test; leave it anonymous otherwise, to discourage name-based usage.
  • Givens are resolved at compile time from the local scope, the companion objects of the involved types, and any imports. Adding an import can therefore change which instance is chosen.
  • Never use a given to hide a dependency that varies at run time. If two instances may need to coexist, pass the value explicitly.

A type class end to end

// 1. the type class: an interface over a type you do not own
trait Encoder[A]:
  def encode(a: A): String

object Encoder:
  def apply[A](using e: Encoder[A]): Encoder[A] = e

  // 3. a summoner for the common case
  def encode[A: Encoder](a: A): String = summon[Encoder[A]].encode(a)

  // 4. instances in the companion: found by implicit scope, no import needed
  given Encoder[Int]    with
    def encode(a: Int) = a.toString

  given Encoder[String] with
    def encode(a: String) = s"\"$a\""

  given [A](using e: Encoder[A]): Encoder[List[A]] with
    def encode(a: List[A]) = a.map(e.encode).mkString("[", ",", "]")

  given [A](using e: Encoder[A]): Encoder[Option[A]] with
    def encode(a: Option[A]) = a.fold("null")(e.encode)

// 2. extension methods for the nice call site
extension [A](a: A)(using e: Encoder[A])
  def toJson: String = e.encode(a)

import Encoder.given
println(List(1, 2, 3).toJson)          // [1,2,3]
println(Option("x").toJson)             // "x"
println(Map("k" -> 1))
PieceRoleConvention
trait Encoder[A]The capabilityOne abstract method where possible
given Encoder[Int]An instanceIn the companion of the trait or of Int
def encode[A: Encoder]Consumes the capabilityContext bound for brevity
extensionCall-site syntaxa.toJson instead of encode(a)
summonRetrieves the instanceInside a definition that needs it by name

Putting the instances in the companion object means they are found through implicit scope, so a caller needs no import at all. That is exactly how Ordering[Int] and every Cats and Circe instance works.

// overriding an instance in a specific scope, for a test or a special case
object TestScope:
  given testEncoder: Encoder[Int] with
    def encode(a: Int) = "REDACTED"

  def render: String =
    import Encoder.encode
    encode(42)                      // picks the local given, which wins over the companion

Reading library code that uses them

// what this signature actually demands
def process[F[_]: Monad, A: Show](fa: F[A]): F[String]
// F is a type constructor with a Monad instance; A has a Show instance

// and this one, spelled out
def process2[F[_], A](fa: F[A])(using m: Monad[F], s: Show[A]): F[String] = ???

// syntax from Cats: the instance is a given, the extension is imported
import cats.syntax.all.*
val x: Option[Int] = Some(1)
val y = x.map(_ + 1).flatMap(v => if v > 0 then Some(v) else None)
val z = (x, y).mapN(_ + _)         // applicative combination

// If you see "given" plus "using" plus an "extension" in one file,
// you are almost certainly looking at a type class and its syntax layer.
// The "syntax" package is the extensions; the instance package is the givens.
⚠️
Ambiguous implicit values is the most common compiler error in this area. It happens when two instances are equally specific and both are in scope. Fix it by moving one to the companion object, by making the intended instance more specific, or by passing it explicitly — never by disabling the warning.

FAQ

Are givens the same as Scala 2 implicits?
Conceptually yes, and the resolution rules are largely the same. The syntax is cleaner, ambiguity and unused-implicit warnings are better, and Scala 3 requires given and using so the two roles are distinguishable by eye.
Should I use a type class or inheritance?
A type class when the capability is orthogonal to the type or you do not own the type. Inheritance when the subtype genuinely is a kind of the parent and the behaviour belongs with the data.

Generics, variance and type bounds A small HTTP service with http4s or Play

Last refreshed 2026-09-18.