Skip to Content
Course content

232: Interview Practice: Whiteboard Session on Functional Design

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Look, the whiteboard session is usually where the anxiety peaks. You know the syntax, you've built the projects, but suddenly you're standing in front of a board and an interviewer asks you to "design a system." The biggest mistake I see Scala developers make here is that they start drawing UML diagrams or thinking in terms of "Managers" and "Services"—the old OOP baggage. In a functional interview, they aren't looking for a class hierarchy; they're looking for a data pipeline.

Think of functional design like a professional kitchen assembly line. You don't have one "Chef" object that holds the state of the entire meal and mutates it as they go. Instead, you have distinct stations. One person chops the vegetables (Input A → Output B), another sears the protein (Input B → Output C), and another plates the dish. Each station is a pure function. If the vegetable chopper messes up, the protein searer doesn't even get the ingredients; the process stops, and we handle the "error" (the spoiled vegetable) immediately. The "recipe" is simply the composition of these stations.

Mapping the Kitchen to your Code

When you're at that whiteboard, I want you to translate that assembly line directly into these three functional pillars:

  • The Ingredients (ADTs): Instead of generic classes, use Algebraic Data Types. Your "ingredients" should be precise. If a payment can be Pending, Completed, or Failed, don't use a String or an Int. Use a sealed trait. This forces you to handle every case, and the interviewer will love seeing that you're leveraging the type system for correctness.
  • The Stations (Pure Functions): Each step of your logic should be a function that takes a value and returns a value. No side effects. No updating a database in the middle of a calculation. If you need to change something in the real world, that happens at the very end of the pipeline.
  • The Quality Check (The Either Monad): In a kitchen, if a steak is burnt, you don't just keep plating it and hope the customer doesn't notice. You stop. In Scala, this means wrapping your returns in Either[Failure, Success]. It turns your pipeline into a railway: as long as things are Right, they keep moving forward. The moment something is Left, it bypasses all remaining stations and goes straight to the error handler.

Designing a Subscription Billing Flow

Let's say the interviewer asks you to design a system that processes monthly subscription renewals. Instead of creating a SubscriptionManager class with a process() method that updates a database, sketch it out as a flow of data. I'd start by defining the domain:

sealed trait BillingError
case object CardExpired extends BillingError
case object InsufficientFunds extends BillingError
case object UserAccountDisabled extends BillingError

case class Subscription(id: UUID, userId: UUID, planPrice: BigDecimal)
case class PaymentReceipt(transactionId: String, amount: BigDecimal)

Now, instead of one giant method, I'd write the "stations." Notice how each one is tiny and does exactly one thing:

def validateAccount(sub: Subscription): Either[BillingError, Subscription] = ???
def calculateTax(sub: Subscription): Either[BillingError, (Subscription, BigDecimal)] = ???
def chargeCard(sub: Subscription, total: BigDecimal): Either[BillingError, PaymentReceipt] = ???

The "magic" happens when you compose them. In the interview, tell them: "I'm going to treat this as a linear transformation of data." You can use a for-comprehension to glue these together. It reads like a story: first we validate, then we tax, then we charge. If any step fails, the whole thing short-circuits.

The Trap of the Mutable Variable

Here is a warning: avoid the temptation to use a var on the whiteboard to "keep track" of the state as you move through the process. It's a subconscious habit from Java or Python. The moment you write var currentStatus = "Pending", you've signaled that you're thinking imperatively.

Instead, pass the state forward. If you need to carry extra information (like the tax amount) to the next function, return a Tuple or a small case class. It feels more verbose at first, but it proves you understand how to maintain referential transparency. That's the "senior" signal interviewers are hunting for.




📋 Practical Task

Implementing a Functional Discount Engine for E-commerce

You are tasked with designing the logic for a checkout discount system. The system must take a Cart and apply a series of rules. If any rule fails (e.g., the coupon is expired), the process should stop and return a specific error.

Requirements:

  • Define a sealed trait DiscountError with cases for ExpiredCoupon and MinimumSpendNotMet.
  • Define a Cart case class containing a list of items and a couponCode: Option[String].
  • Create three distinct functions:
    1. verifyCoupon: Checks if the coupon is valid (return Either).
    2. applyPercentageDiscount: Deducts 10% from the total (return Either).
    3. verifyMinimumSpend: Ensures the final total is still above $10.00 (return Either).
  • Compose these functions using a for-comprehension in a main calculateFinalTotal function.

Goal: Your final implementation should ensure that no mutable state is used and that the flow of data is a pure pipeline from Cart to a final BigDecimal total.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.