SevyDevy

Guide · System design

UML for LLD Interviews: Class and Sequence Diagrams

24 min readUpdated By Saket Bhatnagar

Researched and drafted with AI, reviewed and edited by Saket Bhatnagar.

In short

  • A UML class diagram is a drawing of the classes in a design: what each one stores, what it can do, and how the classes connect. It is the main drawing in a low-level design (LLD) interview, where you design classes and then code them.
  • UML, the Unified Modeling Language, has 14 diagram types. LLD rounds mostly need two: the class diagram for structure, and the sequence diagram, which shows the calls for one request in time order.
  • Class diagrams need a short list of symbols. There is the three-part class box, visibility marks such as + for public, multiplicity numbers such as 1..* (one or more), and six kinds of line.
  • A sequence diagram catches what the class diagram hides. In our running example it exposes a missing class, a missing release method and a race condition, where two customers grab the same slot at once.
  • Beginners can stop at the line that says so, after the mistakes section. Engineers get where UML and Java disagree and the slot race tested in Java 21 and the PostgreSQL 16 database. They also get both diagrams in Mermaid, a plain-text format that tools turn into drawings.

What goes wrong when you draw a UML class diagram in an LLD interview?

You can run out of time. You try to draw a complete UML class diagram, with every field and arrow in perfect notation, and the code never starts. The interviewer needed a readable sketch and some working classes.

Picture a common setup. You get a 45-minute LLD round: design a booking app for box-cricket turfs, the small fenced pitches rented by the hour. A customer picks a turf, a court and a one-hour slot, pays Rs 1,200, and gets a confirmation SMS. The setup and its numbers are made up.

A first attempt goes like this. You add every class you can think of, every getter and setter, and a line between almost every pair of boxes. After 28 minutes the drawing has 14 boxes and 22 lines, and no Java exists yet.

Then the interviewer asks what happens if two people book the same slot at the same moment. Nothing on the page answers it, because a class diagram shows structure, not behaviour.

This guide is the smaller set that fixes it: a small class diagram, one sequence diagram, then code. The turf booking app runs through every section, and the three Java blocks, pasted into one file, compile and run on Java 21.

What is UML, and how much of it do you need for LLD interviews?

UML, the Unified Modeling Language, is a standard set of drawing rules for software designs. For LLD interviews you need only a small part of it: class diagrams and sequence diagrams.

UML is managed by the Object Management Group (OMG), a standards body. As of 19 October 2026, the newest formal version on the OMG site is UML 2.5.1, dated December 2017. Its table of contents lists sections past page 500.

UML 2 defines 14 diagram types, sorted into two families. Structure diagrams show the parts of a system and how they connect, apart from time. Behaviour diagrams show what happens over time, such as calls between objects.

An LLD round tests both sides, so you take one diagram from each family. The class diagram covers structure. The sequence diagram covers behaviour for one important request.

Martin Fowler, a software author who has written about UML since the early 2000s, names three ways to use it. They are sketch, blueprint and programming language. An interview is a sketch. His sound-bite for sketches is "comprehensiveness is the enemy of comprehensibility."

In plain words, a drawing that shows everything is a drawing nobody can read. Fowler calls selectivity the essence of sketching, and names the whiteboard as a common medium for it. Show only the classes and links worth talking about.

What is a UML class diagram?

A UML class diagram is a drawing of the classes in your design, the data and methods each class has, and the lines that show how classes relate. It describes the design, not any single run of it.

Think of a kho-kho team sheet. Kho-kho is a chasing game where seated chasers take turns to run. Before a match, the sheet lists each player and their role: who starts as the active chaser, who sits in the line, and which batch of three defends first.

Map it part by part. A role on the sheet, like chaser, is a class. Each player who fills that role is an object of that class. What every chaser must know and do are the class's attributes and operations. Two roles that work together, like captain and vice-captain, are like two classes joined by a line. Where the picture breaks: a sheet has a few players per role, while one class can have thousands of objects at run time.

The sheet says which roles exist and what each must do. It does not say what happens in the third chase. What happens during one booking is a different drawing, the sequence diagram, which comes later.

The three-part class box

Each class is a rectangle split into three parts. The top part holds the class name, like Slot. The middle part holds attributes, the data the class keeps. The bottom part holds operations, the methods it offers.

UML writes an attribute as name, a colon, then the type, like "start : LocalTime". An operation looks like "pay(amountInRupees : int) : boolean". The part after the last colon is the return type.

Visibility marks

A visibility mark is the symbol before each attribute or operation that says who may use it. UML has four, and each maps to a Java choice.

  • The plus sign (+) means public: any class can use it. Java keyword: public.
  • The minus sign (-) means private: only this class can use it. Java keyword: private.
  • The hash sign (#) means protected: subclasses can use it. Java keyword: protected, which also opens the member to its whole package.
  • The tilde (~) means package: classes in the same package can use it. Java has no keyword for this; you write no modifier at all.

In an interview, one habit covers most cases. Mark fields with a minus and the main methods with a plus. That shows encapsulation, which means hiding a class's data behind its methods.

Multiplicity

Multiplicity is the small number at the end of a line that says how many objects can sit on that side. 1 means exactly one, 0..1 means none or one, 1..* means one or more, and * means any number.

Example: one Turf has 1..* Courts, and each Court belongs to exactly 1 Turf. Writing these numbers forces real questions. Can a turf have zero courts? Can one booking cover two slots?

How do you show relationships in a UML class diagram?

You show relationships with different lines: a plain line for a link, a diamond for whole and part, and a hollow triangle for "is a kind of". Dashed lines show weaker links. Each line answers one question about two classes.

Association. An association is a solid line meaning objects of one class stay linked to objects of another, usually through a field. Example: a Booking keeps the Slot it reserves. An open arrowhead on one end shows which side can reach the other.

Composition. Composition is a strong whole-part link, drawn with a filled diamond at the whole's end. A part belongs to at most one whole at a time, and when the whole is deleted, its parts go with it.

Example: a Court makes no sense without its Turf. If the owner closes the turf, its courts disappear from the app too. So Turf to Court is composition. A part may still be removed before the whole is deleted, like a court taken out for repairs.

Aggregation. Aggregation is a weaker whole-part link, drawn with a hollow diamond, where the part can live on its own. Fowler notes it has no standard meaning, so many teams draw a plain association instead.

Generalization. Generalization is UML's word for inheritance: a solid line with a hollow triangle pointing at the parent class. Example: if CardPayment and UpiPayment share retry code, both can extend an abstract OnlinePayment class.

Realization. Realization means a class implements an interface. It is a dashed line with a hollow triangle pointing at the interface. Example: UpiPayment and CardPayment both implement the PaymentMethod interface.

Dependency. A dependency is a dashed arrow meaning one class uses another briefly but does not keep it. Example: a cancel method receives a RefundPolicy as a parameter and forgets it afterwards. The arrow points from the user to the thing used.

When you are unsure which line to draw, ask the questions in this chart, top to bottom. Follow the first Yes you reach.

Which line should I draw between A and B?

Ask in order and stop at the first Yes. Interfaces are the one extra case: draw realization for them.

Diagram connections:

  • Two classes, A and B to Is B a kind of A?
  • Is B a kind of A? to Generalization (Yes)
  • Is B a kind of A? to Does A own B's whole life? (No)
  • Does A own B's whole life? to Composition (Yes)
  • Does A own B's whole life? to Does A keep B in a field? (No)
  • Does A keep B in a field? to Association (Yes)
  • Does A keep B in a field? to Dependency (No)
  • Two classes, A and B: Example: Turf and Court
  • Is B a kind of A?: Could B stand in wherever A is used?
  • Generalization: Solid line, hollow triangle at A
  • Does A own B's whole life?: A creates B, and B goes when A goes
  • Composition: Filled diamond at A's end
  • Does A keep B in a field?: A remembers B after the method ends
  • Association: Solid line, multiplicity at both ends
  • Dependency: Dashed arrow: A only uses B briefly

The table puts all six relationships side by side, with the Java clue that tells you which one you have.

Class diagram relationships, with the turf booking example
RelationshipHow it looksJava clueTurf example
AssociationSolid line, optional open arrowheadA field of the other typeBooking keeps its Slot
AggregationSolid line, hollow diamond at the wholeA list of objects made elsewhereA Tournament lists Teams that exist on their own
CompositionSolid line, filled diamond at the wholeThe whole creates the parts and never hands them outTurf creates its Courts
GeneralizationSolid line, hollow triangle at the parentextendsUpiPayment extends OnlinePayment (not in our code)
RealizationDashed line, hollow triangle at the interfaceimplementsUpiPayment implements PaymentMethod
DependencyDashed line, open arrowheadA parameter or local variable onlycancel receives a RefundPolicy

Here is a better first sketch for the turf app. It has eight boxes instead of fourteen. Each line label names the relationship and its multiplicity.

Turf booking class diagram, as a sketch

Eight boxes, no getters or setters. Notice the one filled diamond, Turf to Court. The sequence diagram later adds SlotRepository.

Diagram connections:

  • Customer to Booking (Association, 1 to 0..*)
  • Turf to Court (Composition, 1 to 1..*)
  • Court to Slot (Association, 1 to *)
  • Booking to Slot (Association, * to 1)
  • UpiPayment to PaymentMethod (interface) (Realization)
  • BookingService to PaymentMethod (interface) (Association, keeps one)
  • BookingService to Booking (Dependency, creates)
  • Customer: - name, - phone
  • BookingService: + book(userId, slotId) : Optional Booking
  • Turf: - name; + addCourt(label)
  • Court: - label; created only by its Turf
  • Slot: - id, - priceInRupees, - held
  • Booking: - userId; keeps its Slot
  • PaymentMethod (interface): + pay(amountInRupees) : boolean
  • UpiPayment: One way to pay; CardPayment is another

Notice the multiplicity on Booking to Slot: many bookings can point to one slot over time, because a cancelled booking frees the slot for the next person. Writing the numbers down is what surfaces rules like this.

How does a class diagram map to Java code?

Each box becomes a class or interface, each attribute becomes a field, and each line becomes a field, an extends, an implements or a method parameter. If you can read the diagram, you can write the skeleton in minutes.

Here is the first part of one Java file, TurfDemo.java. It holds the imports for the whole file, the PaymentMethod interface, one class that implements it, and Turf with its courts. Most imports serve the booking code later in the same file.

java
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

interface PaymentMethod {                      // the «interface» box
    boolean pay(int amountInRupees);           // + pay(amountInRupees : int) : boolean
}

class UpiPayment implements PaymentMethod {    // realization: dashed line, hollow triangle
    private final boolean bankSaysYes;         // stand-in for a real UPI call
    UpiPayment(boolean bankSaysYes) { this.bankSaysYes = bankSaysYes; }
    public boolean pay(int amountInRupees) { return bankSaysYes; }
}

class Turf {
    private final String name;                 // - name : String
    private final List<Court> courts = new ArrayList<>();

    Turf(String name) { this.name = name; }

    void addCourt(String label) {              // the whole creates its parts
        courts.add(new Court(label));
    }

    List<String> courtLabels() {               // hands out copies of data, never a Court
        List<String> labels = new ArrayList<>();
        for (Court court : courts) labels.add(court.label);
        return labels;
    }

    static final class Court {                 // the part: only Turf can call new Court(...)
        private final String label;            // - label : String
        private Court(String label) { this.label = label; }
    }
}

The line that matters most is new Court(label) inside addCourt. Court's constructor is private, and Court is nested inside Turf, so only Turf can create courts. courtLabels hands out copies of the labels, never a Court.

Pause and predict: what happens if code outside Turf writes new Turf.Court("Z")? It does not compile. The Java compiler, javac, reports "Court(String) has private access in Court" on Java 21. That error enforces half of the filled diamond: only the whole can create its parts.

A common first draft returns the new Court from addCourt. That looks harmless, but it hands a part to any caller, who can keep it after the turf is gone. The diamond then promises something the code does not do.

The private keyword matches the minus sign in the diagram. None of the classes is public, so the file can have any name. javac only insists that a public class sits in a file named after it.

What is a sequence diagram in UML?

A sequence diagram shows the messages that objects send to each other, in time order, for one scenario. Time runs from top to bottom, and each object gets its own vertical line.

Go back to kho-kho. The team sheet lists roles, but a chase is a sequence. The active chaser touches a seated teammate on the back and says "kho". That teammate rises and becomes the new chaser.

Write down the order of the kho passes in one chase, and you have a sequence diagram. It shows who passed to whom, and in what order, for that chase only.

You need only a few parts to draw one:

  • Lifeline: a box with the object's name at the top and a dashed line running down. Example: a box for BookingService.
  • Synchronous message: a solid line with a filled arrowhead. The caller waits for the answer, like a normal Java method call.
  • Reply message: a dashed line back to the caller, often labelled with the returned value.
  • Asynchronous message: a solid line with an open arrowhead. The caller sends it and carries on without waiting.
  • Activation bar: a thin rectangle on a lifeline showing when that object is busy handling a call.

Now draw one sequence: a customer books the 7 pm slot on court A. Our diagram boxes cannot draw lifelines, so in the diagram below each box is one message, and its note says who sends it to whom. Follow the happy path and both failures.

Booking the 7 pm slot, message by message

One scenario, in time order. Notice the two ways to fail, and that the SMS step is the only one nobody waits for. Price Rs 1,200 (made up).

Diagram connections:

  • Ask to book 7 pm slot to Hold the slot
  • Hold the slot to Was the slot free?
  • Was the slot free? to Charge Rs 1,200 (Yes)
  • Was the slot free? to Tell customer it failed (No, already held)
  • Charge Rs 1,200 to Did payment go through?
  • Did payment go through? to Create the Booking (Yes)
  • Did payment go through? to Release the slot (No)
  • Create the Booking to Send confirmation SMS
  • Release the slot to Tell customer it failed
  • Ask to book 7 pm slot: Customer → BookingService
  • Hold the slot: BookingService → SlotRepository
  • Was the slot free?: SlotRepository → BookingService (reply)
  • Charge Rs 1,200: BookingService → UpiPayment
  • Did payment go through?: UpiPayment → BookingService (reply)
  • Create the Booking: BookingService → Booking
  • Send confirmation SMS: BookingService → Notifier (async)
  • Release the slot: BookingService → SlotRepository
  • Tell customer it failed: BookingService → Customer

What just happened? Drawing the messages found two gaps in the class sketch. No class could hold a slot safely, so SlotRepository appears. And a slot had no way to be released after a failed payment, so release appears.

A sequence diagram catches gaps like these because every arrow must land on a real operation. The hold step also raises the hardest question in the round: what if two customers hold at once?

How do you show if-else, loops and async calls in a sequence diagram?

You wrap the messages in a labelled box called a combined fragment: alt for if-else, opt for a plain if, and loop for repetition. Async calls use an open arrowhead instead of a filled one.

Each section of a fragment can carry a guard, a condition in square brackets like [slot free]. An alt fragment chooses between its sections, and an [else] section runs when no other guard holds. An opt section either happens or does not.

  • alt: split the box with a dashed line. Top part [slot free], bottom part [else]. Use it for the slot check and the payment check.
  • opt: one section only. Example: [customer has a coupon] apply the discount.
  • loop: repeat a group of messages. Example: loop [for each court] fetch free slots, when showing the day's grid.

The confirmation SMS is async: BookingService hands it to the Notifier and returns the booking without waiting. In the drawing, that arrow gets an open arrowhead and no reply arrow.

The second part of TurfDemo.java holds the small classes the sequence needs. Slot marks itself held with compareAndSet, a check-and-mark in one step that the engineer section tests. Notifier queues the SMS on a background thread.

java
class Slot {
    private final String id;
    private final int priceInRupees;
    private final AtomicBoolean held = new AtomicBoolean(false);

    Slot(String id, int priceInRupees) { this.id = id; this.priceInRupees = priceInRupees; }
    String id() { return id; }
    int priceInRupees() { return priceInRupees; }
    boolean tryHold() { return held.compareAndSet(false, true); }  // check and mark in one step
    void release() { held.set(false); }
}

class SlotRepository {
    private final Map<String, Slot> slots = new ConcurrentHashMap<>();
    void add(Slot slot) { slots.put(slot.id(), slot); }
    Slot holdSlot(String slotId) {             // null if the slot is unknown or already held
        Slot slot = slots.get(slotId);
        return (slot != null && slot.tryHold()) ? slot : null;
    }
    void release(String slotId) { slots.get(slotId).release(); }
}

record Booking(String userId, Slot slot) {}    // association: a Booking keeps its Slot

class Notifier {
    private final ExecutorService worker = Executors.newSingleThreadExecutor();
    final List<String> sent = new CopyOnWriteArrayList<>();
    void sendConfirmationAsync(Booking b) {    // queues the work and returns at once
        worker.submit(() -> sent.add("SMS to " + b.userId() + ": " + b.slot().id() + " confirmed"));
    }
    void shutdown() throws InterruptedException {
        worker.shutdown();
        worker.awaitTermination(5, TimeUnit.SECONDS);
    }
}

Three Java helpers appear here. ConcurrentHashMap is a map that many threads can use safely at once. A record is a short way to write a class that only holds fixed values. ExecutorService runs tasks on background threads; this one has a single thread.

The third part is the method that matches the sequence, plus a main method that runs all three paths. book returns an Optional, a Java box that holds a Booking or nothing. Each if is one alt fragment, so the code and the drawing have the same two failure exits.

java
class BookingService {
    private final SlotRepository slots;
    private final PaymentMethod payment;
    private final Notifier notifier;

    BookingService(SlotRepository s, PaymentMethod p, Notifier n) {
        slots = s; payment = p; notifier = n;
    }

    Optional<Booking> book(String userId, String slotId) {
        Slot slot = slots.holdSlot(slotId);              // sync message
        if (slot == null) return Optional.empty();       // alt: [slot taken]
        if (!payment.pay(slot.priceInRupees())) {        // alt: [payment failed]
            slots.release(slotId);
            return Optional.empty();
        }
        Booking booking = new Booking(userId, slot);     // create message
        notifier.sendConfirmationAsync(booking);         // async: no wait
        return Optional.of(booking);
    }
}

class TurfDemo {
    public static void main(String[] args) throws InterruptedException {
        Turf turf = new Turf("Riverside turf");
        turf.addCourt("A");
        turf.addCourt("B");
        System.out.println("courts: " + turf.courtLabels());

        SlotRepository repo = new SlotRepository();
        repo.add(new Slot("A-19:00", 1200));
        Notifier notifier = new Notifier();
        BookingService declined = new BookingService(repo, new UpiPayment(false), notifier);
        BookingService approved = new BookingService(repo, new UpiPayment(true), notifier);

        System.out.println("payment declined, booked: " + declined.book("u1", "A-19:00").isPresent());
        System.out.println("slot released, retry booked: " + approved.book("u2", "A-19:00").isPresent());
        System.out.println("same slot again, booked: " + approved.book("u3", "A-19:00").isPresent());
        notifier.shutdown();
        System.out.println("sent: " + notifier.sent);
    }
}

To check it, paste the three parts in order into one file named TurfDemo.java. Then run javac TurfDemo.java and java TurfDemo. On Java 21 you should see exactly these five lines:

text
courts: [A, B]
payment declined, booked: false
slot released, retry booked: true
same slot again, booked: false
sent: [SMS to u2: A-19:00 confirmed]

The first booking fails at payment and releases the slot, so the retry wins it. The third request finds the slot held. Only the paid booking gets an SMS. On Java 21, java TurfDemo.java fails, because that shortcut looks for main in the file's first top-level type, here the PaymentMethod interface.

What UML mistakes cost marks in LLD interviews?

The costly mistakes are about time and meaning, not small notation slips. Drawing too much, drawing the wrong thing, or drawing lines you cannot explain all hurt more than a wrong arrowhead.

  • Drawing every getter and setter. They add noise. Leave them out and say you will add them in code.
  • Using inheritance for everything. Ask the kind-of question first. A Customer is not a kind of User just because both have a name.
  • Diamonds everywhere. Use composition only when you can name what goes when the whole goes.
  • No multiplicity. Without 1 and * on the lines, you skip the business rules hidden there.
  • Only a class diagram. Structure without one sequence hides missing methods and race conditions.

Beginners can stop here. You can now draw both diagrams and turn them into Java. The rest is for engineers: where UML and Java disagree, making the slot hold safe under load, and writing diagrams as text.

Where do UML and Java disagree?

They differ on visibility and on composition. A diagram that maps one-to-one onto Java keywords can promise less, or more, than the code does.

Protected and package visibility

In UML, # protected makes a member visible to subclasses. In Java, protected also makes it visible to every class in the same package. We checked on Java 21: a non-subclass in the same package read a protected field. Java also narrows protected across packages. There, a subclass reads a protected member only through its own type or its subclasses. javac 21 rejects a read through a parent-type reference.

UML's ~ package visibility has no Java keyword at all. You get it by writing no modifier, which Java calls package-private. Say this aloud if you draw a ~, because many readers expect a keyword.

UML visibility marks and what Java actually does (checked on Java 21)
UML markUML meaningJava way to write itJava difference
+Visible to everyonepublicThe class must be public too
-Only this classprivateReadable anywhere inside the same top-level class
#SubclassesprotectedAlso the whole package
~Same packageNo modifierNo keyword exists

The private row matters for our turf code. Turf reads court.label even though label is private, because Court is nested inside Turf. That is the same rule that lets Turf call Court's private constructor.

Composition is a design promise

A filled diamond says the part dies with the whole. Java has no such rule. Your code makes the promise true by creating parts inside the whole and never handing them out, as Turf does.

How do you stop two customers booking the same slot at once?

Make the check and the mark one atomic step, meaning no other thread can act between them. In Java that is compareAndSet. In a database it is one UPDATE with the free check in its WHERE clause.

Why check-then-mark fails

The naive version reads "is it held?", then sets held to true. Two threads can both read false before either writes true, and both believe they hold the slot. This is a race condition.

We ran it on Java 21 with two threads on a 2-core machine. Forcing both threads to finish the check before either marked, the naive version gave the slot to both in 1,000 of 1,000 rounds, as the forcing guarantees.

Without forcing, it gave the slot to both in 2 of 20,000 rounds in one run, and 0 in two other runs. A race that rarely shows in tests is still a double booking in production.

compareAndSet(false, true) sets the value only if it is still false, and returns whether it did. In every run, forced or not, it gave the slot to exactly one thread.

The same idea in a database

If slots live in a database, put the check inside the update and read the row count it returns. On PostgreSQL the statement looks like this:

sql
UPDATE slot SET held = true WHERE id = 'A-19:00' AND held = false;

UPDATE 1 means you hold the slot once your transaction commits. UPDATE 0 means someone else holds it, or no slot has that id. An isolation level sets how much one transaction sees of other transactions' changes. We ran two sessions on PostgreSQL 16.15 at its default level, Read Committed.

Session 1 held the slot inside an open transaction. Session 2 ran the same UPDATE and waited until session 1 committed, about 2 seconds in our test because session 1 paused on purpose. Then it printed UPDATE 0. When session 1 rolled back instead, session 2 printed UPDATE 1.

PostgreSQL's documentation explains why: the second updater waits, then re-checks the WHERE condition on the new version of the row. The naive two-step version failed the same way as in Java: both sessions read false, and both printed UPDATE 1.

At the stricter Repeatable Read level, session 2 got an error instead: "could not serialize access due to concurrent update". Your code must then retry or report the slot as taken.

One more design point. A real system gives each hold an expiry time, so a crash between the hold and the payment does not lock the slot forever.

How do you write a UML class diagram as text?

Use a text tool such as Mermaid or PlantUML: you write the diagram as plain text, and the tool draws it. Text diagrams sit in your notes or repository and change like code.

Here is the turf sketch in Mermaid. Mermaid 12.2.0, the current release as of 19 October 2026, parsed it without errors. In Mermaid, *-- is composition, o-- aggregation, --> association, ..> dependency and ..|> realization.

text
classDiagram
    class PaymentMethod {
        +pay(int amountInRupees) boolean
    }
    class Turf {
        -String name
        +addCourt(String label) void
    }
    class Court {
        -String label
    }
    class Slot {
        -String id
        -int priceInRupees
        +tryHold() boolean
    }
    class Booking {
        -String userId
    }
    class BookingService {
        +book(String userId, String slotId) Optional~Booking~
    }
    Turf "1" *-- "1..*" Court : owns
    Court "1" --> "*" Slot : offers
    Booking "*" --> "1" Slot : reserves
    Customer "1" --> "0..*" Booking : makes
    UpiPayment ..|> PaymentMethod
    BookingService --> "1" PaymentMethod : pays with
    BookingService ..> Booking : creates

Mermaid can also mark PaymentMethod with the word interface inside double angle marks. We left that line out to keep the sample plain; the dashed realization line already shows it.

And here is the booking sequence. The alt and else lines are the combined fragments, -->> is a dotted reply line, and -) is Mermaid's async arrow, a solid line with an open arrowhead.

text
sequenceDiagram
    participant C as Customer
    participant S as BookingService
    participant R as SlotRepository
    participant P as UpiPayment
    participant N as Notifier
    C->>S: book(u2, A-19:00)
    S->>R: holdSlot(A-19:00)
    alt slot already held
        R-->>S: null
        S-->>C: empty
    else slot free
        R-->>S: slot
        S->>P: pay(1200)
        alt payment failed
            P-->>S: false
            S->>R: release(A-19:00)
            S-->>C: empty
        else payment ok
            P-->>S: true
            S-)N: sendConfirmationAsync(booking)
            S-->>C: booking
        end
    end

To see the drawings, paste either text into a Mermaid renderer, such as the live editor on the Mermaid site. In our run, deleting the last end line made Mermaid 12.2.0 report a parse error, so a broken fragment fails loudly.

How do you explain your UML class diagram in a system design interview?

Use UML as the bridge from requirements to code: nouns become classes, one risky flow becomes a sequence, and both drawings stay small. Here is the order for the turf round.

  1. List the use cases first

    Write three to five lines: search slots, book a slot, cancel a booking, pay. Ask which ones the interviewer cares about most.

  2. Pick classes from the nouns

    Turf, Court, Slot, Booking and Customer come straight from the problem. Add a service class only for logic that fits nowhere else.

  3. Draw boxes with key fields and methods

    Use minus for fields and plus for the main methods. Skip getters, setters and logging.

  4. Draw lines with multiplicity

    Use the chart: kind of, owns, keeps, uses. Write 1, 0..1 or * at both ends, and say each rule out loud.

  5. Draw one sequence for the riskiest flow

    For booking, show the hold, the payment and both failure branches with alt. Mark the SMS as async.

  6. Fix the class diagram, then code

    Add what the sequence needed, like SlotRepository and release. Then write the interfaces and the main service method, and name the race.

Questions people ask

Do LLD interviews require exact UML notation?
It depends on the interviewer, so ask at the start. Our advice is to aim for a sketch they can follow, with classes, relationships and multiplicity that make sense. Use standard symbols where you know them, and explain each line out loud so your meaning is never in doubt.
What is the difference between composition and aggregation in UML?
Composition is a strong whole-part link: a part belongs to at most one whole at a time and, unless removed first, is deleted with it. It uses a filled diamond. Aggregation is a weaker whole-part link with a hollow diamond, where the part can live on its own. Aggregation has no standard meaning beyond a plain association, so many teams draw that instead.
What is the difference between a class diagram and a sequence diagram?
A class diagram shows structure: the classes, their data and methods, and how they relate. A sequence diagram shows behaviour: the messages objects send, in time order, for one scenario. You need both, because each one hides problems the other reveals, such as a missing release method.
How do you show an interface in a UML class diagram?
Draw a class box with the word interface inside double angle marks above the name. Classes that implement it connect with a dashed line and a hollow triangle pointing at the interface. In Java, that line is the implements keyword.
Should I draw a sequence diagram for every use case?
No. In an interview, draw one sequence for the flow with the most risk, such as a race on shared data or a payment that can fail. One careful sequence shows more skill than four rushed ones, and it leaves time for code.
Which tool should I use to draw UML for interview practice?
Use whatever the interview will use, often a whiteboard, paper or an online canvas. For notes after practice, text tools like Mermaid and PlantUML let you write the diagram as plain text and keep it next to your code. During a timed round, speed matters more than polish.
Is UML still updated?
The formal version is UML 2.5.1, dated December 2017, and as of 19 October 2026 it is still the newest formal version on the OMG site. The notation in this guide comes from UML 2. Tools such as Mermaid keep releasing, so pin the version you test with.

Sources and further reading

Practise this with Lala

Practise