0Pricing
Scala for Backend Engineering & Functional Programming · Lesson

Source, Flow, and Sink

The streaming building blocks.

Source, Flow, and Sink is a free Scala for Backend Engineering & Functional Programming lesson on CoddyKit — lesson 1 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.

The Three Building Blocks

Akka Streams models a data pipeline as a graph of processing stages. The three core linear stages are Source (produces elements), Flow (transforms them), and Sink (consumes them).

A Source has one output, a Sink has one input, and a Flow has exactly one input and one output. Wiring them together describes what should happen, not when.

Defining a Source

A Source[Out, Mat] emits elements of type Out and exposes a materialized value of type Mat. The simplest sources come from in-memory collections or ranges.

Until the stream is run, a Source is just an immutable blueprint that can be reused freely.

import akka.stream.scaladsl.Source

val numbers: Source[Int, akka.NotUsed] =
  Source(1 to 100)

val single: Source[String, akka.NotUsed] =
  Source.single("hello")

Defining a Sink

A Sink[In, Mat] consumes elements of type In. The materialized value often captures the result of consumption, such as a Future that completes when the stream finishes.

Sink.foreach runs a side effect per element; Sink.fold accumulates a single result.

import akka.stream.scaladsl.Sink
import scala.concurrent.Future

val printSink: Sink[Int, Future[akka.Done]] =
  Sink.foreach(println)

val sumSink: Sink[Int, Future[Int]] =
  Sink.fold(0)(_ + _)

Defining a Flow

A Flow[In, Out, Mat] sits between a Source and a Sink, transforming each element. Flows are reusable on their own and can be composed before being attached to any endpoint.

Here a Flow doubles integers and converts them to strings.

import akka.stream.scaladsl.Flow

val doubleToString: Flow[Int, String, akka.NotUsed] =
  Flow[Int]
    .map(_ * 2)
    .map(n => s"value=$n")

Connecting Source to Sink

The via operator attaches a Flow to a Source, and to attaches a Sink. Connecting a Source directly to a Sink with to produces a RunnableGraph: a closed, runnable blueprint.

No elements move yet; this only describes the topology.

import akka.stream.scaladsl.{Source, Sink, RunnableGraph}

val graph: RunnableGraph[akka.NotUsed] =
  Source(1 to 10).to(Sink.foreach(println))

via: Inserting a Flow

Use via to splice a Flow into the pipeline. A Source.via(flow) yields a new Source whose output type matches the Flow's output.

Chaining via calls lets you build long transformation pipelines from small, testable Flow pieces.

val pipeline =
  Source(1 to 10)
    .via(Flow[Int].filter(_ % 2 == 0))
    .via(Flow[Int].map(_ * 10))
    .to(Sink.foreach(println))

Type Safety Across Stages

The compiler enforces that the output type of each stage matches the input type of the next. A Source[Int] cannot connect to a Sink[String] without an intervening Flow that converts the type.

This static checking catches pipeline wiring mistakes before runtime.

// Source[Int] -> Flow[Int, String] -> Sink[String]
val ok =
  Source(1 to 3)
    .via(Flow[Int].map(_.toString))
    .to(Sink.foreach[String](println))

Materialized Values

Each blueprint carries a materialized value: a handle produced when the stream runs. A Sink.fold materializes a Future of the result. By default, combining stages keeps the leftmost materialized value (NotUsed for plain sources).

Use toMat and Keep to select which side's value you want.

import akka.stream.scaladsl.Keep
import scala.concurrent.Future

val g: RunnableGraph[Future[Int]] =
  Source(1 to 100)
    .toMat(Sink.fold(0)(_ + _))(Keep.right)

Reusable Components

Because Sources, Flows, and Sinks are immutable values, you can define them once and reuse them across many pipelines. This encourages a library of small, named processing stages.

A Flow defined for parsing can be inserted into both a file pipeline and an HTTP pipeline.

val parse: Flow[String, Int, akka.NotUsed] =
  Flow[String].map(_.trim.toInt)

val fromFile  = lines.via(parse)
val fromHttp  = requestBody.via(parse)

Common Source Constructors

Akka Streams ships many Source factories: Source.single, Source.repeat, Source.tick for timed emission, Source.future from a Future, and Source.empty.

Choosing the right constructor makes the intent of a data producer explicit.

import scala.concurrent.duration._

val ticks  = Source.tick(0.seconds, 1.second, "tick")
val onceF  = Source.future(scala.concurrent.Future.successful(42))
val forever = Source.repeat("x")

Common Sink Constructors

Likewise, sinks include Sink.head (first element as a Future), Sink.seq (collect all into a Seq), Sink.ignore (drain and discard), and Sink.last.

For pipelines that feed a result back to your code, Sink.seq and Sink.fold are the most common.

import scala.concurrent.Future

val collect: Sink[Int, Future[Seq[Int]]] = Sink.seq
val firstOne: Sink[Int, Future[Int]]    = Sink.head
val drain: Sink[Int, Future[akka.Done]] = Sink.ignore

Quick Check

Test your understanding of the linear stage types.

Recap

You learned the three linear building blocks: Source produces, Flow transforms, and Sink consumes. They are immutable, reusable blueprints connected with via and to.

Connecting a Source to a Sink yields a RunnableGraph that carries a materialized value but does not move any data until run. Next you will transform streams with richer operators.

Frequently asked questions

Is the “Source, Flow, and Sink” lesson free?

Yes — the full text of “Source, Flow, and Sink” 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 “Source, Flow, and Sink”?

The streaming building blocks. 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 1 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Source, Flow, and Sink” 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. Source, Flow, and Sink
  2. Transforming Streams
  3. Backpressure
  4. Running a Pipeline
← Back to Scala for Backend Engineering & Functional Programming