Imagine you're building a file upload service.
At first, it seems simple. The client sends a file to your API.
The API receives it.
Then the API uploads it to object storage.
Done.
Until your files become large.
Now every upload is passing through your API server.
A 5 GB file means your server has to receive 5 GB of data and then send 5 GB of data again.
Your API isn't doing useful application work anymore.
It's becoming a very expensive pipe.
So the first improvement is obvious:
Let the client upload directly to object storage.
Your API generates a presigned URL.
The client uses that URL to upload the file directly to the private S3 bucket.
Now the API server doesn't handle the file bytes.
Much better.
But there's another problem.
What happens when someone uploads a 10 GB file and the connection fails at 8 GB?
Do you start from zero?
That wastes bandwidth and time.
So the next step is multipart upload.
Split the file into smaller parts.
Upload those parts independently.
If one part fails, retry only that part.
Now large uploads can survive temporary network failures without restarting the entire upload.
But we've created another problem.
The upload itself is no longer the only thing happening.
Once the file reaches storage, you may need to process it.
Generate metadata.
Scan it.
Transcode it.
Create thumbnails.
Validate it.
Extract information.
You don't want the client waiting for all of that.
So instead of doing the processing inside the API request, push a job into a queue.
An asynchronous worker picks it up and processes the file in the background.
Now the architecture starts to look different.
The API handles control-plane work.
The object store handles file data.
The queue handles asynchronous work.
Workers handle processing.
And the CDN handles delivery.
There's one more optimization.
If users repeatedly download the same files, don't make every request hit your private bucket.
Put CloudFront in front of it.
The CDN caches frequently accessed objects closer to users.
Now your storage doesn't have to serve every download directly.
The final architecture becomes:
Client โ API for metadata and upload authorization.
Client โ S3 for the actual file upload.
S3 โ Queue โ Worker for asynchronous processing.
Client โ CDN โ S3 for file delivery.
The important lesson is:
Don't make your application server carry data that it doesn't need to understand.
Let each component do one job.
API servers handle logic.
Object storage handles bytes.
Queues handle work.
Workers handle processing.
CDNs handle delivery.
That's how a simple file upload turns into a system that can scale without making your API server the bottleneck.
Imagine you build a platform with millions of users.
Some are real humans. Some are bots.
Some are coordinated bot networks pretending to be humans.
Now you need to answer a simple question:
"Is this account actually human?"
The obvious approach is to read what the user posts.
Look for spam. Look for repeated text.
Look for suspicious words.
But there's a problem.
Content is becoming a terrible signal.
A bot can generate perfectly normal-looking text.
It can reply like a human.
It can even use AI to rewrite everything it posts.
So instead of asking:
"What did this account say?"
Ask:
"How does this account behave?"
That's where behavioral detection becomes interesting.
A human doesn't interact with a platform like a script.
You open the app. Scroll. Stop. Read something.
Maybe like it.
Ignore the next few posts.
Come back later.
Your behavior is messy.
A bot is often much more predictable.
Follow.
Like.
Reply.
Follow.
Like.
Reply.
Again.
And again.
The timing is consistent. The sequence is repetitive.
The activity can continue for hours.
So instead of feeding the actual post content into a model, you can feed the model the sequence of actions.
What did the user do? When did they do it?
What action came next?
How frequently did they repeat it?
A sequence model can then estimate:
"How likely is this behavior to belong to a bot?"
That score can be passed into a policy layer.
Low risk? Do nothing. Suspicious?
Ask for additional verification.
Highly suspicious?
Limit or block the account.
The interesting part is that the system doesn't need to understand what you said.
It only needs to understand how you behave.
And that's a powerful idea beyond social media:
When content becomes easy to imitate, behavior becomes a much stronger signal of identity.
Imagine you're opening a website on your phone.
You connect to Wi-Fi. Your browser connects to the server.
Everything works.
Then you leave your house.
Your phone switches from Wi-Fi to 5G.
Your IP address changes.
And suddenly, the connection you were using is gone.
Why?
Because the internet was built around TCP.
TCP is extremely good at one thing:
Reliability.
It makes sure packets arrive, keeps them in order, and retransmits what gets lost.
But that reliability comes with a cost.
Before your HTTP request can even begin, you have to establish the connection.
Then TLS adds another handshake.
Then your actual HTTP request finally gets sent.
Every extra round trip adds latency.
HTTP/2 fixed another major problem.
Multiple requests could now share one TCP connection.
Much better.
But TCP still had a fundamental limitation.
If one packet was lost, streams sharing that connection could be forced to wait.
The transport layer didn't understand that the streams were independent.
And there was another problem.
Change your network.
Your connection breaks.
So engineers started asking a different question:
What if we kept the speed and simplicity of UDP, but built reliability ourselves?
That's where QUIC came in.
Instead of TCP + TLS, QUIC runs over UDP and builds reliability, encryption, streams, and connection management into the protocol itself.
The result became the foundation for HTTP/3.
One connection.
Multiple independent streams.
No TCP head-of-line blocking.
And a connection can survive a network change because it's identified by a connection ID rather than being tied entirely to the old network path.
The interesting part isn't that HTTP/3 replaced HTTP/2.
It's that the transport layer itself had become the bottleneck.
Imagine you're opening a website on your phone.
You connect to Wi-Fi. Your browser connects to the server.
Everything works.
Then you leave your house.
Your phone switches from Wi-Fi to 5G.
Your IP address changes.
And suddenly, the connection you were using is gone.
Why?
Because the internet was built around TCP.
TCP is extremely good at one thing:
Reliability.
It makes sure packets arrive, keeps them in order, and retransmits what gets lost.
But that reliability comes with a cost.
Before your HTTP request can even begin, you have to establish the connection.
Then TLS adds another handshake.
Then your actual HTTP request finally gets sent.
Every extra round trip adds latency.
HTTP/2 fixed another major problem.
Multiple requests could now share one TCP connection.
Much better.
But TCP still had a fundamental limitation.
If one packet was lost, streams sharing that connection could be forced to wait.
The transport layer didn't understand that the streams were independent.
And there was another problem.
Change your network.
Your connection breaks.
So engineers started asking a different question:
What if we kept the speed and simplicity of UDP, but built reliability ourselves?
That's where QUIC came in.
Instead of TCP + TLS, QUIC runs over UDP and builds reliability, encryption, streams, and connection management into the protocol itself.
The result became the foundation for HTTP/3.
One connection.
Multiple independent streams.
No TCP head-of-line blocking.
And a connection can survive a network change because it's identified by a connection ID rather than being tied entirely to the old network path.
The interesting part isn't that HTTP/3 replaced HTTP/2.
It's that the transport layer itself had become the bottleneck.
Currently building an uptime monitoring SaaS.
Rather than having one service do everything, I split the system into independent pipelines:
This keeps monitoring reliable under load and prevents notification spikes from affecting check execution.
Architecture โ
1. Scheduler runs every minute and elects a leader.
2. Due checks are pushed to a BullMQ queue.
3. Stateless workers consume jobs and probe external targets.
4. Monitor state + execution results persist in Postgres.
5. Redis powers queues, caching, and coordination.
6. Status transitions generate alert events.
7. Dedicated notification workers deliver Email, Slack, and Webhook alerts.
Queues decouple everything. Workers scale horizontally. Failure in one stage doesn't bring down the pipeline.
Currently building an uptime monitoring SaaS.
Rather than having one service do everything, I split the system into independent pipelines:
This keeps monitoring reliable under load and prevents notification spikes from affecting check execution.
Architecture โ
1. Scheduler runs every minute and elects a leader.
2. Due checks are pushed to a BullMQ queue.
3. Stateless workers consume jobs and probe external targets.
4. Monitor state + execution results persist in Postgres.
5. Redis powers queues, caching, and coordination.
6. Status transitions generate alert events.
7. Dedicated notification workers deliver Email, Slack, and Webhook alerts.
Queues decouple everything. Workers scale horizontally. Failure in one stage doesn't bring down the pipeline.