Thinking Like a Backend Engineer
What Makes Backend Engineering Advanced?
Building a backend application is relatively straightforward.
Building a backend system that remains reliable as traffic grows, dependencies fail, data is modified concurrently, requirements change, and many engineers work on it at the same time is much harder.
This is where Advanced Backend Engineering begins.
Advanced Backend Engineering is not simply about knowing more frameworks, libraries, infrastructure tools, or architectural patterns.
The real challenge is learning how to reason about systems:
How will the system behave? Where can it fail? How will it evolve? What trade-offs are acceptable?
What You Will Learn
By the end of this lesson, you should understand:
- the difference between ordinary backend development and advanced backend engineering;
- why working code does not necessarily mean a correct system;
- how scale introduces new engineering problems;
- why failure is a normal part of production systems;
- how concurrency can break otherwise correct code;
- why compatibility matters as systems evolve;
- how team structure influences architecture;
- why architecture is fundamentally about managing trade-offs.
1. Building a Feature Is Not the Same as Engineering a System
Suppose the business requirement is simple:
A customer should be able to create an order.
A basic implementation might look like this:
Client
│
▼
OrderController
│
▼
OrderService
│
▼
OrderRepository
│
▼
PostgreSQL
The client sends:
POST /orders
The application validates the request, creates an order, stores it in the database, and returns a response.
The feature works.
But production introduces a different set of questions:
- What happens if the same request arrives twice?
- What happens if the order is created but the client never receives the response?
- What happens if the client retries?
- Could two customers purchase the last available item at the same time?
- What happens if the database becomes slow?
- What happens if payment succeeds but inventory reservation fails?
- What happens when traffic grows from 100 RPS to 10,000 RPS?
- How do we know when the system becomes unhealthy?
- Could a database schema change break an older application instance?
These are not primarily questions about controllers, annotations, or framework APIs.
They are system engineering questions.
A backend developer may be asked:
How do we implement this endpoint?
A backend engineer must also ask:
Under what conditions can this endpoint behave incorrectly?
That difference becomes increasingly important as systems move from development environments into production.
2. Working Code Does Not Necessarily Mean a Correct System
Consider this endpoint:
POST /orders
Request:
{
"productId": "P123",
"quantity": 1
}
The server creates an order:
{
"orderId": "O456",
"status": "CREATED"
}
Everything appears correct.
Now consider this sequence:
Client
│
│ POST /orders
▼
Server
│
├── Order successfully created
│
├── Response sent
│
X Network connection lost
From the server's perspective, the operation succeeded.
But the client never received the response.
The client cannot know whether:
The order was created
or
The request failed
So the client retries:
POST /orders
The server may create another order.
Both HTTP requests can be technically successful.
But the business result is incorrect:
Expected orders = 1
Actual orders = 2
This gives us an important principle:
Request-level correctness is not the same as business-level correctness.
Later in the course, we will study techniques such as idempotency for handling this type of problem.
For now, the goal is not to solve it.
The goal is to recognize that the problem exists.
3. Scale Creates Complexity
Suppose an application initially receives:
10 requests/second
Its architecture might be extremely simple:
Client
│
▼
Application
│
▼
PostgreSQL
This may work perfectly well.
Then traffic increases to:
1,000 requests/second
Later:
10,000 requests/second
Suddenly, decisions that barely mattered before can become significant.
For example:
- slow SQL queries;
- missing indexes;
- too many database connections;
- unnecessary network calls;
- CPU-intensive processing;
- synchronous operations;
- large response payloads;
- memory pressure;
- hot database rows.
A common response might be to add more application instances:
Load Balancer
│
┌─────────────┼─────────────┐
▼ ▼ ▼
API #1 API #2 API #3
│ │ │
└─────────────┼─────────────┘
▼
PostgreSQL
The application layer now has more capacity.
But all three application instances may open additional connections to the same database.
The result might be:
Application bottleneck reduced
but
Database bottleneck introduced
The problem did not disappear.
It moved.
This leads to another principle:
Scaling one component does not automatically scale the entire system.
Backend engineers must understand the system end to end.
4. Failure Is a Normal Part of Production Systems
Production systems fail.
Not only during major outages.
Small failures happen continuously.
Examples include:
- network timeouts;
- slow database queries;
- application restarts;
- container termination;
- DNS failures;
- unavailable external APIs;
- duplicate message delivery;
- connection resets.
Failure is therefore not something that happens outside the normal operation of a distributed system.
It is part of normal operation.
Consider:
Order Service
│
▼
Payment Service
│
▼
Inventory Service
Suppose payment succeeds.
Then the inventory operation fails.
What should the system do?
Possible options include:
Refund the payment
Retry inventory reservation
Keep the order pending
Cancel the order
Send the case for manual review
Which option is correct?
There is no universal answer.
It depends on the business requirements.
For one system, immediately refunding the customer may be appropriate.
For another, retrying inventory reservation may be preferred.
Another business may require manual review.
This leads to an important principle:
Failure handling is part of architecture.
It is not an optional feature to add after the main implementation is finished.
5. Concurrency Can Break Correct Code
Consider this inventory table:
product_id | available_quantity
-----------|-------------------
P123 | 1
Only one item remains.
Two customers attempt to purchase it at almost exactly the same time.
Request A reads:
available_quantity = 1
Request B also reads:
available_quantity = 1
Both requests conclude that the item is available.
The application code might look perfectly reasonable:
if (product.getAvailableQuantity() > 0) {
product.decreaseQuantity();
repository.save(product);
}
If you execute this code once, it may behave correctly.
But if two requests execute it concurrently, the result may be wrong.
That is the concurrency problem.
Code that is correct when executed sequentially can become incorrect when executed concurrently.
Later in the course, we will study concepts such as:
- transactions;
- isolation levels;
- optimistic locking;
- pessimistic locking.
For now, the important point is simply to recognize concurrency as a first-class engineering concern.
6. Backend Systems Manage State
Backend systems ultimately manage state.
Examples include:
- orders;
- payments;
- inventory;
- account balances;
- subscriptions;
- identities;
- permissions.
Incorrect state can be more dangerous than an obvious outage.
Consider:
Expected balance = €100
Actual balance = €150
The servers may still be running.
CPU usage may look completely normal.
Health checks may be green.
The API may still return:
200 OK
But the system is wrong.
This is why the backend engineer's responsibility is not limited to asking:
"Is the API running?"
The more important question is:
"Is the system's state correct?"
A healthy process does not necessarily mean a healthy business system.
7. Systems Become More Complex Over Time
Production systems are not static.
They evolve.
Suppose the first version of an API returns:
{
"id": "123",
"name": "Laptop"
}
A new requirement introduces a different field:
{
"id": "123",
"displayName": "Laptop"
}
At first glance, this appears to be a simple rename.
But consider what may already depend on the original field:
- another backend service may still expect
name; - an older mobile application may still expect
name; - previously produced Kafka events may contain
name; - an older background worker may still process the original schema.
Suddenly, a simple rename is no longer simple.
Production systems therefore require engineers to think about:
Backward compatibility
Schema evolution
Migration
Deprecation
Rollback
Deployment order
Architecture is not only about designing the desired final state.
It is also about:
Safely moving the system from today's state to tomorrow's state.
The migration path is part of the architecture.
8. Architecture Becomes More Important as Teams Grow
When one engineer owns an entire application, coordination is relatively simple.
Now imagine an organization with separate teams for:
Accounts
Catalog
Orders
Payments
Inventory
Shipping
Suppose the Orders team changes an event contract:
Orders change
↓
Inventory breaks
Or the Payments team changes an API:
Payments change
↓
Checkout breaks
If every team directly reads and modifies every other team's database, ownership becomes unclear.
If every change requires coordination across several teams, development becomes increasingly difficult.
Architecture helps define:
- who owns which system;
- who owns which data;
- which interfaces are stable;
- where dependencies are allowed;
- which teams can deploy independently.
Architecture is therefore not only about software components.
It is also connected to how people and teams work together.
9. Advanced Engineers Do Not Start With Technology
A common question is:
Which database is best?
That question is usually too broad to be useful.
A better question is:
What does this workload require?
Before choosing a database, we should ask questions such as:
Do we need transactions?
How important is durability?
What are the access patterns?
How much data will we store?
Is the workload read-heavy or write-heavy?
Can the application tolerate stale data?
What latency target do we have?
How much operational complexity can the team support?
Only after understanding the requirements should we evaluate the technology.
The same principle applies to:
Kafka
Redis
Microservices
Kubernetes
Temporal
NoSQL databases
Do not begin with:
We should use Kafka.
Begin with:
What problem are we trying to solve?
Then evaluate whether Kafka is an appropriate solution.
Technology should follow requirements.
Not the other way around.
10. Every Solution Introduces New Problems
Suppose database reads are becoming slow.
You introduce Redis.
Before:
Application
│
▼
PostgreSQL
After:
Application
│
▼
Redis
│
Cache miss
▼
PostgreSQL
Latency improves.
But now another problem appears.
Suppose PostgreSQL contains:
Price = €100
while Redis still contains:
Price = €90
Which value should the customer see?
The system improved:
Performance
but introduced another challenge:
Consistency
This pattern appears repeatedly in backend engineering:
Solving one problem often introduces a new trade-off.
Good engineering therefore requires evaluating both what a solution provides and what it costs.
11. Microservices Are Not Automatically Better
Microservices can provide useful properties such as:
- independent deployment;
- clearer team ownership;
- independent scaling;
- domain separation.
But those benefits come with costs:
- network failures;
- distributed transactions;
- additional operational overhead;
- more complicated observability;
- deployment complexity;
- cross-service debugging.
Therefore:
Using microservices does not automatically make an architecture advanced.
In many situations, a well-designed modular monolith may be the better engineering decision.
Advanced engineering does not mean choosing the most complex architecture.
It means choosing the architecture that best fits the constraints.
12. Architecture Is Trade-Off Management
Suppose an engineer says:
We should use Kafka because Kafka is scalable.
That is weak architectural reasoning.
A stronger explanation would be:
We require durable event retention, replay, partition-level ordering, and multiple independent consumers. Kafka provides those properties, but it also introduces additional infrastructure and operational complexity.
The second explanation considers both:
Benefits
+
Costs
That is architectural thinking.
Instead of asking:
Is Kafka good?
Ask:
What problem does Kafka solve for us, and what complexity do we accept in return?
The same reasoning applies to almost every major architectural decision.
13. The Definition of "Done" Changes in Production
For a simple application, "done" might mean:
✓ Code works
✓ Tests pass
✓ API responds correctly
For a production system, the definition is larger:
✓ Functional requirements work
✓ Concurrent operations remain correct
✓ Retries are safe
✓ Failure behavior is understood
✓ Useful logs exist
✓ Important metrics exist
✓ Deployment is safe
✓ Rollback is possible
✓ Database migrations remain compatible
✓ Capacity limits are understood
Production readiness is therefore not simply a property of application code.
It is a property of the entire system.
14. A Backend Engineer's Mental Checklist
Throughout this course, we will repeatedly use a small set of questions when reasoning about backend systems.
Correctness
Can this operation create an invalid business state?
Failure
What happens when this dependency fails?
Concurrency
What happens when this operation executes simultaneously?
Scale
What happens if traffic increases by 100×?
Data
Where is the source of truth?
Consistency
How stale can this data safely become?
Recovery
Can the system recover from partial failure?
Observability
How would we know that this system is broken?
Evolution
Could this change break an existing consumer?
Operations
Could another engineer understand what is happening during an incident?
Developing the habit of asking these questions is one of the foundations of advanced backend engineering.
Practical Exercise — The Simple Order System
Consider this architecture:
Client
│
▼
Order API
│
▼
PostgreSQL
The endpoint is:
POST /orders
Request:
{
"productId": "P123",
"quantity": 1
}
The application performs four operations:
1. Check available inventory
2. Create the order
3. Reduce inventory
4. Return the order
Assume the current traffic is:
100 requests/second
Now analyze the following scenarios.
Scenario 1 — Concurrent Purchase
Two customers attempt to purchase the final item at the same time.
Ask yourself:
What could go wrong?
Do not design the solution yet.
First, identify the engineering problem.
Scenario 2 — Client Retry
The server successfully creates an order.
Before the response reaches the client, the network connection disappears.
The client retries the request.
Ask:
What incorrect business state could be created?
Again, focus on identifying the problem rather than selecting a technology.
Scenario 3 — Slow Database
Database queries normally complete in:
20 ms
They suddenly begin taking:
5 seconds
Consider:
- What happens to incoming requests?
- What happens to application resources?
- How could this affect the user experience?
- Could this problem spread to other parts of the system?
Scenario 4 — Traffic Spike
Traffic increases from:
100 RPS
to:
10,000 RPS
Which components might become bottlenecks?
Again, do not immediately jump to technology.
First identify where pressure could appear.
Scenario 5 — Introducing Redis
Redis is added to reduce database reads.
Now ask:
What new consistency problems might appear?
Think about what happens when the cached representation and the database representation no longer contain the same value.
Exercise Format
For each scenario, answer three questions:
1. What can fail or behave incorrectly?
2. What could the business impact be?
3. What information would you need before choosing a solution?
The purpose of this exercise is not to select technology.
The purpose is to develop the habit of:
Understanding the problem before selecting the solution.
Key Takeaways
The most important ideas from this lesson are:
1. Working Code Is Not Enough
A production system must remain correct under failure, retries, concurrency, and change.
2. Scale Changes Assumptions
An architecture that works well at 100 RPS may behave very differently at 100,000 RPS.
3. Failure Is Normal
Production systems should be designed with failure in mind rather than assuming every dependency will always work.
4. Concurrency Changes Correctness
Code that works correctly when executed sequentially can become incorrect when executed concurrently.
5. Systems Evolve
Backward compatibility, migration, and safe evolution are fundamental engineering concerns.
6. Architecture Is Connected to Team Structure
Ownership, boundaries, and dependencies affect both software and organizations.
7. Every Solution Has a Cost
Redis, Kafka, microservices, and other architectural choices solve problems while introducing new forms of complexity.
8. Architecture Is About Trade-Offs
The important question is not:
Which technology is best?
The better question is:
Which trade-offs are appropriate for the requirements and constraints of this system?
Next Lesson
Lesson 2 — Understanding the Request Lifecycle
In the next lesson, we will follow a request through a backend system:
Client
↓
Load Balancer
↓
Application
↓
Cache
↓
Database
↓
External Dependencies
We will examine:
- where latency is introduced;
- where failures can occur;
- where resources are consumed;
- and why understanding the complete request lifecycle is essential before attempting to optimize a backend system.
