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.ignoreQuick 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
- Source, Flow, and Sink
- Transforming Streams
- Backpressure
- Running a Pipeline