Skip to main content
CleanCodeMastery

Large Class: The School Bag That Carries Everything

Understand the Large Class code smell — why god classes grow, how to spot low cohesion, and how Extract Class splits them into small, focused classes.

23 min read Updated June 11, 2026beginner
code smellslarge classbloatersrefactoringgod objectsingle responsibility

🎯 The School Bag That Weighed More Than the Student

Meet Anaya, a class 7 student at Green Valley School, Nagpur. Her school bag is famous in the whole colony — because it carries everything.

All fourteen textbooks, even on days with only four periods. Three notebooks per subject. Her tiffin box, a water bottle, and an extra packet of biscuits. Her skates for Tuesday's sports class — kept inside all week, "just in case." Her painting kit. Her old test papers from last year. Even the spare umbrella her grandmother insisted on.

Every morning, Anaya groans lifting the bag. When the teacher says, "take out your geometry box," Anaya digs for five whole minutes. Once, her leaking water bottle soaked the maths homework. When her cousin borrowed "just the science book," he had to empty half the bag to find it.

Her mother, Sunita, finally fixed it the sensible way: a small daily bag packed by the timetable, a separate tiffin bag, and a sports kit that goes only on Tuesday. Now each bag is light, each thing is findable, and a leak in one bag cannot ruin another.

Here the story takes a turn. Anaya's elder sister Diya is a second-year CSE student. For a college hackathon, Diya and her teammate Sameer build a "Smart School" app — and Diya, smiling at the family joke, names her main class SchoolBag. Six weeks later, the joke has become real. The SchoolBag class in the app has grown exactly like Anaya's bag: books, tiffin, sports, library fines — everything stuffed into one class. Sameer cannot add a feature without stepping on Diya's code. Their professor, Dr. Rao, takes one look and writes two words on the review sheet: "Large Class."

In code, a Large Class is Anaya's old school bag. One class stuffed with every field and method that ever seemed "related" to it. It works — the bag does carry things — but every search is slow, every change risks soaking something unrelated, and nobody can lift it with confidence. This lesson follows Diya and Sameer as they learn to spot the smell, measure it, and split the bag.

💡 What is this smell?

First, a quick reminder of the golden rule: a code smell is not a bug. The program runs and gives right answers. A smell is a structural warning — like a creaking shelf that still holds the books today. Large Class is one of the "Bloater" smells from Martin Fowler's book Refactoring: code that has swollen so much it is hard to handle.

A Large Class is a class that has collected too many fields, too many methods, and — most importantly — too many responsibilities. It is no longer one "thing." It has become a whole department squeezed into a single name.

The extreme version even has a nickname in the industry: the god object — a class that knows too much and does too much, and which everything else in the program depends on. Legacy systems are famous for them: a Manager or Context class with thousands of lines that every developer fears and every feature touches.

💡

The true measure of this smell is not line count but cohesion — do the parts of the class actually belong together? A 700-line class where everything serves one purpose can be healthier than a 200-line class hiding three unrelated jobs.

This smell is the big sibling of Long Method. A long method does many jobs in one function; a large class does many jobs in one class — and it is usually full of long methods too. Bloaters travel in packs. When Dr. Rao opened Diya's SchoolBag, he found three long methods inside it within a minute.

College corner: The formal name for "one class, one job" is the Single Responsibility Principle (SRP) — Robert C. Martin defines it as "a class should have only one reason to change." Cohesion is measured by metrics like LCOM (Lack of Cohesion of Methods): roughly, count the method pairs that share no fields. A high LCOM says the class is a hostel of strangers, mathematically.

👃 How to spot it

Dr. Rao gives Diya and Sameer his checklist. Open any class in your project and run it:

  • The file is hundreds or thousands of lines long; you navigate by Ctrl+F, not by reading.
  • The class has many fields, and each method touches only a small, different subset of them.
  • The class name is a vague absorber: Manager, Service, Helper, Util, Engine, Context, Processor.
  • Field names carry prefixes that mark territories: tiffinBox, tiffinFresh, sportsShoes, sportsDay — those prefixes are class names trying to escape.
  • Unrelated reasons make the file change: a UI tweak, a pricing rule, and a logging fix all land in this one class.
  • The class's test file is enormous, with painful setup, because creating the object drags in every concern at once.
  • Merge conflicts happen here constantly, because everyone on the team edits this same file.
SymptomWhat it tells you
Methods use disjoint groups of fieldsSeveral classes are hiding inside one — low cohesion
Name like Manager or HelperThe class has no single idea, so it got a bucket name
Prefixed field names (billingX, shippingY)The author already grouped the concepts informally — make the groups real classes
Every feature touches this fileThe class has become a god object and a team bottleneck
Giant test setupYou cannot create the object without building all its unrelated machinery
Same logic appears twice inside the classThe class is so big that even its own author forgot what it contains

Diya runs the checklist and ticks five boxes. Sameer ticks the sixth — he had quietly re-written a date-formatting helper that already existed 300 lines above, because he never scrolled that far.

⚠️ Why it is a problem

Why should we care, if the bag still carries the books?

  1. Cognitive overload. Nobody can hold a 2,000-line class in their head. So changes are made with partial understanding, and partial understanding breeds side effects.
  2. Low cohesion, high coupling. Methods that work on unrelated fields share one namespace and can interfere. Meanwhile, every consumer of the class depends on far more than it actually uses — like carrying skates to a maths exam.
  3. Team traffic jam. One hot file that everyone edits means endless merge conflicts. The class becomes a bottleneck for parallel work, just like one school counter handling all admissions. Diya and Sameer hit this in week three: every pull request conflicted with the other's.
  4. Hidden duplication. With so much surface area, the same helper logic gets re-written in two corners of the same class, and nobody notices. Sameer's date helper is the living proof.
  5. Untestable units. You cannot test the tiffin-freshness logic without also constructing the sports kit and the library records. Tests become heavy, slow, and rare — and rare tests mean hidden bugs.

And like all bloaters, it grows silently. Classes obey a kind of gravity: the more a class already knows, the easier it is to add the next feature there, because all the data is conveniently in scope. Researchers and practitioners call this a rich-get-richer dynamic — well-connected classes attract still more responsibility.

Figure 1: The gravity of a growing class — convenience pulls every new feature into it

When Dr. Rao asks Diya to count her fields by territory, the result embarrasses her. The class called SchoolBag is barely about bags at all:

Figure 2: What actually lives inside the SchoolBag class — four territories under one roof

Then he shows them the team cost. As the class grows, merge conflicts on the file grow much faster than the line count, because more people need the same file more often:

Figure 3: Merge conflicts per month as the god class grows — pain rises faster than size

College corner: This is coupling theory in action. A god object has high afferent coupling (many classes depend on it) and high efferent coupling (it depends on many classes), so it sits in the blast radius of every change in both directions. SRP fixes the cohesion side; dependency inversion and small interfaces fix the coupling side. The two problems almost always arrive together.

🧪 A week in the life of the god class

To make the cost concrete, watch Sameer try to add one small feature — "remind students to return library books" — to the swollen SchoolBag:

Figure 4: Sameer adds one feature to the god class — and trips over every other territory

One feature, four territories disturbed, one merge conflict, one broken test in an unrelated area. Now look at the same week as an emotional journey:

Figure 5: The feature-adding journey inside a god class — most energy goes to navigation and fear

"Notice," Dr. Rao says, "the actual feature took twenty minutes. The bag cost you two days."

📊 Which classes should be split first?

You cannot split every big class this sprint, and you should not. Dr. Rao teaches the same two-question trick we used for long methods: how incohesive is the class, and how often does it change? Plot your classes and the priorities reveal themselves:

Figure 6: Split urgency — low cohesion plus frequent change means refactor now

SchoolBag sits deep in "Refactor now": it mixes four jobs and both teammates edit it weekly. The old report generator is just as messy but frozen — it can wait its turn.

🧪 A real-life code example

Let us look at Diya's actual class. The app began with a simple SchoolBag. Two terms (well, six hackathon weeks) later, it looks like this:

class SchoolBag {
  // books territory
  books: string[] = [];
  notebooks: string[] = [];
 
  // tiffin territory
  tiffinDish: string = "";
  tiffinPackedAt: Date = new Date();
  waterBottleFull: boolean = true;
 
  // sports territory
  sportsShoes: boolean = false;
  skates: boolean = false;
  sportsDay: string = "Tuesday";
 
  // library territory
  borrowedBook: string = "";
  dueDate: Date = new Date();
 
  packForDay(timetable: string[]): void {
    this.books = timetable.map((subject) => subject + " textbook");
    this.notebooks = timetable.map((subject) => subject + " notebook");
  }
 
  isTiffinFresh(now: Date): boolean {
    const hours = (now.getTime() - this.tiffinPackedAt.getTime()) / 3600000;
    return hours < 6;
  }
 
  refillWater(): void {
    this.waterBottleFull = true;
  }
 
  needsSportsKit(day: string): boolean {
    return day === this.sportsDay;
  }
 
  packSportsKit(day: string): void {
    if (this.needsSportsKit(day)) {
      this.sportsShoes = true;
      this.skates = true;
    }
  }
 
  isLibraryBookOverdue(today: Date): boolean {
    return this.borrowedBook !== "" && today > this.dueDate;
  }
 
  lateFine(today: Date): number {
    if (!this.isLibraryBookOverdue(today)) return 0;
    const daysLate = Math.ceil(
      (today.getTime() - this.dueDate.getTime()) / 86400000,
    );
    return daysLate * 2; // Rs. 2 per day
  }
 
  totalWeightKg(): number {
    let weight = this.books.length * 0.5 + this.notebooks.length * 0.2;
    if (this.sportsShoes) weight += 0.7;
    if (this.skates) weight += 1.5;
    if (this.borrowedBook !== "") weight += 0.4;
    return weight;
  }
}

Pause and inspect, the way Dr. Rao made Diya inspect. Do you see the four territories? Books, tiffin, sports, library. The comments confess it, the field prefixes confess it, and the methods confess it too: isTiffinFresh touches only tiffin fields; lateFine touches only library fields. The clusters never talk to each other — they merely share a roof.

Why does this hurt in practice?

  • A bug in lateFine forces you to open and re-understand a file full of tiffin and skates code.
  • To unit test tiffin freshness, you must create a whole SchoolBag, dragging sports and library baggage along.
  • When the library module of the school app wants due-date logic, it cannot reuse it without importing the entire bag.
  • Two teammates editing tiffin and library features collide in this one file — exactly Diya and Sameer's week three.
⚠️

When methods inside a class form camps that never share fields, the class is not one thing — it is a hostel of strangers. Each camp deserves its own room.

🛠️ Cleaning it up, step by step

The cure is Extract Class: pick one cohesive cluster of fields + methods, move them into a new class, and let the original hold a reference. Diya and Sameer split the work — and for the first time in weeks, they can work in parallel without conflicts.

Step 1: Extract the most independent cluster first — the library record.

class LibraryLoan {
  constructor(
    readonly bookTitle: string,
    readonly dueDate: Date,
  ) {}
 
  isOverdue(today: Date): boolean {
    return today > this.dueDate;
  }
 
  lateFine(today: Date): number {
    if (!this.isOverdue(today)) return 0;
    const daysLate = Math.ceil(
      (today.getTime() - this.dueDate.getTime()) / 86400000,
    );
    return daysLate * 2;
  }
 
  weightKg(): number {
    return 0.4;
  }
}

The fine rule now lives where the due date lives. The library module can reuse LibraryLoan directly — no school bag required. Notice the bonus: in the old code, isLibraryBookOverdue had to check borrowedBook !== "" because the bag might not have a book. In the new design, a LibraryLoan object only exists when a book is borrowed. A whole category of "empty string" bugs disappears.

Step 2: Extract the tiffin and sports kit clusters.

class Tiffin {
  constructor(
    readonly dish: string,
    readonly packedAt: Date,
  ) {}
 
  isFresh(now: Date): boolean {
    const hours = (now.getTime() - this.packedAt.getTime()) / 3600000;
    return hours < 6;
  }
}
 
class SportsKit {
  constructor(
    readonly items: { name: string; weightKg: number }[],
    readonly sportsDay: string,
  ) {}
 
  isNeededOn(day: string): boolean {
    return day === this.sportsDay;
  }
 
  weightKg(): number {
    return this.items.reduce((sum, item) => sum + item.weightKg, 0);
  }
}

Step 3: The bag becomes a light coordinator.

class SchoolBag {
  books: string[] = [];
  notebooks: string[] = [];
  tiffin?: Tiffin;
  sportsKit?: SportsKit;
  loan?: LibraryLoan;
 
  packForDay(timetable: string[], day: string): void {
    this.books = timetable.map((s) => s + " textbook");
    this.notebooks = timetable.map((s) => s + " notebook");
    if (this.sportsKit && !this.sportsKit.isNeededOn(day)) {
      this.sportsKit = undefined; // leave it at home!
    }
  }
 
  totalWeightKg(): number {
    return (
      this.books.length * 0.5 +
      this.notebooks.length * 0.2 +
      (this.sportsKit?.weightKg() ?? 0) +
      (this.loan?.weightKg() ?? 0)
    );
  }
}

Look at what the team gained. SchoolBag now does only bag things: deciding what to pack and weighing the result. Tiffin freshness, sports schedules, and library fines each live in a small, named, independently testable class. And notice the design improvement that came free: the bag can now actually leave the skates at home — modelling that was awkward when everything was boolean fields in one giant class.

Here is the new design as a class diagram — the picture Diya finally pinned on the team's wall:

Figure 7: The refactored design — SchoolBag coordinates three small, cohesive classes

And the before-and-after at a glance:

Figure 8: Extract Class turns one stuffed bag into a coordinator with focused parts

Two more tools belong in this kit. If a class is large because some features apply only to some objects (say, a Vehicle class with truck-only fields), use Extract Subclass. If clients depend on a giant class but each client uses only a slice of it, use Extract Interface to give each client a small, honest contract.

College corner: Extract Interface is really the Interface Segregation Principle at work — no client should be forced to depend on methods it does not use. In a layered system, you often apply both: Extract Class to fix the implementation's cohesion, then Extract Interface so each consumer sees only its own slice. Together they convert one giant dependency into several narrow, replaceable ones — which is also what makes mocking in unit tests possible.

🔄 The life cycle of this smell

Like Anaya's bag, a class is not born heavy. It moves through states, and team habits decide the direction:

Figure 9: The life cycle of a class — gravity pulls it toward god object; Extract Class pulls it back

The cheapest arrow is Growing to Clean — split at the first visible seam, when extraction takes an afternoon. The GodObject state is sticky: by then, so many callers depend on the class that every extraction needs coordination across the whole team.

🧰 The same smell in C#

A quick second example, from the sample code Dr. Rao shows in class. This hostel warden class started as "the person who manages the hostel" and absorbed every hostel-related job:

public class HostelWarden
{
    public List<string> Rooms = new();
    public Dictionary<string, string> RoomAllotments = new();
    public List<string> MessMenu = new();
    public decimal MessBudget;
    public List<string> Complaints = new();
 
    public void AllotRoom(string student, string room) =>
        RoomAllotments[student] = room;
 
    public bool IsRoomFree(string room) =>
        !RoomAllotments.ContainsValue(room);
 
    public void PlanWeeklyMenu(List<string> dishes) => MessMenu = dishes;
 
    public decimal CostPerStudent(int students) => MessBudget / students;
 
    public void FileComplaint(string text) => Complaints.Add(text);
 
    public int PendingComplaints() => Complaints.Count;
}

Three strangers in one room: room allotment, mess planning, complaints. After Extract Class:

public class RoomRegister
{
    private readonly Dictionary<string, string> _allotments = new();
    public void Allot(string student, string room) => _allotments[student] = room;
    public bool IsFree(string room) => !_allotments.ContainsValue(room);
}
 
public class MessPlan
{
    public List<string> Menu { get; private set; } = new();
    public decimal Budget { get; set; }
    public void PlanWeek(List<string> dishes) => Menu = dishes;
    public decimal CostPerStudent(int students) => Budget / students;
}
 
public class ComplaintBook
{
    private readonly List<string> _complaints = new();
    public void File(string text) => _complaints.Add(text);
    public int PendingCount => _complaints.Count;
}
 
public class HostelWarden
{
    public RoomRegister Rooms { get; } = new();
    public MessPlan Mess { get; } = new();
    public ComplaintBook Complaints { get; } = new();
}

The warden still exists — but now as a coordinator of three focused helpers, exactly like Sunita with her three small bags. Each helper can be tested with three-line tests, and the mess team can change MessPlan without ever opening room-allotment code.

🔍 Where this smell hides in real projects

Large Class has well-known nesting grounds in real software:

  • Legacy god classes. Old enterprise systems often contain an ApplicationManager, SystemContext, or MainForm of several thousand lines that every feature passes through. The Wikipedia entry on god object describes exactly this anti-pattern: an object that references many unrelated types and knows too much.
  • Entities that absorbed services. A Customer or User class that began as data and slowly gained email sending, password hashing, invoice formatting, and report generation — each addition "related to customers," none of them being a customer.
  • Utility dumping grounds. Utils, Helpers, and Common classes where homeless functions accumulate. The name itself is a confession that no single concept lives there.
  • Fat controllers and fat models in MVC apps. Web frameworks make the controller (or the model) the easiest place to put logic, so checkout controllers and User models swell. The community even coined slogans against it ("skinny controllers").
  • God constants files. A single class holding hundreds of unrelated constants — discussed by Maximiliano Contieri as its own smell — is the same disease in miniature.
ℹ️

Many static analysers flag this smell. SonarQube counts class lines and complexity; metrics like LCOM (Lack of Cohesion of Methods) directly measure whether methods share fields. A high LCOM score is the mathematical version of "strangers sharing one room."

🤔 When it is okay to ignore

SituationIgnore the smell?Why
Big class, but every method serves one purpose on the same data✅ YesSize without low cohesion is not the disease; splitting would scatter one concept
Generated code (ORM models, API clients)✅ YesMachines maintain it; humans rarely read it
Framework base classes designed to be broad✅ UsuallySome frameworks intentionally offer wide surface areas
Class with clear field clusters and method camps❌ NoThe seams are visible — extract along them
God class that every team member edits weekly❌ NoHighest change traffic means highest payoff for splitting
Splitting would create classes with no clear names❌ WaitIf you cannot name the pieces, you have not found the real seam yet — do not chop along arbitrary lines

The honest rule: split only along a real seam — a subset of fields used together by a subset of methods. Cutting a healthy class into random slices produces several anemic classes plus new plumbing, which is worse than one big bag. Sunita did not give Anaya fourteen bags, one per book — she made three, along the real seams of her daughter's day.

💊 Which refactorings cure it

RefactoringWhen to use it
Extract ClassThe main cure — move a cohesive cluster of fields and methods into a new collaborating class
Extract SubclassSome features are used only by certain instances — push them down into a subclass
Extract InterfaceClients need only a slice of the class — give each client a small contract
Extract MethodLarge classes are full of long methods; shrinking them first often reveals the class seams
Replace Data Value with ObjectLoose primitive fields in the giant class become proper value objects you can move out

🧠 The whole smell on one page

Diya's final revision sketch, straight from her hackathon notebook:

Figure 10: Large Class at a glance — signs, causes, costs, and cures

📦 Quick revision box

+----------------------------------------------------------------+
|                  LARGE CLASS - CHEAT SHEET                     |
+----------------------------------------------------------------+
| What     : One class stuffed with many jobs (the school bag)   |
| Family   : Bloaters (big sibling of Long Method)               |
| Nickname : God object, when extreme                            |
| Spot it  : Many fields, method camps, name prefixes,           |
|            bucket names like Manager/Helper/Util               |
| Costs    : Overload, coupling, merge conflicts, heavy tests    |
| Main fix : Extract Class along real seams                      |
| Helpers  : Extract Subclass, Extract Interface                 |
| Ignore   : Big but cohesive classes; generated code            |
| Mantra   : "Low cohesion is the disease; size is only          |
|             the symptom."                                      |
+----------------------------------------------------------------+

✍️ Practice exercise

Dr. Rao's homework for Diya and Sameer — and for you. Here is a small but smelly class from a cricket club app. It works fine — but at least three different "bags" are stuffed inside it. Find the seams and extract classes.

class CricketClub {
  // members
  memberNames: string[] = [];
  memberFeesPaid: boolean[] = [];
 
  // ground booking
  groundBookings: { date: string; team: string }[] = [];
 
  // kit inventory
  bats: number = 10;
  balls: number = 24;
 
  addMember(name: string): void {
    this.memberNames.push(name);
    this.memberFeesPaid.push(false);
  }
 
  payFee(name: string): void {
    const i = this.memberNames.indexOf(name);
    if (i >= 0) this.memberFeesPaid[i] = true;
  }
 
  bookGround(date: string, team: string): boolean {
    if (this.groundBookings.some((b) => b.date === date)) return false;
    this.groundBookings.push({ date, team });
    return true;
  }
 
  lendKit(batCount: number, ballCount: number): boolean {
    if (batCount > this.bats || ballCount > this.balls) return false;
    this.bats -= batCount;
    this.balls -= ballCount;
    return true;
  }
}

Your tasks:

  1. Identify the three clusters of fields and methods. Which fields does bookGround touch? Which does payFee touch? Do they overlap?
  2. Extract a MemberRegister, a GroundSchedule, and a KitStore class. Let CricketClub hold one of each.
  3. Draw your own version of Figure 7 — a class diagram of your refactored design — before you write any code. Diagrams first, code second.
  4. Bonus: the parallel arrays memberNames and memberFeesPaid are fragile (what if they go out of sync?). Replace them with a single Member object holding a name and a fee status — a small taste of our next lesson, Primitive Obsession.

When each class in your solution can be explained in one sentence, you have beaten the school-bag smell — and like Diya and Sameer, you can finally work on the same project without stepping on each other.

Frequently asked questions

Is every big class a Large Class smell?
No. Size alone is not the disease — low cohesion is. A class can be big but healthy if all its methods work on the same data for one purpose. The smell appears when groups of fields and methods inside the class ignore each other, like strangers sharing one room.
What is a 'god object'?
A god object is the extreme form of Large Class — one class that knows too much and does too much, referencing many unrelated types and collecting uncategorised methods. It becomes the centre of the program that everything depends on, which makes it very hard to change safely.
Won't splitting one class into five make my project confusing?
Usually the opposite happens. Five small classes with honest names are easier to learn than one tangled giant, because you can understand each one alone. Confusion comes from entangled concepts, not from file count.
How do I find the 'seams' where a class should be split?
Look for a subset of fields that is used together by a subset of methods. If three fields and four methods always work together and rarely touch the rest, that cluster is a hidden class. Name prefixes like billingTotal and billingTax are also strong hints.
Which refactoring fixes Large Class most often?
Extract Class. You pick one cohesive cluster of fields and methods, move it into a new class, and let the old class hold a reference to it. Extract Subclass and Extract Interface help in special cases.

Further reading

Related Lessons