0Pricing
Scala for Backend Engineering & Functional Programming · Lesson

Defining Instances

Implicit instances.

Defining Instances is a free Scala for Backend Engineering & Functional Programming lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Scala for Backend Engineering & Functional Programming learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Defining Instances

An instance tells the compiler how a type class behaves for a specific type. In Scala, instances are usually marked implicit (Scala 2) or declared with given (Scala 3) so they are found automatically.

An implicit val Instance

For a simple type class, an implicit val is enough. The compiler finds it when a function needs a Show[Int].

trait Show[A] { def show(a: A): String }

object Main {
  implicit val intShow: Show[Int] = (a: Int) => s"Int=$a"

  def display[A](a: A)(implicit s: Show[A]): String = s.show(a)

  def main(args: Array[String]): Unit = {
    println(display(123))
  }
}

implicitly: Summon an Instance

implicitly[Show[Int]] asks the compiler to fetch the in-scope instance. It is useful for testing that an instance exists or for accessing it directly.

trait Show[A] { def show(a: A): String }

object Main {
  implicit val intShow: Show[Int] = (a: Int) => s"<$a>"

  def main(args: Array[String]): Unit = {
    val s = implicitly[Show[Int]]
    println(s.show(8))
  }
}

Instances for Custom Types

Define an instance for your own case class. The data class stays clean; the formatting lives in the instance.

trait Show[A] { def show(a: A): String }

case class Point(x: Int, y: Int)

object Main {
  implicit val pointShow: Show[Point] = (p: Point) => s"(${p.x}, ${p.y})"

  def display[A](a: A)(implicit s: Show[A]): String = s.show(a)

  def main(args: Array[String]): Unit = {
    println(display(Point(3, 4)))
  }
}

implicit def for Generic Instances

When an instance depends on another instance, use implicit def. Here a Show[List[A]] is built from a Show[A].

trait Show[A] { def show(a: A): String }

object Main {
  implicit val intShow: Show[Int] = (a: Int) => a.toString

  implicit def listShow[A](implicit s: Show[A]): Show[List[A]] =
    (xs: List[A]) => xs.map(s.show).mkString("[", ", ", "]")

  def display[A](a: A)(implicit s: Show[A]): String = s.show(a)

  def main(args: Array[String]): Unit = {
    println(display(List(1, 2, 3)))
  }
}

Instances in the Companion Object

Placing instances in the type class's companion object means they are found automatically without any import. This is the recommended home for default instances.

trait Show[A] { def show(a: A): String }

object Show {
  implicit val intShow: Show[Int] = (a: Int) => s"i:$a"
  implicit val strShow: Show[String] = (a: String) => s"s:$a"
}

object Main {
  def display[A](a: A)(implicit s: Show[A]): String = s.show(a)

  def main(args: Array[String]): Unit = {
    println(display(5))
    println(display("hi"))
  }
}

Implicit Scope and Priority

The compiler searches several places for an instance: the local/imported scope first, then the companion objects of the types involved. If two instances are equally specific, you get an ambiguous implicit error.

Context Bound Shorthand

The syntax def f[A: Show](a: A) is a context bound: it means there must be an implicit Show[A] in scope. Inside, retrieve it with implicitly.

trait Show[A] { def show(a: A): String }

object Main {
  implicit val intShow: Show[Int] = (a: Int) => s"n=$a"

  def display[A: Show](a: A): String = implicitly[Show[A]].show(a)

  def main(args: Array[String]): Unit = {
    println(display(77))
  }
}

A Summoner Helper

Libraries add an apply method to the companion as a convenient summoner: Show[Int] returns the instance. It is cleaner than implicitly.

trait Show[A] { def show(a: A): String }

object Show {
  def apply[A](implicit s: Show[A]): Show[A] = s
  implicit val intShow: Show[Int] = (a: Int) => s"=$a"
}

object Main {
  def main(args: Array[String]): Unit = {
    println(Show[Int].show(10))
  }
}

Avoiding Orphan Instances

An orphan instance is one defined neither with the type class nor with the type. They are legal but can cause inconsistent behavior across imports. Prefer companion-object instances to keep them coherent.

Putting It Together

A complete program: companion-object instances, a derived list instance, and a summoner.

trait Show[A] { def show(a: A): String }

object Show {
  def apply[A](implicit s: Show[A]): Show[A] = s
  implicit val intShow: Show[Int] = _.toString
  implicit def listShow[A](implicit s: Show[A]): Show[List[A]] =
    (xs: List[A]) => xs.map(s.show).mkString(", ")
}

object Main {
  def main(args: Array[String]): Unit = {
    println(Show[List[Int]].show(List(4, 5, 6)))
  }
}

Quick Check

Test your knowledge of defining instances.

Recap

You learned to define instances:

  • implicit val for simple instances, implicit def for derived ones.
  • Summon with implicitly or a companion apply.
  • Use context bounds [A: Show] as shorthand.
  • Put instances in companion objects to avoid orphans.

Frequently asked questions

Is the “Defining Instances” lesson free?

Yes — the full text of “Defining Instances” is free to read here on the web, and the Scala for Backend Engineering & Functional Programming course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Scala for Backend Engineering & Functional Programming course, upgrade to CoddyKit PRO.

What will I learn in “Defining Instances”?

Implicit instances. You practise Scala for Backend Engineering & Functional Programming with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Scala for Backend Engineering & Functional Programming?

No prior experience is required. Scala for Backend Engineering & Functional Programming on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Defining Instances” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Scala for Backend Engineering & Functional Programming lesson?

Yes. Every Scala for Backend Engineering & Functional Programming lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. The Type Class Pattern
  2. Defining Instances
  3. Common Type Classes
  4. Type Class Derivation
← Back to Scala for Backend Engineering & Functional Programming