DEV Community

Chris
Chris

Posted on

🐍 I Love Python, But It’s a Pain at Scale

Disclaimer: I like Python. I’m not here to bash it — I’m here to talk about the challenges I’ve personally faced while using it to build something real, at scale. If you're a Python dev, I hope this resonates with you (or challenges you — that’s welcome too).


🚀 Python is Great... Until It’s Not

Python is beloved for good reason. It’s clean, expressive, and beginner-friendly. The syntax almost reads like English, and you can go from idea to working prototype insanely fast.

But once your application grows — more services, more developers, more requirements — things start to get messy. And Python doesn’t do much to stop you from falling into that mess.


💥 Real-Life Pain: FastAPI in a Growing Project

I’m building a project with FastAPI, and I was excited at first. It’s async-ready, Pydantic-powered, and comes with auto-generated Swagger docs. What’s not to love?

But as the app grew, problems showed up fast:

  • 🧩 Abstractions are hard. FastAPI’s dependency injection system becomes unwieldy at scale. DI feels more like a workaround than a core feature.
  • 🔄 Type safety isn’t enforced. Pydantic gives runtime validation — but not compile-time safety. You don’t get warned about mistakes until you hit the endpoint.
  • 🐛 Refactoring is scary. Rename a field, miss a usage somewhere? No compile-time errors. It breaks silently.
  • 🛠️ Tooling is decent, but not great. VSCode helps, but it doesn't match the instant feedback of tsc or Go’s compiler.

Let me show you an example that made me stop and reconsider Python as a backend language for large systems.


📦 The Service Layer Pattern — In 3 Languages

We’ll look at a simple flow:

  • Get a user from the database by ID.
  • Return a user DTO.
  • Catch an error if the user doesn’t exist.

🐍 Python (FastAPI + Pydantic)

# user_repository.py
class UserRepository:
    def get_by_id(self, user_id: int):
        user = db.query(User).filter(User.id == user_id).first()
        if not user:
            raise Exception("User not found")
        return user

# user_service.py
class UserService:
    def __init__(self, repo: UserRepository):
        self.repo = repo

    def get_user(self, user_id: int):
        return self.repo.get_by_id(user_id)

# main.py
@app.get("/users/{user_id}")
def get_user(user_id: int):
    service = UserService(UserRepository())
    return service.get_user(user_id)
Enter fullscreen mode Exit fullscreen mode

❌ No enforced types between layers.

❌ If you change the return type of get_by_id, nothing tells you.

❌ Errors are just exceptions — no clear contract.


✅ TypeScript (NestJS)

// user.entity.ts
export interface User {
  id: number;
  name: string;
}

// user.repository.ts
@Injectable()
export class UserRepository {
  getById(id: number): User {
    const user = db.find(u => u.id === id);
    if (!user) throw new NotFoundException();
    return user;
  }
}

// user.service.ts
@Injectable()
export class UserService {
  constructor(private readonly repo: UserRepository) {}

  getUser(id: number): User {
    return this.repo.getById(id);
  }
}

// user.controller.ts
@Controller("users")
export class UserController {
  constructor(private readonly service: UserService) {}

  @Get(":id")
  getUser(@Param("id", ParseIntPipe) id: number): User {
    return this.service.getUser(id);
  }
}
Enter fullscreen mode Exit fullscreen mode

Type-safe across all layers — change the return type in one place, and TypeScript will scream.

✅ Clear contracts.

✅ IDE support and autocompletion are first-class.

✅ Easy to scale with modules and DI containers.


✅ Go (Gin or Fiber)

// user.go
type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

// repository.go
func GetUserByID(id int) (*User, error) {
    user := dbFindUserByID(id)
    if user == nil {
        return nil, errors.New("user not found")
    }
    return user, nil
}

// service.go
func GetUser(id int) (*User, error) {
    return GetUserByID(id)
}

// handler.go
func GetUserHandler(c *gin.Context) {
    idParam := c.Param("id")
    id, _ := strconv.Atoi(idParam)

    user, err := GetUser(id)
    if err != nil {
        c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
        return
    }
    c.JSON(http.StatusOK, user)
}
Enter fullscreen mode Exit fullscreen mode

Compile-time safety — change the type, and the compiler helps you fix everything.

✅ Lightweight and fast.

✅ Great for performance-critical systems.


🔍 What This Shows

Feature Python (FastAPI) TypeScript (NestJS) Go
Compile-time type safety
Enforced architecture
Refactor-safe
Dependency Injection ✅ (loose) ✅ (structured) ✅ (manual)
Runtime performance ⚠️ Moderate ⚠️ Moderate ✅ Fast
Dev experience at scale

🧘 Final Thoughts

This post isn’t about hating Python. It’s about knowing what you’re dealing with.

Python is phenomenal for ML, scripting, automation, and even small APIs. But for high-scale backend systems, I’ve found TypeScript (NestJS) and Go to be way more reliable, maintainable, and scalable — especially when working in teams, with lots of moving parts, over the long term.

What’s been your experience? Have you managed to scale a large Python codebase without pain? Or did you make the switch like I did?

Let me know. I’m open minded and love feedback.

API Trace View

How I Cut 22.3 Seconds Off an API Call with Sentry 🕒

Struggling with slow API calls? Dan Mindru walks through how he used Sentry's new Trace View feature to shave off 22.3 seconds from an API call.

Get a practical walkthrough of how to identify bottlenecks, split tasks into multiple parallel tasks, identify slow AI model calls, and more.

Read more →

Top comments (0)

👋 Kindness is contagious

Explore a trove of insights in this engaging article, celebrated within our welcoming DEV Community. Developers from every background are invited to join and enhance our shared wisdom.

A genuine "thank you" can truly uplift someone’s day. Feel free to express your gratitude in the comments below!

On DEV, our collective exchange of knowledge lightens the road ahead and strengthens our community bonds. Found something valuable here? A small thank you to the author can make a big difference.

Okay