Skip to main content

Class diagram

What are the pieces, and how are they wired together? The class diagram is UML's workhorse - the one notation every other diagram on this site borrows boxes from, and the one you will draw the most in an actual LLD interview. Everything below builds up a LoginForm / AuthService / Database login flow in stages, so by the end you have seen every piece of notation land on the same running example.

The three compartments

A class box is always the same three stacked rectangles: the name on top, its attributes (state) in the middle, its operations (behavior) at the bottom. Nothing else belongs in the box - no prose, no comments about what the class does.

AuthService- secretKey: string- maxAttempts: int+ login(user: String, pass: String): boolean- hash(pass: String): string
Name on top, attributes in the middle, operations on the bottom. Every class box in UML follows this layout.

A class with no attributes or operations worth showing can drop to just the name compartment - useful when a box exists only to be pointed at by a relationship, not to be read in detail.

Visibility markers

Every attribute and operation is prefixed with a symbol that says who else is allowed to touch it:

SymbolVisibilityWho can see it
+PublicAnything, anywhere
-PrivateOnly this class
#ProtectedThis class and its subclasses
~Package / internalAnything in the same package or module

AuthService above already uses two of them: + login(...) is the public entry point callers are meant to use, - hash(...) is a private helper nobody outside the class should call directly. Reach for # the moment a subclass genuinely needs to override or call a member its parent defines, and ~ only if your language actually has a package-private concept to draw - plenty of UML diagrams for Python or JavaScript code skip it entirely.

Attribute and method signature syntax

Two fixed shapes, borrowed straight from statically typed languages, used here even when the code itself is dynamically typed:

  • Attribute: name: Type - - secretKey: string, # balance: double.
  • Method: name(param: Type, ...): ReturnType - + login(user: String, pass: String): boolean. A method with no meaningful return value omits the : ReturnType entirely rather than writing : void.

The type names are illustrative, not a commitment to a language. A diagram meant for a Python codebase can write - secretKey: str and nobody will object; the notation cares about the shape of the signature, not which language's spelling of String you used.

Static and abstract rendering

Two rendering conventions layer on top of the same member list, and they answer two different questions - "does this belong to the class or to each object?" and "does this have a body at all?":

  • Underlined - the member is static: it belongs to the class itself, shared across every instance, not to any one object.
  • Italic - the member (or the whole class) is abstract: declared here with no body, filled in by a subclass.
Session
  • maxSessions: int
  • expiresAt: DateTime
  • renew(): void
  • close(): void
Underline means static (shared by the class), italics mean abstract (no body yet). The class name itself is italicized here because Session is an abstract class.

maxSessions is one shared cap for every Session object, hence underlined. renew() has no implementation on Session itself, hence italic - and the class name is italic too, marking the whole class abstract, which forces every concrete subclass to supply its own renew().

Interface, abstract class and enum stereotypes

Three special class kinds get a «stereotype» label above the name, in addition to (or instead of) the italics rule above:

«interface»Notifier+ send(message: String): void
«interface» - a pure contract, no field ever belongs here.
«abstract»Shape+ area(): double+ describe(): String
«abstract» - area() is italic (no body); describe() is finished code.
«enum»DirectionNORTHSOUTHEASTWEST
«enum» - the members are the only instances that will ever exist.

An interface never has fields and never finishes a method body - every member is implicitly abstract, which is why the stereotype alone is enough without italicizing each line. An abstract class can mix both, exactly like Shape above. An enum lists its named constants directly in the member compartment instead of attributes.

Relationship lines and arrowheads

Every line between two boxes is one of a fixed small set, and the only thing that tells them apart is the line style and the arrowhead - the notation carries no color, no thickness variation, nothing else:

AB
Association. A holds a reference to B, usually as a field.
AB
Dependency. A uses B briefly - a parameter or a local variable, never a field.
AB
Aggregation. A has-a B; B survives A being destroyed.
AB
Composition. A owns B's entire lifecycle; B cannot outlive A.
AB
Extends (inheritance). A is a specialization of B; A inherits B's fields and method bodies.
AB
Implements (realization). A promises to provide every method B (an interface) declares.

The full reasoning for choosing between association, dependency, aggregation and composition - and the fifth relationship, realization, on its own - lives in Class relationships; this page only needs you to recognize the line on sight.

Multiplicity notation

A number (or range) written at each end of an association line says how many objects on that end can be linked to one object on the other end, at any moment. It is written right next to the class it constrains, not in the middle of the line:

OrderLineItem11..*
Read from each end inward: "one Order" on the left, "one or more LineItems" on the right.

The common values, and what each one promises:

NotationMeaning
1Exactly one
0..1Zero or one (optional, never more than one)
* (or 0..*)Zero or more
1..*One or more (at least one)
2..5Any exact range

Order "1" -- "1..*" LineItem above reads as "one Order has one or more LineItems, and every LineItem belongs to exactly one Order" - which also tells you, without a word of prose, that a LineItem cannot exist floating on its own.

The login flow, built up in stages

Same technique, assembled on the running example one decision at a time - this is the order you would actually draw it in, live, in an interview.

Stage 1 - just the nouns. Get the classes on the page before committing to what is inside any of them.

LoginFormAuthServiceDatabase
Stage 1: the nouns, nothing else.

Stage 2 - wire the relationships. LoginForm uses an AuthService to do its work; AuthService uses a Database to look users up. Neither holds the other as a permanent field yet, so these are dependencies for now.

LoginFormAuthServiceDatabase
Stage 2: the relationships, still no members.

Stage 3 - add the operations. The verbs from the prompt - submit credentials, log a user in, find a user record - become methods on whichever class owns the data they touch.

LoginForm+ submit(user: String, pass: String): voidAuthService+ login(user: String, pass: String): booleanDatabase+ findUser(name: String): User
Stage 3: one method per class, named after the verb it owns.

Stage 4 - add the state, and promote a dependency to an association. AuthService turns out to need Database on every call, not just once, so it becomes a held field - an association, drawn with a plain solid line instead of dependency's dashed one - and gets a multiplicity: one AuthService uses exactly one Database.

LoginForm+ submit(user: String, pass: String): voidAuthService- db: Database+ login(user: String, pass: String): booleanDatabase+ findUser(name: String): User
Stage 4: a held reference is an association, not a dependency - the line style changed to say so.

Four stages, and the diagram now says something a paragraph of prose would have taken three times as long to say precisely: what exists, how long each link lasts, and who is responsible for which verb.

When to reach for it: any time you are about to write code with more than one class in it - it is the default, load-bearing diagram of the bunch. When not to bother: for a single class with no interesting relationships, just write the class; a box with one name in it is not a diagram.

Common mistakes

  • Drawing every getter and setter. A class box crowded with a dozen accessor pairs hides the two or three methods that actually matter. Show the behavior worth talking about; trust that accessors exist.
  • Confusing which end owns the relationship. An arrow (or an association line with multiplicities) has a direction. Order --> Customer and Customer --> Order are different claims about who holds a reference to whom - getting this backwards silently describes a different design than the one you meant.
  • Forgetting multiplicity when it is the whole point. "A Team has Players" is incomplete - can a team have zero players mid-season? Is there a cap? The multiplicity is often the one piece of information an interviewer is actually listening for.
  • Reaching for inheritance to get code reuse, with no real "is-a" relationship. Two classes sharing a field and a method by coincidence are not a hierarchy; see Favor composition over inheritance before drawing the arrow.

Try it yourself: sketch a class diagram for a Book, a Library, and a Member, building it up the way Stage 1 through 4 did above - nouns first, then relationships, then operations, then multiplicities. Does a Library "have" Books by composition or aggregation? What multiplicity goes on each end?

Check yourself

Question 1 of 4

A member is written `# balance: double`. What does the `#` mean, and who can see this field?