LLD Dojo

Syllabus · F5

Clock management and finishing

The idea

Clock management — finishing what you start

You walk in with a blank page, and a clock that starts the moment you sit down, and you get one hour for the whole round. By minute 50 you have a design sketch and code that is half-there. By minute 55 you start testing it. By minute 58 you realize the tests will not compile, because your main method does not exist yet. That is exactly where the round ends, on something that almost worked, and "almost" earns the same score as nothing at all.

The interview measures exactly three things: does it compile, does it have a runnable main, and do the tests pass. It measures those three and nothing softer, because none of them needs a judgment call from the interviewer. Either the class compiles or it does not. Either main exists or it does not. A missing main is an automatic zero on functionality, because interviewers run it first, before they read a single line you wrote by hand. A clock that runs out before every test passes earns its own failure tag for the same underlying reason: nobody can score a design they never got to see run.

None of the three checks reads the design itself. A design with a real seam for a second pricing rule, described out loud but never typed, scores the same zero on functionality as a design with no seam at all. Functionality here is measured on what ran, never on what was only explained. That is uncomfortable to hear after spending forty minutes getting the design right, and it is still the rule the round grades by.

That way of measuring forces a choice between two different ways to lose time. You can ship a thin slice that works, or a thick slice that does not. Interviewers would rather see a parking lot that parks one type of vehicle, behind a fully passing test suite, than one that handles five vehicle types and crashes on startup. The first design proves everything it claims about itself. The second proves nothing at all, regardless of how much of the prompt it appears to cover.

You manage the clock by committing to a scope early, and by protecting the last ten minutes for compilation and a walkthrough rather than for new features. Write a main method that demonstrates the happy path before you touch anything else, so there is always something runnable on the screen. Get the base suite fully green before you ever attempt the curveball, because a green base suite is worth more by itself than a half-handled curveball bolted onto a shaky one. If the curveball drags on and the clock is closing, step back from it. Code that compiles and runs scores higher than clever code that does not, every single time, and that ordering is not a matter of taste.

The three failure tags that live in this lesson, no-main, ran-out-of-clock, and suite-not-green, all point at execution discipline rather than at design. Each one describes a candidate who designed correctly and still did not reach the finish line before the round ended. That is a different failure from the one the rest of this syllabus spends most of its time on.


Worked walkthrough

Clock Management: Execution Discipline in an Interview

Run it first

Open the reference solution for the rate-limiter problem. Look at Main.java. Read the entire main method. That is your target: a method that is runnable, clear, and shows the core flow without being overly simple.

Why the three failures route here

All three defect tags point to finishing problems, not design problems:

What the grader actually checks

Phase 3 (grade-phase3.mjs, line 22 onwards) checks exactly three things:

const main = hasMain(source)           // Line 32: does a main method exist?
const withinClock = elapsedMs <= 30*60*1000  // Did you finish within 30 minutes?
const level = functionalityLevel({ hasMain: main, ... })  // Line 34

A missing main zeros functionality. You cannot recover that. The grader never overrides it. Line 90 of the standard states it plainly: "A missing driver caps this dimension at 0 — interviewers run it first."

The clock: Phase 2 vs Phase 3

You get one clock per phase. Running out in phase 3 doesn't matter if you passed phase 2. But it shows poor pacing — you either took too long on design or didn't ruthlessly scope the implementation.

The threshold

You're done when:

  1. Your code compiles with zero errors
  2. A public static void main(String[] args) method exists and can run
  3. All base-suite tests pass (look at summarise() in the test framework)
  4. You still have time left on the clock (ideally 3-5 minutes)

Common moves that fail the clock

What a main method shows

It's not about cleverness. It's about demonstrating that you understand what you built and that the code runs. For a parking lot, main might:

ParkingLot lot = new ParkingLot(10);
lot.park(new Vehicle("KA01", VehicleType.CAR));
System.out.println("Fee: " + lot.fee(VehicleType.CAR));

That's it. It shows that instantiation works, a method call works, and the result makes sense. Tests show correctness; main shows that the thing boots.

If you're running out of time

At minute 28:

  1. Ask yourself: does my code compile? Is main there? Do the base tests pass?
  2. If yes to all three, you've earned level 2 on functionality. Done.
  3. If no, use these last minutes to fix ONE critical issue in this order: compile → main → test.
  4. Compilation is mandatory. A missing main is the next priority. Failing tests are last.
  5. Do not start a new feature. Do not refactor. Do not try the curveball.

This is not giving up. This is pacing like an engineer.


Worked source

The 1 file of the worked design

Every file below is the one the app opens, verbatim. This is the part worth reading slowly: the prose above argues for a shape, and these are the lines that have it.

worked/Main.java147 lines

/**
 * Clock Management: Finishing What You Start
 *
 * This main method walks through the rate-limiter implementation to show:
 *   1. How to design for time constraints
 *   2. What a complete main method looks like
 *   3. When to step back and accept the scope
 *
 * See NOTES.md for the strategy of clock management in a timed interview.
 */

import java.time.Clock;
import java.util.HashMap;
import java.util.Map;

public class Main {

  /**
   * A token-bucket rate limiter. The core design: one class, one interface.
   * This is a scope choice — not handling distribution, persistence, or
   * background refill. All of these reshape the design; we explicitly forbade
   * them to finish on time.
   */
  interface TokenBucket {
    boolean allowRequest(String clientId);
  }

  /**
   * Simple token-bucket implementation with demand-driven refill.
   * No background thread, no persistence. Single-process only.
   *
   * Why this design within the clock:
   *   - Refill happens on-demand when a request comes in
   *   - No scheduler thread to manage
   *   - No state to persist
   *   - One file, under 100 lines
   */
  static class RateLimiter implements TokenBucket {
    private final Map<String, ClientState> clients = new HashMap<>();
    private final int tokensPerWindow;
    private final long windowMs;
    private final Clock clock;

    RateLimiter(int tokensPerWindow, long windowMs, Clock clock) {
      this.tokensPerWindow = tokensPerWindow;
      this.windowMs = windowMs;
      this.clock = clock;
    }

    @Override
    public boolean allowRequest(String clientId) {
      long now = clock.millis();
      ClientState state = clients.get(clientId);

      // New client or window expired.
      if (state == null || now >= state.windowEnd) {
        state = new ClientState(now + windowMs, tokensPerWindow);
        clients.put(clientId, state);
      }

      // Consume a token if available.
      if (state.tokensRemaining > 0) {
        state.tokensRemaining--;
        return true;
      }
      return false;
    }

    /**
     * Why this is a static inner class, not a separate file:
     * Scope decision. We're shipping on time. If the interview
     * demanded distributed state, we'd extract this. It doesn't.
     */
    private static class ClientState {
      long windowEnd;
      int tokensRemaining;

      ClientState(long windowEnd, int tokensRemaining) {
        this.windowEnd = windowEnd;
        this.tokensRemaining = tokensRemaining;
      }
    }
  }

  // Entry point — the mandatory part. Never skip this.
  public static void main(String[] args) {
    System.out.println("Rate Limiter: Clock Management");
    System.out.println("=".repeat(60));

    // Scenario 1: Single client, within quota.
    System.out.println("\n=== Scenario 1: Within Quota ===");
    Clock fixedClock = Clock.fixed(
      java.time.Instant.parse("2025-01-01T00:00:00Z"),
      java.time.ZoneId.of("UTC")
    );
    TokenBucket limiter = new RateLimiter(5, 60_000, fixedClock);

    String client = "client-001";
    for (int i = 1; i <= 6; i++) {
      boolean allowed = limiter.allowRequest(client);
      System.out.println("Request " + i + ": " + (allowed ? "ALLOWED" : "DENIED"));
    }
    // Expect: first 5 allowed, 6th denied.

    // Scenario 2: Multiple clients, independent quotas.
    System.out.println("\n=== Scenario 2: Multiple Clients ===");
    limiter = new RateLimiter(3, 60_000, fixedClock);
    String client1 = "user-a";
    String client2 = "user-b";

    System.out.println("User A, request 1: " + limiter.allowRequest(client1)); // true
    System.out.println("User B, request 1: " + limiter.allowRequest(client2)); // true
    System.out.println("User A, request 2: " + limiter.allowRequest(client1)); // true
    System.out.println("User A, request 3: " + limiter.allowRequest(client1)); // true
    System.out.println("User A, request 4: " + limiter.allowRequest(client1)); // false
    System.out.println("User B, request 2: " + limiter.allowRequest(client2)); // true
    System.out.println("User B, request 3: " + limiter.allowRequest(client2)); // true
    System.out.println("User B, request 4: " + limiter.allowRequest(client2)); // false

    // Scenario 3: Window expiry (simplified).
    System.out.println("\n=== Scenario 3: Window Expiry ===");
    System.out.println("At design time, you decided: 'windows don't overlap, only one client.'");
    System.out.println("This scope choice let us finish the implementation in time.");
    System.out.println("We didn't add concurrency, persistence, or background threads.");

    System.out.println("\n" + "=".repeat(60));
    System.out.println("The Clock Test: What to Ship");
    System.out.println("=".repeat(60));

    System.out.println("\nYou have 30 minutes. At minute 28:");
    System.out.println("  ✓ Code compiles? Check.");
    System.out.println("  ✓ main method exists and runs? Check.");
    System.out.println("  ✓ Base suite 100% green? Check.");
    System.out.println("  → Level 2 earned. Clock still running, level 3 within reach.");

    System.out.println("\nIf the curveball (add metrics, handle concurrency, etc.) drags:");
    System.out.println("  1. Stop at minute 28. You've already won.");
    System.out.println("  2. Commit the green state.");
    System.out.println("  3. Do not rewrite half your code chasing level 3.");

    System.out.println("\nWhy this matters in real rounds:");
    System.out.println("  - Interviewers run main first. No main, no score.");
    System.out.println("  - A full test suite beating the clock shows execution discipline.");
    System.out.println("  - Rushed, broken code costs more than careful, finished code.");
    System.out.println("  - You'll interview again. Prove you can ship.");
  }
}

The faded stage is not here, on purpose

In the app, the third stage of this lesson is a written completion: a prompt with blanks, answered in prose and then graded against what the reference extraction expects. The grading needs the app, so this page stops at the worked walkthrough.

Run the app for the drill: it is the download in the header, and it works offline once unpacked.

← F4 · Decision defence and trade-off articulation G1 · Reading and drawing a class diagram under time pressure →

← all lessons