Classes and objects: the building blocks

Part of the free Low-Level Design course on LogicWiz, in the chapter: Object-oriented programming from zero. The class-diagram exercises run in the app.

The cart kept in three lists

"Names in one list, prices in another, quantities in a third. What happens when someone removes milk from only one of them?"

Priya, reviewing Arjun's first cart code


Meet Arjun, and what this course covers

Arjun is an SDE-1 at SwiftKart, a quick-commerce app that delivers groceries in 10 minutes. From his DSA practice he is comfortable with loops, functions and arrays. Soon he has interviews with an LLD round and a machine-coding round. His mentor is Priya, a staff engineer at SwiftKart who reviews his code.

LLD means low-level design: designing the classes inside one program. You decide what data each class keeps, what it can do, and how the classes work together. This course starts from the very first idea, the class. It then moves through design principles, design patterns, diagrams and thread safety. It ends with a full machine-coding round.

The code is in Java, because most interview panels in India accept it and its types make the design easy to see. The ideas apply to any object-oriented language, such as C++, Python, Kotlin or C#.

Here is the cart Arjun wrote for a practice task.

List<String> names = new ArrayList<>();
List<Long> pricesPaise = new ArrayList<>();
List<Integer> quantities = new ArrayList<>();

names.add("Milk 1L");  pricesPaise.add(6800L);  quantities.add(2);
names.add("Bread");    pricesPaise.add(4500L);  quantities.add(1);

long totalPaise = 0;
for (int i = 0; i < names.size(); i++) {
    totalPaise += pricesPaise.get(i) * quantities.get(i);
}

A List<String> is a list of strings that grows as you add to it, and ArrayList is the usual kind of list. Prices are kept in paise (100 paise make one rupee) as whole numbers, so there are no rounding errors. The type long holds large whole numbers, and the L in 6800L marks a long value.

The code runs. But one cart line lives in three lists, matched only by its position. If some code removes "Milk 1L" from names and forgets the other two lists, every later item gets the wrong price. Nothing in the code says that these three values belong together.

A class is a blueprint, an object is one thing built from it

Priya's fix starts by saying in code what a product is.

public class Product {
    private String name;
    private long pricePaise;
    private int stock;

    public Product(String name, long pricePaise, int stock) {
        this.name = name;
        this.pricePaise = pricePaise;
        this.stock = stock;
    }

    public boolean isInStock() {
        return stock > 0;
    }

    public long priceFor(int quantity) {
        return pricePaise * quantity;
    }
}

A class is a blueprint for a kind of thing. It describes what every product has and what every product can do. It is not a product itself. An object is one real product built from that blueprint, such as "Milk 1L at ₹68, with 40 in stock". An object is also called an instance of its class.

A class has two kinds of members:

  • Fields hold the object's data, which is also called its state. Here they are name, pricePaise and stock.
  • Methods are functions that belong to the class. They are the object's behaviour. Here they are isInStock() and priceFor(quantity).

A method can read the fields of its own object directly. That is why priceFor needs only the quantity as input. The price is already inside the object.

The words private and public control who may use a member. private means only code inside Product can touch it. public means any code can call it. Fields are usually private; the chapter on encapsulation explains why.

Think of a printed form at a bank, with empty boxes for name and amount. The printed form is the class. Each filled-in copy is an object, with its own values in the same boxes. The analogy stops working at behaviour. A paper form cannot do anything, but an object can. You can ask a product "are you in stock?" and it answers from its own data.

Arjun asked, "So a class is a folder that groups related functions?" Priya said no. A folder of functions keeps no data, so every caller must pass the data in and remember its rules. A class keeps the data and the code that uses it together. The rules about that data then live in one place.

The constructor, this and new

Look again at the method named Product, with no return type. It is the constructor. It runs once, when an object is created, and fills in the fields. You create an object with the keyword new, which builds the object and then calls the constructor.

Product milk = new Product("Milk 1L", 6800, 40);
Product bread = new Product("Bread", 4500, 0);

System.out.println(milk.isInStock());   // true
System.out.println(bread.isInStock());  // false
System.out.println(milk.priceFor(2));   // 13600

You call a method on an object with a dot: milk.isInStock() asks the milk object whether it is in stock. System.out.println prints a value, and the comment after each line shows what it prints.

Inside the constructor you see this.name = name. Two things are called name there. The plain name is the parameter that was passed in. this.name is the field of the object being built. The keyword this always means "the object this code is running on". So the line reads: copy the parameter into my own field.

Now look at the two objects. milk and bread come from one class, but each holds its own data. Calling isInStock() on milk reads milk's stock, which is 40. Calling it on bread reads bread's stock, which is 0. The method is written once, and every object keeps its own separate fields.

Variables hold references, not objects

A cart line is a product plus a quantity, so it becomes a small class too.

public class CartItem {
    private Product product;
    private int quantity;

    public CartItem(Product product, int quantity) {
        this.product = product;
        this.quantity = quantity;
    }

    public long lineTotal() {
        return product.priceFor(quantity);
    }
}

The field product does not hold a copy of the milk. It holds a reference: an address that points at the one milk object in memory. Think of a contact saved in your phone. The contact is not the person; it is how you reach them. Ten carts can point at the same milk object without making ten copies.

References matter most when two variables point at one object.

Cart cart = new Cart();
Cart sameCart = cart;                 // no new cart: a second name
sameCart.add(milk, 2);
System.out.println(cart.itemCount());       // 1

Cart otherCart = new Cart();          // a new, empty cart
System.out.println(otherCart.itemCount());  // 0

Only new creates an object. The line Cart sameCart = cart copies the reference, not the cart. So cart and sameCart are two names for one cart, and a change made through either name shows up in both. This causes many real bugs, and interviewers like to ask about it.

Static: one value for the whole class

Here is the Cart class itself.

public class Cart {
    public static final int MAX_ITEMS = 50;
    private List<CartItem> items = new ArrayList<>();

    public void add(Product product, int quantity) {
        if (items.size() >= MAX_ITEMS) {
            throw new IllegalStateException("Cart is full");
        }
        items.add(new CartItem(product, quantity));
    }

    public int itemCount() {
        return items.size();
    }

    public long totalPaise() {
        long total = 0;
        for (CartItem item : items) {
            total += item.lineTotal();
        }
        return total;
    }
}

Every cart has its own items list. But the limit of 50 items is the same for every cart. The keyword static makes a member belong to the class itself, not to each object. There is one MAX_ITEMS in the whole program, and you read it as Cart.MAX_ITEMS without creating any cart. The word final means the value can never change after it is set. The throw line stops the method with an error when the cart is full. The loop for (CartItem item : items) reads "for each item in items".

You have already met one static method: main. It is static because it must run before any object exists.

⚠️ Warning: A static field that changes, such as a shared counter, is one value for the whole program. Any code anywhere can change it under everyone else. Keep static for constants and for helper methods that need no object.

A program is objects calling each other

Here are the three classes together, drawn as a class diagram.

classDiagram
  class Cart {
    -items: List~CartItem~
    +add(product, quantity) void
    +totalPaise() long
  }
  class CartItem {
    -quantity: int
    +lineTotal() long
  }
  class Product {
    -name: String
    -pricePaise: long
    -stock: int
    +isInStock() boolean
    +priceFor(quantity) long
  }
  Cart "1" *-- "*" CartItem : holds
  CartItem "*" --> "1" Product : refers to

Each box is a class: its name on top, then its fields, then its methods. A minus sign means private and a plus sign means public. The line with a filled diamond says a cart owns its items: when the cart goes, its items go too. The plain arrow says each item refers to one product that it does not own. The * means "many". The class-diagram chapter teaches this notation in full.

Now follow one call. Checkout calls cart.totalPaise().

sequenceDiagram
  participant Checkout
  participant Cart
  participant CartItem
  participant Product
  Checkout->>Cart: totalPaise()
  Cart->>CartItem: lineTotal()
  CartItem->>Product: priceFor(quantity)
  Product-->>CartItem: 13600
  CartItem-->>Cart: 13600
  Cart-->>Checkout: total of all lines

A solid arrow is a method call, and a dashed arrow is the answer coming back. The cart asks each item for its line total. Each item asks its product for the price of its quantity. No object reaches into another object's fields. Each one asks the object that owns the data. That is what a running object-oriented program is: objects calling each other's methods, each doing one small job.

Compare this with the three lists. The rule "a line total is price times quantity" now lives in one method, next to the data it needs. Removing a line removes one whole CartItem, so nothing can fall out of step.

Now draw Arjun's cart yourself. The board shows his three-lists version as one class. Add a Product and a CartItem with the fields and methods they need, and connect the three classes as in the diagram above. Click a class to edit it. For each line you draw, pick composition for "owns" (the filled diamond) and association for "refers to" (the plain arrow).

📌 Summary: A class is a blueprint of fields and methods; an object is one thing built from it with new. Variables hold references to objects. Static members belong to the class, not each object.

What Arjun learns next

Arjun's cart now has real classes. Next, Priya shows him how one checkout can take UPI, card and cash on delivery without an if for each. The tools are interfaces, abstract classes and enums.

Check your understanding

  1. Arjun writes Cart a = new Cart(); Cart b = a; b.add(milk, 1); What does a.itemCount() return?
  2. SwiftKart allows at most 10 units of any one product per cart, the same limit for every cart. Where does the number 10 belong?
  3. Arjun says, "A class is a folder that groups related functions." What does this miss?
  4. Inside the Product constructor, what does this.stock refer to in the line this.stock = stock;?