Context Bounds & Deferred Givens

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Scala 3.10 (3.10.0, released 2026-10-12) and every example was compiled and run with Scala 3.10.0 through Scala CLI on JDK 26 (checked on 2026-10-15). Features newer than the 3.3 LTS line name their release; the named and aggregate context bounds and deferred givens (all 3.6) were also compiled with 3.9.0, and the 3.3.8 result is given where it matters.

A context bound is a shorthand for a context parameter that depends on a type parameter. Where a method says "I work for any T, provided there is an Ordering[T]`", the bound writes that requirement next to `T instead of in a separate using clause. Context bounds are what makes the type-class style readable, so this page builds on Givens & Using Clauses (where instances are defined and resolved) and leads into Type Classes and Extension Methods. The bound T: Ordering only helps if a type can be bounded, so it also touches Generics & Bounds.

The basic context bound

A bound T: Ordering on a type parameter T of a method or class adds a context parameter of type Ordering[T], called the witness (older text says evidence) for the bound. The method below is written both ways to show that the two are the same thing.

//> using scala 3.10.0

package trailhead

trait Show[T]:
  extension (t: T) def show: String

given Show[Int] = n => s"#$n"
given Show[String] = s => s"'$s'"

// context bound
def longest[T: Ordering](xs: List[T]): T = xs.max

// the same method with an explicit using clause
def longestExplicit[T](xs: List[T])(using Ordering[T]): T = xs.max

// named bound (3.6)
def sorted[T: Ordering as ord](xs: List[T]): List[T] = xs.sortWith(ord.lt)

// two bounds, chained
def describe[T: Show: Ordering](xs: List[T]): String = xs.sorted.map(_.show).mkString(", ")

@main def run(): Unit =
  println(longest(List(3, 9, 4)))
  println(longestExplicit(List("b", "z", "a")))
  println(sorted(List(5, 1, 3)))
  println(describe(List(30, 10, 20)))
  println(summon[Ordering[Int]].compare(1, 2))
9
z
List(1, 3, 5)
#10, #20, #30
-1

The compiler expands longest[T: Ordering](xs: List[T]) to longest[T](xs: List[T])(using Ordering[T]). The witness has a compiler-synthesized name, so inside the body the instance is reached with summon[Ordering[T]], with the Ordering[T] companion’s apply (Ordering[T] as a value, which the standard library defines), or implicitly, as xs.max does. When a method has both a subtype bound and a context bound, the subtype bound comes first: def f[T <: B: C](x: T).

Context bounds work on classes too, where the witness becomes a context parameter of the primary constructor. The same witness is then available to every method of the class.

//> using scala 3.10.0

package trailhead

class Box[T: Ordering](val items: List[T]):
  def top: T = items.max
  def ord: Ordering[T] = summon[Ordering[T]]
  def ord2: Ordering[T] = summon

def f[T: Ordering](x: T, y: T) = Ordering[T].lt(x, y)

@main def run(): Unit =
  val b = Box(List(1, 5, 2))
  println(b.top)
  println(b.ord eq b.ord2)
  println(f(1, 2))
  println(f(1, 2)(using Ordering[Int].reverse))
5
true
true
false

The last line shows that a context bound is still an ordinary context parameter: an argument is passed explicitly with (using ...), here the reversed ordering.

A bound can also constrain a type constructor, as in F[_]: Functor, so the same shorthand covers the higher-kinded type classes of Type Lambdas & Higher Kinds.

//> using scala 3.10.0

package trailhead

trait Functor[F[_]]:
  extension [A](fa: F[A]) def map[B](f: A => B): F[B]

given Functor[List] with
  extension [A](fa: List[A]) def map[B](f: A => B): List[B] = fa.map(f)

def double[F[_]: Functor](fa: F[Int]): F[Int] = fa.map(_ * 2)

@main def run(): Unit = println(double(List(1, 2, 3)))
List(2, 4, 6)

Named context bounds

Because the witness is anonymous, code that needs the instance as a value must call summon. Scala 3.6 adds an as clause after the type of a bound to give the witness a name. The name is analogous to an import rename, which also introduces a new name for something that comes before it.

//> using scala 3.10.0

package trailhead

def pick[T: Ordering as ord](xs: List[T]): T = xs.reduce((a, b) => if ord.lt(a, b) then a else b)

@main def run(): Unit = println(pick(List(8, 3, 5)))
3

def pick[T: Ordering as ord](...) expands to def pick[T](...)(using ord: Ordering[T]). The generated parameter carries exactly the name written after as. On Scala 3.3.8 the same code does not compile, because as is not part of the syntax of that line:

Not found: type as
Not found: type ord

The reference notes a related language extension that would name an unnamed witness after its type parameter; it is experimental and this page does not use it.

Aggregate context bounds

Several bounds on one type parameter are written inside braces. Each element may have its own as clause.

//> using scala 3.10.0

package trailhead

trait Show[T]:
  extension (t: T) def show: String

given Show[Int] = n => s"#$n"
given Show[String] = s => s"'$s'"

// aggregate bounds (3.6)
def render[T: {Ordering, Show}](xs: List[T]): String = xs.sorted.map(_.show).mkString(", ")

// named aggregate bounds
def renderMax[T: {Ordering as ord, Show as sh}](xs: List[T]): String =
  sh.show(xs.max(using ord))

@main def run(): Unit =
  println(render(List(3, 1, 2)))
  println(renderMax(List("a", "c", "b")))
#1, #2, #3
'c'

The braces syntax is valid from Scala 3.6. It replaces the chained form [T: Show: Ordering] (a second colon for each extra bound), which still compiles but is expected to be deprecated and removed over time. On Scala 3.3.8 the braces are a syntax error:

Illegal start of declaration

Bounds on type members

A type member of a trait can carry a context bound as well, written type Element: Ord. That reads as "whoever fixes Element must also supply an `Ord[Element]`". The bound is the abstract-type counterpart of a bound on a type parameter, and it is the reason deferred givens exist (next sections).

//> using scala 3.10.0

package trailhead

trait Show[T]:
  extension (t: T) def show: String

given Show[Int] = n => s"#$n"

trait Grid:
  type Cell: Show
  def cells: List[Cell]
  def dump: String = cells.map(summon[Show[Cell]].show(_)).mkString(" ")

class IntGrid extends Grid:
  type Cell = Int
  def cells = List(1, 2, 3)

@main def run(): Unit = println(IntGrid().dump)
#1 #2 #3

Where the generated parameters go

The compiler places the witnesses by three rules, applied in this order:

  1. If a bound is referred to by name in a later parameter clause, the bounds become a using clause immediately before that clause. A path-dependent parameter type needs this.

  2. Otherwise, if the last parameter clause is a using (or Scala 2 implicit) clause, the witnesses are merged in front of the parameters of that clause.

  3. Otherwise they form a new using clause at the end.

The first rule is new in 3.6; rules 2 and 3 match Scala 2, so binary compatibility with Scala 2 and earlier Scala 3 code is kept. The example uses rule 1 so that the parameter in can have the type p.Input.

//> using scala 3.10.0

package trailhead

trait Parser[P]:
  type Input
  type Result
  def parse(in: Input): Result

object DigitParser
given Parser[DigitParser.type] with
  type Input = String
  type Result = Int
  def parse(in: String): Int = in.toInt

// rule 1: the named bound is used by the parameter type of the next clause
def run[P: Parser as p](in: p.Input): p.Result = p.parse(in)

def f[T: Ordering](x: T)(using n: Int): String = s"$x $n"

@main def go(): Unit =
  println(run[DigitParser.type]("42"))
  given Int = 7
  println(f(1))
  println(f(1)(using Ordering.Int, 3))
42
1 7
1 3

run expands to def run[P](using p: Parser[P])(in: p.Input): p.Result, with the using clause in front of the explicit clause. f follows rule 2: the expansion is f[T](x: T)(using Ordering[T], n: Int), so the explicit arguments of the last call are given in that order (the witness first). Putting 3 first is a compile error:

Found:    (3 : Int)
Required: Ordering[Int]

Deferred givens

A trait cannot use a context bound on a type member directly the way a class does with a parameter, because the trait has no constructor to receive the instance. Scala 3.6 adds a deferred given: a given whose right-hand side is scala.compiletime.deferred. It can appear only in a trait. Each class that implements the trait supplies the implementation, and if the class does not write one, the compiler synthesizes it by searching for a given of the required type in the scope of the class (the class environment plus its parameters, but not its own members, which would cause recursive resolution).

type Element: Ord in a trait is exactly shorthand for the deferred given form:

//> using scala 3.10.0

package trailhead

import scala.compiletime.deferred

trait Ord[T]:
  def lt(a: T, b: T): Boolean

trait Collection:
  type Elem
  given Ord[Elem] = deferred          // same as `type Elem: Ord`
  def smallest(xs: List[Elem]): Elem =
    xs.reduce((a, b) => if summon[Ord[Elem]].lt(a, b) then a else b)

given Ord[Int] = (a, b) => a < b

class Heights extends Collection:
  type Elem = Int                     // given Ord[Int] found at the top level

@main def run(): Unit = println(Heights().smallest(List(8, 3, 5)))
3

The class Heights writes no implementation. The compiler searches for an Ord[Heights.this.Elem] outside the class, finds the top-level given Ord[Int], and uses it. A class may also take the instance as a parameter, with the same bound a trait cannot have:

//> using scala 3.10.0

package trailhead

trait Ord[T]:
  def lt(a: T, b: T): Boolean

given Ord[Int] = (a, b) => a < b

trait Sorted:
  type Element: Ord
  def items: List[Element]
  def isSorted: Boolean = items.zip(items.drop(1)).forall((a, b) => summon[Ord[Element]].lt(a, b))

// the class parameter `ord` implements the deferred given
class SortedSet[A: Ord as ord](val items: List[A]) extends Sorted:
  type Element = A

// an explicit implementation counts as an override
class SortedStrings(val items: List[String]) extends Sorted:
  type Element = String
  override given Ord[Element] = (a, b) => a.length < b.length

@main def run(): Unit =
  println(SortedSet(List(1, 2, 3)).isSorted)
  println(SortedStrings(List("a", "bb", "ccc")).isSorted)
true
true

The compiler expands SortedSet to a class with (using ord: Ord[A]) and override given Ord[Element] = ord. An explicit implementation is an override of a concrete definition, because the deferred given is concrete as far as the language is concerned, so it needs the override modifier. The deferred given reads like a compiler-supported macro whose implementation also affects every subclass.

Common mistakes

If the compiler finds no instance, the implementing class is rejected at its declaration, not at the use site:

//> using scala 3.10.0

package trailhead

trait Ord[T]
trait Sorted:
  type Element: Ord
class NoInstance extends Sorted:
  type Element = String
No given instance of type trailhead.Ord[NoInstance.this.Element] was found for inferring the implementation of the deferred given instance given_Ord_Element in trait Sorted

The synthesized member is named from the written type, given_Ord_Element. An explicit implementation therefore has to be written as override given Ord[Element] (or Ord[Elem], whatever the trait wrote). Writing it as override given Ord[String] produces a differently named member that does not override the deferred one, and the same error as above is reported even though an Ord[String] is in scope.

Abstract givens

Before 3.6 the way to require a given from a subclass was an abstract given: a named given without a right-hand side, as in given ord: Ord[T], which every class then had to define. Since 3.6 the reference calls abstract givens redundant and recommends migrating to deferred givens, which are implemented automatically. Abstract givens still compile on 3.10.

Context bounds in Scala 2

Scala 2 has the same bound with the same syntax, def maximum[T: Ordering](xs: List[T]), and it expands to an implicit parameter list. The braces form, the as name and the deferred given are Scala 3.6 additions. In Scala 2 the evidence has no usable name, so code that needs it writes implicitly[Ordering[T]], or declares the implicit parameter by hand:

def pick[T](xs: List[T])(implicit ord: Ordering[T]): T =
  xs.reduce((a, b) => if ord.lt(a, b) then a else b)

The migration rule is a difference worth knowing: in Scala 3.0 to 3.5 a context bound maps to an old-style implicit parameter, for which an argument can be passed either with (using ...) or as a plain argument. From 3.6 on it maps to a real context parameter, and the plain argument no longer compiles. Compiled with 3.3.8, f(1, 2)(Ordering.Int) works. With 3.10.0 it is rejected:

method f in package trailhead does not take more parameters

The fix is f(1, 2)(using Ordering.Int). The compiler can rewrite such calls with the 3.4-migration source setting and -rewrite.

Java and Kotlin

Java has no context parameters. Its closest feature is a bound on the type parameter, <T extends Comparable<T>>, which requires the type itself to implement the interface. A context bound does not touch the type: Ordering[Int] works though Int has no ordering method, and a second instance (the reverse ordering) can be passed for the same type. The Java version of that flexibility is an explicit Comparator<T> argument, which has to be passed at every call. Kotlin is similar: the upper bound T : Comparable<T> fixes the instance to the type, and a Comparator<T> is passed by hand otherwise. In Scala the compiler passes the argument, and the same call still lets a caller override the choice with using.

Scala 3.9 LTS versus 3.10

  • Context bounds, as names, braces and deferred givens all arrived in 3.6 and behave the same on 3.9 and 3.10. The named, aggregate and deferred examples above gave identical output with 3.9.0.

  • On the 3.3 LTS line only the basic bound T: Ordering and the chained T: A: B forms exist; as, braces and type Element: Ord in a trait do not compile (checked with 3.3.8).

Where the books are outdated

The Scala 3 Book chapter on context bounds describes only the basic form. It predates the as clause, the braces syntax and deferred givens, so those are only in the Reference.