A God Class is a class that has grown to know too much and do too much. It violates the Single Responsibility Principle so thoroughly that it becomes the center of gravity for the entire system — everything depends on it, and every change risks breaking something.

How to Spot a God Class

  • 500+ lines of code (some reach thousands)
  • 20+ methods — many unrelated to each other
  • Dozens of instance variables — managing multiple concerns
  • Used by most other classes — it’s the hub everything depends on
  • Changes frequently — every feature touches this class
// ❌ Classic God Class
class UserManager {
  // Authentication
  async login(email: string, password: string) { /* ... */ }
  async logout(userId: string) { /* ... */ }
  async refreshToken(token: string) { /* ... */ }
  
  // Registration
  async register(data: RegistrationData) { /* ... */ }
  async verifyEmail(token: string) { /* ... */ }
  
  // Profile management
  async updateProfile(userId: string, data: ProfileData) { /* ... */ }
  async uploadAvatar(userId: string, file: File) { /* ... */ }
  async changePassword(userId: string, oldPw: string, newPw: string) { /* ... */ }
  
  // Permissions
  async checkPermission(userId: string, resource: string) { /* ... */ }
  async assignRole(userId: string, role: string) { /* ... */ }
  
  // Notifications
  async sendNotification(userId: string, message: string) { /* ... */ }
  async getNotifications(userId: string) { /* ... */ }
  
  // Analytics
  async trackEvent(userId: string, event: string) { /* ... */ }
  async getActivityLog(userId: string) { /* ... */ }
  
  // ... 30 more methods
}

Step-by-Step Breakdown

1. Identify Responsibility Clusters

Group methods by what they do:

  • Authentication: login, logout, refreshToken
  • Registration: register, verifyEmail
  • Profile: updateProfile, uploadAvatar, changePassword
  • Authorization: checkPermission, assignRole
  • Notifications: sendNotification, getNotifications
  • Analytics: trackEvent, getActivityLog

2. Extract Each Cluster

// ✅ Each service handles one concern
class AuthService {
  constructor(
    private userRepo: UserRepository,
    private tokenService: TokenService,
  ) {}

  async login(email: string, password: string): Promise<AuthResult> {
    const user = await this.userRepo.findByEmail(email);
    if (!user) throw new AuthError('Invalid credentials');
    
    const valid = await bcrypt.compare(password, user.passwordHash);
    if (!valid) throw new AuthError('Invalid credentials');
    
    const tokens = this.tokenService.generatePair(user.id);
    return { user: this.sanitize(user), ...tokens };
  }

  async logout(userId: string): Promise<void> {
    await this.tokenService.revokeAll(userId);
  }

  async refreshToken(refreshToken: string): Promise<TokenPair> {
    const payload = this.tokenService.verify(refreshToken);
    return this.tokenService.generatePair(payload.userId);
  }

  private sanitize(user: User): SafeUser {
    const { passwordHash, ...safe } = user;
    return safe;
  }
}

class ProfileService {
  constructor(
    private userRepo: UserRepository,
    private storage: FileStorage,
  ) {}

  async updateProfile(userId: string, data: ProfileData): Promise<User> {
    const user = await this.userRepo.findById(userId);
    if (!user) throw new NotFoundError('User');
    return this.userRepo.update(userId, data);
  }

  async uploadAvatar(userId: string, file: File): Promise<string> {
    const url = await this.storage.upload(`avatars/${userId}`, file);
    await this.userRepo.update(userId, { avatarUrl: url });
    return url;
  }

  async changePassword(userId: string, oldPw: string, newPw: string): Promise<void> {
    const user = await this.userRepo.findById(userId);
    if (!user) throw new NotFoundError('User');
    
    const valid = await bcrypt.compare(oldPw, user.passwordHash);
    if (!valid) throw new AuthError('Current password is incorrect');
    
    const hash = await bcrypt.hash(newPw, 12);
    await this.userRepo.update(userId, { passwordHash: hash });
  }
}

class AuthorizationService {
  constructor(private permissionRepo: PermissionRepository) {}

  async checkPermission(userId: string, resource: string): Promise<boolean> {
    const permissions = await this.permissionRepo.getForUser(userId);
    return permissions.includes(resource);
  }

  async assignRole(userId: string, role: string): Promise<void> {
    await this.permissionRepo.assignRole(userId, role);
  }
}

3. Wire It Together

If callers need a unified API, use a facade:

class UserFacade {
  constructor(
    private auth: AuthService,
    private profile: ProfileService,
    private authorization: AuthorizationService,
    private notifications: NotificationService,
  ) {}

  // Delegate to the appropriate service
  login(email: string, password: string) { return this.auth.login(email, password); }
  updateProfile(userId: string, data: ProfileData) { return this.profile.updateProfile(userId, data); }
  // ... only expose what's needed
}

Prevention

  • Watch class size: Set a soft limit (200-300 lines) and refactor when exceeded
  • One responsibility test: Can you describe what the class does in one sentence without “and”?
  • New feature test: If a new feature requires modifying this class, it might be doing too much
  • Linting rules: ESLint plugins can flag classes with too many methods

“A class should have only one reason to change.” — Robert C. Martin