The "monolith" isn’t a dirty word for startups; it’s a feature.
Imagine a startup’s first product: a simple e-commerce site. It needs user accounts, product listings, a shopping cart, and checkout. A monolith bundles all this into a single application.
Here’s a minimal Python/Flask example, just to see the pieces in one place:
from flask import Flask, request, render_template
app = Flask(__name__)
# In-memory "database" for simplicity
users = {}
products = {
"prod_1": {"name": "Gadget", "price": 99.99},
"prod_2": {"name": "Widget", "price": 49.50},
}
carts = {}
@app.route('/signup', methods=['POST'])
def signup():
username = request.form['username']
password = request.form['password']
if username in users:
return "Username already exists", 400
users[username] = password # Insecure, just for demo
return "Signup successful"
@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
if username in users and users[username] == password:
return "Login successful"
return "Invalid credentials", 401
@app.route('/products')
def list_products():
return render_template('products.html', products=products)
@app.route('/add_to_cart/<product_id>', methods=['POST'])
def add_to_cart(product_id):
username = request.form['username'] # Assume user is logged in
if username not in carts:
carts[username] = {}
carts[username][product_id] = carts[username].get(product_id, 0) + 1
return f"Added {product_id} to {username}'s cart"
@app.route('/cart/<username>')
def view_cart(username):
return render_template('cart.html', cart=carts.get(username, {}), products=products)
if __name__ == '__main__':
app.run(debug=True)
This single app.py file, when run, serves all these functions. There’s no network latency between the user service and the product service; they’re literally in the same memory space. Deploying it means running this one process. Scaling it means running more copies of this one process behind a load balancer.
The core problem a monolith solves early on is coordination overhead. When you’re building something new, your requirements change daily. You need to rapidly iterate on features. Imagine if, in our Flask example, the signup route needed to talk to a separate UserService microservice. That means defining an API contract, handling network failures between them, managing inter-service authentication, and deploying two services instead of one. Each of those adds friction.
With a monolith, when you need to change how user data is stored and also how it’s displayed in the product listings (e.g., showing a "purchased by X users" count), you just modify the code in one place. The data structures and functions are readily available. This tight coupling, which is a weakness in large, mature systems, is a superpower for a small, agile team.
The exact levers you control are the standard features of your chosen framework and language. In Flask, it’s routing (@app.route), request handling (request.form), templating (render_template), and your application’s internal state (the dictionaries users, products, carts). The deployment is a single command: python app.py. Scaling is docker run ... multiple times.
A common misconception is that "monolith" implies old, clunky technology. This isn’t true. You can build a highly performant, modern monolith using any language or framework. The architectural choice is about how your codebase is organized and deployed, not necessarily the underlying libraries. For instance, you could use SQLAlchemy for ORM, Redis for caching, and Celery for background tasks, all within a single Flask application.
The one thing most people don’t realize is that the complexity of distributed systems, like microservices, doesn’t disappear; it just moves. It moves from your application code to your infrastructure and operational tooling. Managing service discovery, distributed tracing, inter-service communication resilience, and deployment pipelines for dozens of independent services is a massive undertaking that can easily dwarf the complexity of the business logic itself. For a startup with limited engineering resources, this is often a distraction from the primary goal: building and validating a product.
Eventually, as the application grows in complexity and team size, the monolith will become a bottleneck. But that’s a problem for later. The next problem you’ll hit is deciding when and how to break it apart.