Scala Lesson 42 – Futures | Dataplexa

Futures in Scala

In this lesson, you will learn about Futures in Scala. Futures are one of the most important abstractions for writing asynchronous and non-blocking code.

Instead of manually managing threads, Scala Futures allow you to run computations in the background and handle their results later, in a clean and safe way.


What Is a Future?

A Future represents a value that may not be available yet, but will be computed at some point in the future.

You can think of a Future as a placeholder for a result that is being calculated asynchronously.

  • The computation starts immediately
  • The result is delivered later
  • Your program does not block while waiting

Why Use Futures?

Futures help solve common concurrency problems:

  • Blocking threads while waiting for results
  • Manually synchronizing shared data
  • Complex callback-based code

With Futures, you can write asynchronous code that looks clear, readable, and composable.


Importing Future and ExecutionContext

To use Futures, you must import:

  • scala.concurrent.Future
  • scala.concurrent.ExecutionContext

The ExecutionContext manages the thread pool that runs the Future.

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

Creating a Future

You create a Future by passing a block of code to the Future constructor.

val futureResult = Future {
  Thread.sleep(1000)
  42
}

This computation runs asynchronously, without blocking the main thread.


Non-Blocking Behavior

The main advantage of Futures is that they do not block program execution.

println("Before Future")

Future {
  Thread.sleep(1000)
  println("Inside Future")
}

println("After Future")

The output order is not guaranteed because the Future runs concurrently.


Handling Future Results with onComplete

To react when a Future completes, use onComplete.

futureResult.onComplete {
  case scala.util.Success(value) =>
    println(s"Result: $value")

  case scala.util.Failure(error) =>
    println(s"Error: ${error.getMessage}")
}

This callback executes once the Future finishes.


Success and Failure

A Future can end in one of two states:

  • Success – the computation completes normally
  • Failure – an exception occurs

Scala represents these states using Success and Failure.


Transforming Futures with map

You can transform the result of a Future using map.

val doubled = futureResult.map(result => result * 2)

This creates a new Future without blocking the original one.


Chaining Futures with flatMap

When one Future depends on another, use flatMap.

val chained = futureResult.flatMap { value =>
  Future(value + 10)
}

This allows complex asynchronous workflows.


for-Comprehensions with Futures

Scala provides for-comprehensions to make chained Futures readable.

val combined = for {
  a <- Future(10)
  b <- Future(20)
} yield a + b

This syntax is clean and avoids deeply nested callbacks.


Blocking (and Why to Avoid It)

Scala provides Await to block and wait for a Future, but this should be avoided in production code.

import scala.concurrent.Await
import scala.concurrent.duration._

val result = Await.result(futureResult, 2.seconds)

Blocking defeats the purpose of asynchronous programming and reduces scalability.


Common Use Cases for Futures

  • Calling web APIs
  • Database queries
  • File and network I/O
  • Background computations

📝 Practice Exercises


Exercise 1

Create a Future that returns a string after a delay.

Exercise 2

Transform a Future result using map.

Exercise 3

Combine two Futures using a for-comprehension.


✅ Practice Answers


Answer 1

Future {
  Thread.sleep(500)
  "Hello Scala"
}

Answer 2

Future(10).map(_ * 3)

Answer 3

for {
  x <- Future(5)
  y <- Future(7)
} yield x + y

What’s Next?

In the next lesson, you will learn about Promises and how they work together with Futures to control asynchronous results.