Every Role I've Played — Junior to CTO, What I Actually Did at Each Level

I stopped being evaluated on lines of code. I was evaluated on decisions — what architecture to use, what trade-offs to accept, and how to guide other developers. This is the real story of designing IoT telemetry for 50K events/sec, microservices for Australia's largest insurer, and the stack decision that became my most-read article.

"You don't become a senior engineer by writing more code. You become one by making decisions that outlive you." — Me, after breaking production for the 47th time
The jump from mid-level to senior is the most disorienting transition in engineering. Your mid-level years taught you to ship features. Now, nobody cares how fast you personally ship. They care whether the architecture you chose survives three years of growth, whether the trade-offs you documented save the next team six months of rediscovery, and whether the junior engineers you mentored can ship without you.
Before (Mid-Level): "Can you own a feature end-to-end and ship it reliably?" Now (Senior): "Can you make architectural decisions that multiple teams will follow — and be right about the trade-offs 80% of the time?"
I stopped being evaluated on lines of code. I was evaluated on decisions — what architecture to use, what trade-offs to accept, and how to guide other developers without writing their code for them.
Here is what that actually looked like in practice.
Role: Backend Engineer & System Architect Stack: Go, MQTT, Kafka, TimescaleDB, React Scale: 50K telemetry events/second, 100+ heavy equipment units across 3 remote mining sites
Petrosea's mining operations had a problem: 100+ dump trucks, excavators, and support vehicles operating across remote sites in Kalimantan generated continuous telemetry — GPS position, engine temperature, fuel consumption, payload weight — every 2 seconds. The data was critical for real-time dispatch decisions, geofence alerts, and predictive maintenance. But the sites had intermittent satellite connectivity that made HTTP-based architectures unreliable.
I designed a real-time geofencing engine capable of processing 50K events per second with sub-2-second alert latency. The core architectural choice was the data pipeline:
Problem: 100+ mining trucks across remote sites generating GPS/engine data every 2 seconds. Intermittent satellite connectivity — sometimes 6-8 hours of complete blackout. Data must reach HQ dashboard within 5 seconds for real-time geofence alerts.
Architecture Decision:
Edge Gateway (Go) → MQTT (QoS 1) → Kafka → Stream Processor → TimescaleDB → WebSocket → React Dashboard
↓
Geofence Engine (< 2s alerts)Component Breakdown:
Edge Gateway (Go): Runs on Raspberry Pi-class hardware mounted on each truck. Local SQLite buffer for offline periods. Publishes telemetry via MQTT when connectivity is available. Handles protocol translation from various OEM sensors (Caterpillar, Komatsu, Hitachi) into a normalized schema.
MQTT Broker (Mosquitto): Chosen explicitly for its lightweight protocol, QoS levels (QoS 1 for at-least-once delivery), and persistent sessions that survive disconnection. When a truck emerges from a blank spot after 6 hours, MQTT's session state re-establishes and the backlog drains without developer intervention.
Kafka Stream Processor: Decouples ingestion from processing. Handles the "thundering herd" problem when 50 trucks reconnect simultaneously — Kafka buffers the burst while downstream consumers process at their own pace.
TimescaleDB: Time-series optimized PostgreSQL extension. Chosen over InfluxDB because we needed SQL JOINs between telemetry and equipment metadata. 10x query performance improvement over vanilla PostgreSQL for time-range queries.
Geofence Engine: In-memory spatial index using R-tree. Processes incoming GPS coordinates against predefined geofence polygons. Sub-second detection triggered alerts via WebSocket push — no polling required.
Key Trade-Off: MQTT over HTTP. HTTP polling at 50K events/sec would have required 50 requests per second minimum, each with TLS handshake overhead. On satellite links with 600ms+ latency, the retry storm alone would have collapsed the pipeline. MQTT's persistent TCP connection with QoS 1 and session state eliminated this entirely.
What I'd Change: Add a dead-letter queue for unprocessable messages and implement backpressure signaling from Kafka to MQTT. We lost ~0.3% of telemetry in the first month from Kafka consumer lag during peak hours — a backpressure mechanism would have prevented this.
| Metric | Before | After |
|---|---|---|
| Telemetry processing | Manual/log-based | Real-time, 50K events/sec |
| Geofence alert latency | 5-10 minutes | < 2 seconds |
| Fuel theft detection | Unknown (undetected) | Caught in month one |
| Annual savings | — | Rp 3.2B ($200K) |
| Fleet utilization visibility | Paper-based shift reports | Live dashboard, 65% → 79% |
Role: Frontend & Backend Engineer Stack: Nuxt.js (SSR), Go microservices, PostgreSQL, D3.js Scale: 5+ partner integrations, digitized entire insurance lifecycle for Australia's largest insurer
IAG (Insurance Australia Group) — Australia's largest general insurer, ~AU$12B market cap — needed a platform to serve the underserved SME cyber insurance market. The existing process took 3-4 days for a quote, involved 100+ manual questions, and required underwriter back-and-forth via email. We had to digitize the entire lifecycle: risk assessment → quoting → binding → monitoring → claims.
Business Requirement: Australian insurance brokers search for "cyber insurance quote" on Google. If our quoting flow isn't indexable by search engines, brokers won't find us — and they'll go to competitors.
Decision: Nuxt.js Server-Side Rendering (SSR) over a React Single Page Application (SPA).
Why Not React SPA:
/quote/business-profile?industry=tech&employees=50 — each URL needs to be indexable with unique meta tagsWhy Nuxt.js SSR:
Architecture Overview:
Browser → Nuxt.js SSR → Go API Gateway → Microservices:
├── Risk Assessment Service (vulnerability scanning integration)
├── Quoting Engine (premium calculation + rules engine)
├── Policy Management Service (binding + document generation)
├── Partner Integration Layer (UpGuard, Acronis, RHCS)
└── Notification Service (email, dashboard alerts)Trade-Off Accepted: SSR introduces higher server cost per request compared to a static SPA. Each page render requires a Node.js server round-trip. We mitigated this with Redis page caching for public pages (rate pages, FAQ, blog) and reserved dynamic SSR only for authenticated quoting flows.
Result: The platform was fully indexed by Google within 2 weeks of launch. Organic traffic from broker searches contributed 40% of new quoting sessions in the first quarter. The digitized quoting flow reduced time-to-quote from 3-4 days to under 15 minutes.
| Metric | Before | After |
|---|---|---|
| Quote turnaround | 3-4 days | < 15 minutes |
| Insurance lifecycle | Manual/paper | Fully digitized |
| Partner integrations | None | 5+ (UpGuard, Acronis, RHCS, etc.) |
| Platform reach | Australia only | Asia Pacific (Singapore, Malaysia, Hong Kong) |
Role: Technology Decision-Maker Stack: Go (chosen), evaluated against Node.js, Python, Rust
Every senior engineer eventually faces the question: "What's your primary stack and why?" This isn't a trivia question — it's a test of your ability to evaluate trade-offs across multiple dimensions: performance, developer productivity, ecosystem maturity, hiring market, and long-term maintenance cost.
I evaluated four options against five criteria:
| Criterion | Go | Node.js | Python | Rust |
|---|---|---|---|---|
| Concurrency model | Goroutines (built-in) | Event loop (single-threaded) | GIL-limited | Async/await (complex) |
| Compilation speed | < 2 seconds | N/A (interpreted) | N/A (interpreted) | 30-120 seconds |
| Deployment | Single binary | Node + node_modules | Python runtime + pip | Single binary |
| Learning curve | Low (25 keywords) | Low | Low | Steep (ownership, lifetimes) |
| Hiring in Indonesia | Growing rapidly | Mature | Mature | Niche |
The Decision: Go won on simplicity, compilation speed, and deployment ergonomics — not raw performance. A Rust advocate could correctly point out that Rust outperforms Go in CPU-bound workloads by 2-5x. But raw CPU throughput was rarely my bottleneck. Network I/O, database queries, and message queue throughput were. Go's goroutine-based concurrency model handles those perfectly.
Every senior-level decision in my career followed the same pattern: identify the real constraint (not the hypothetical one), evaluate trade-offs explicitly, and choose the option that optimizes for the bottleneck — whether that's developer productivity, deployment simplicity, or SEO requirements.
Beyond individual projects, the senior role demanded that I document architecture decisions so they could be understood, challenged, and extended by other engineers. Here are two key diagrams that represent the most complex architectural problems I solved.
Problem Statement: 100+ mining trucks generating 50K telemetry events/second across 3 remote sites with intermittent satellite connectivity. Data must reach HQ dashboards in under 5 seconds for real-time dispatch and safety alerts.
Architecture:
┌─────────────────┐ ┌──────────┐ ┌──────────┐ ┌───────────────┐
│ Edge Gateway │────▶│ MQTT │────▶│ Kafka │────▶│ TimescaleDB │
│ (Go + SQLite) │ │ Broker │ │ Stream │ │ (Time-Series)│
│ Local Buffer │ │ QoS 1 │ │ Cluster │ │ │
└─────────────────┘ └──────────┘ └──────────┘ └───────┬───────┘
│ │
│ Offline: Local SQLite │
│ Online: MQTT publish │
│ (QoS 1, persistent session) │
│
┌────────────────┐ ┌──────────────────┐ ┌─────────────────┐│
│ Geofence │◀────│ Stream Processor│◀────│ Kafka Consumer ││
│ Engine (R-Tree)│ │ (Go) │ │ (Go) ││
│ < 2s alerts │ │ + Alerting │ └─────────────────┘│
└────────────────┘ └──────────────────┘ │
│
┌────────────▼───────┐
│ WebSocket Server │
│ (Go) │
└────────────┬───────┘
│
┌────────────▼───────┐
│ React Dashboard │
│ (Real-Time Maps) │
└────────────────────┘Decision: Edge gateway with local SQLite buffer → MQTT publish (QoS 1) → Kafka stream processing → TimescaleDB → WebSocket push to React.
Trade-Offs Considered:
Problem Statement: 5,000+ field agents working in areas with zero internet connectivity. They fill dynamic forms, capture photos, and collect digital signatures. All data must sync without loss when connectivity returns. Previous Microsoft Power Apps solution had 23% data loss rate.
Architecture:
┌──────────────────┐ ┌──────────┐ ┌──────────────────┐
│ Flutter Mobile │────▶│ MQTT │────▶│ Go API Gateway │
│ App │ │ Broker │ │ │
│ ┌────────────┐ │ │ QoS 1 │ │ ┌────────────┐ │
│ │ SQLite DB │ │ └──────────┘ │ │ CRDT │ │
│ │ (Local) │ │ │ │ Resolution │ │
│ ├────────────┤ │ ┌──────────┐ │ ├────────────┤ │
│ │ Change Log │ │────▶│ MQTT │────▶│ │ PostgreSQL │ │
│ │ Table │ │ │ Batch │ │ │ (Source of │ │
│ └────────────┘ │ │ Upload │ │ │ Truth) │ │
└──────────────────┘ └──────────┘ └──────────────────┘
│
│ Offline: Write to SQLite + change log
│ Online: Push change log via MQTT
│ Reconnect: Pull server changes, apply CRDT mergeDecision: SQLite local DB → change log table → MQTT push when online → server-side conflict resolution (last-write-wins with timestamp + device ID vector).
Trade-Offs Considered:
Result: 0% data loss (from 23% in the previous system). 4.1M end-users served.
| Portfolio Item | Did I Do It? | Evidence |
|---|---|---|
| System architecture diagrams | ✅ Documented across multiple case studies with trade-off analysis | |
| Trade-off decisions with rationale | ✅ Go vs Rust, MQTT vs HTTP, SSR vs SPA — each with explicit evaluation matrix | |
| Mentorship impact | ⚠️ Informal mentoring only — never created a structured program | |
| Speaking/writing | ✅ 20+ published technical articles including most-read Go stack analysis | |
| Cross-team architecture influence | ✅ Designed patterns adopted by 3+ product teams (form engine, telemetry pipeline) | |
| Production incident leadership | ✅ Led RCA for critical IoT incidents, documented runbooks for ops handover | |
| ADR / technical documentation | ⚠️ Inconsistent — decisions documented in case studies but not formal ADRs in every case |
The informal mentorship column is my biggest gap. I guided juniors and mid-level engineers on their code reviews and architecture decisions, but I never created a structured mentorship framework. Looking back, that was a missed opportunity — the engineers I informally mentored grew faster, but the organization couldn't replicate the pattern because it wasn't documented.
The difference between a senior and a mid-level isn't making perfect decisions — it's being able to articulate why you made a decision, what alternatives you considered, and when you'd revisit it. Start writing ADRs (Architecture Decision Records) for every non-trivial technical choice. The Go-stack analysis I published became my most-read article not because it was technically impressive, but because it showed the evaluation matrix — the exact same format you need for your senior portfolio.
You don't become a senior by knowing more. You become one by making others more effective. Start with one junior engineer. Code review with explanation ("I'd do X because Y, but your approach works if Z"). Pair program on a complex feature. Write internal documentation that unblocks the team. The compound interest of mentoring pays off faster than any individual feature you could build.
Every architecture decision, trade-off analysis, or production postmortem you write is an asset. It validates your thinking for future employers, helps other engineers facing the same problems, and builds recognition that makes senior roles easier to secure. I wrote 20+ technical articles during my senior years — and every single one of them contributed more to my career trajectory than any single feature I shipped.
Mid-level engineers optimize for "does it work." Senior engineers optimize for "does it survive." Latency, throughput, error budgets, MTTR, deployment frequency — these aren't DevOps metrics. They're senior engineering metrics. Start measuring before you optimize. The "$200K annual savings" from Petrosea exists because I instrumented the system from day one and could point to specific improvements with specific dollar values.
Senior engineering is not the ceiling. It's the launching pad. The architecture decisions you make at this level outlive your tenure. The engineers you mentor outgrow your influence. The trade-offs you document save future teams years of rediscovery. Your code compiles and deploys. But your decisions compound — and that's what makes a senior engineer.



