07 — Software Engineering
From silicon-near C to edge TypeScript
I pick the language for the failure mode I am trying to avoid.
Rust where concurrency must be provably correct, Go where network services have to stay simple, C near the hardware, Python and TypeScript where models and people meet the system — chosen deliberately, operated at depth.
The honest measure of an engineer is not the stack on a résumé — it is what has survived contact with real users. Mine is eleven years of that.
For more than eleven years I have shipped mobile applications to real users across iOS, Android, and Windows. That number is the credential I trust most, because shipping to a device in someone's pocket is unforgiving: there is no staging environment for a crash on a stranger's phone, no rollback for a feature that confuses the person using it. A decade of that teaches a specific discipline — version fragmentation, offline behaviour, battery and memory budgets, and the slow accumulation of taste about what to leave out.
Behind every one of those apps was a backend and the cloud infrastructure underneath it. So the practice is not front-of-glass alone; it runs from the touch event down through the API, the queue, the datastore, and the container, to the kernel the container runs on. I have spent enough time at each layer to know where the seams are, and to design so the seams do not tear under load.
What I have added since is depth in systems languages and multi-cloud delivery: Rust and Go for the cores that have to be correct and fast, Docker and Kubernetes for delivery, and a deliberate refusal to lock any of it to a single provider. The rest of this page is the specific shape of that — languages, platforms, clouds, and the small tools I build for myself along the way.
years shipping mobile applications to real users
mobile platforms in production: iOS, Android, Windows
clouds operated without vendor lock-in: GCP, Azure, AWS
primary languages held at depth: Rust, Go, Python, TypeScript
A stack chosen by layer, not by fashion.
Systems-language layer diagram
Each language earns its place on the layer where its guarantees matter.
I do not reach for one favourite language and bend every problem toward it. The diagram reads from the silicon up: C and C++ where the code touches hardware, Rust for the memory-safe concurrent cores, Go for the concurrent network layer, then Python and TypeScript at the surface where AI, data, and users live.
The bar widths are a rough sense of breadth of use across my work — not a benchmark, just where the volume of code tends to sit.
- Lower layers buy determinism and safety
- Upper layers buy velocity and reach
- The boundary is a deliberate choice, made per project
What the code actually looks like.
A bounded worker pool in Go — fan out work across goroutines, collect results through a channel, and respect cancellation. The pattern shows up under almost every network service I write.
// FanOut runs fn across at most n goroutines, streaming results
// back on a channel and stopping early if ctx is cancelled.
func FanOut[T, R any](ctx context.Context, in []T, n int, fn func(T) R) []R {
jobs := make(chan int)
out := make([]R, len(in))
var wg sync.WaitGroup
for w := 0; w < n; w++ {
wg.Add(1)
go func() {
defer wg.Done()
for i := range jobs {
select {
case <-ctx.Done():
return
default:
out[i] = fn(in[i])
}
}
}()
}
for i := range in {
select {
case <-ctx.Done():
close(jobs)
wg.Wait()
return out
case jobs <- i:
}
}
close(jobs)
wg.Wait()
return out
} Where the engineering actually goes.
Choosing the language for the failure mode I am avoiding.
Rust is where correctness under concurrency is non-negotiable. The borrow checker is not a tax — it is a compile-time proof that a class of data races and use-after-free bugs cannot occur, which is exactly what I want for a performance-critical core that has to run unattended.
Go is where I want concurrent network services I can reason about quickly: goroutines and channels for the concurrency model, the built-in pprof profiler for finding the hot path, and a static binary that deploys cleanly into a container. C and C++ stay reserved for the hardware-near work — firmware, embedded SoC, the code that sits directly on the register map.
- Rust — ownership, lifetimes, zero-cost abstractions, fearless concurrency
- Go — goroutines, channels, context cancellation, pprof profiling
- C / C++ — embedded firmware, hardware-near control, deterministic timing
- Production Bash and SQL across PostgreSQL and BigQuery
Eleven years of shipping to real devices, not simulators.
On iOS I work in Swift and Objective-C with SwiftUI for new surfaces, Core Data for persistence, Core Animation for motion, and SiriKit for voice intents. On Android it is Kotlin and Java against the Android SDK, with the version-fragmentation discipline that comes from supporting many OS versions in the field.
Cross-platform, I reach for React Native (advanced) or Flutter (advanced) when a shared codebase earns its keep, and I have shipped with Xamarin at a working level. Behind the apps sit real backends — Node/Express, Spring Boot, Django/Flask, Ruby on Rails, or Go with Gin and Gorm — exposing REST and GraphQL.
- iOS — Swift, Objective-C, SwiftUI, Core Data, Core Animation, SiriKit
- Android — Kotlin, Java, Android SDK across many OS versions
- Cross-platform — React Native, Flutter (advanced), Xamarin (working)
- Data — SQLite, Redis, ORMs (Sequelize, Mongoose); REST and GraphQL APIs
Portable by design, not married to one provider.
I keep workloads portable so the cloud stays a commodity rather than a dependency. On GCP I use Vertex AI, Cloud Run, Cloud Functions, GKE, Pub/Sub, BigQuery and Firestore. On Azure, AKS, Functions, Cognitive Services and Container Apps. On AWS, Lambda, ECS/EKS, S3, RDS, and SQS/SNS behind CloudFront.
At the edge I run Cloudflare Workers and Vercel Edge; for fast-moving products I lean on Supabase or Firebase as a backend. Everything ships through containers — Docker and Kubernetes — with CI/CD on GitHub Actions or GitLab CI and infrastructure defined as code, so an environment is reproducible rather than hand-built.
- GCP — Vertex AI, Cloud Run, Cloud Functions, GKE, Pub/Sub, BigQuery, Firestore
- Azure — AKS, Functions, Cognitive Services, Container Apps
- AWS — Lambda, ECS/EKS, S3, RDS, SQS/SNS, CloudFront
- Edge & BaaS — Cloudflare Workers, Vercel Edge, Supabase, Firebase
Distributed systems that stay correct while things move.
The backend work is where the systems thinking pays off. I design around message queues and event-driven architecture so components stay decoupled, and around real-time systems where WebSocket and socket-level communication carry state between client and server with low latency.
Service-to-service protocols, idempotency, and backpressure are first-class concerns, not afterthoughts. The goal is a topology that degrades gracefully — one service failing should slow the system, not stop it.
- Message queues and event-driven architecture
- Real-time systems over WebSocket and socket-level communication
- Service-to-service protocols, idempotency, backpressure
- Distributed coordination with graceful degradation
One application, three clouds, no lock-in.
A workload I keep portable: containers and infrastructure-as-code at the centre, deployable to whichever provider an engagement already lives in. The cloud is a commodity layer, not a dependency.
GCP
Vertex AI, Cloud Run, Cloud Functions, GKE, Pub/Sub, BigQuery, Firestore — my default for AI workloads and managed container delivery.
Azure
AKS, Functions, Cognitive Services, Container Apps — used where an organisation already lives in the Microsoft estate.
AWS
Lambda, ECS/EKS, S3, RDS, SQS/SNS behind CloudFront — the breadth of primitives for traditional production stacks.
Edge
Cloudflare Workers and Vercel Edge — logic that runs close to the user, for latency and global reach.
BaaS
Supabase and Firebase (Auth, Firestore, Realtime DB) — when a product needs a backend in days, not weeks.
Delivery
Docker and Kubernetes, GitHub Actions and GitLab CI, infrastructure as code — reproducible environments end to end.
The cloud should be a commodity I can swap, never a dependency that owns the product.
A continuous personal Linux toolchain
Every workflow friction becomes a small program.
I run Linux at administrator level — kernel tuning, sysctl hardening, systemd units, container runtimes, and the networking underneath. But the part that defines how I work is smaller: over the years I have written dozens of my own command-line utilities, one at a time, each born from a friction I hit more than once.
A task done twice is a candidate for a tool. The result is a workstation that fits my hands rather than the defaults — and, more importantly, the instinct of someone who builds the tool rather than tolerating the gap. That instinct is the same one that ends up shipping in the products.
- Kernel tuning, sysctl hardening, systemd, container runtimes, networking
- Dozens of self-authored CLI utilities, each solving a recurring friction
- The reflex of a tool-builder, not only a tool-user
What I bring to a build.
Engineering stack
- Primary systems langs
- Rust, Go, C/C++
- High-level langs
- Python, TypeScript/JS
- Mobile — native
- Swift, Obj-C, Kotlin, Java
- Mobile — cross
- React Native, Flutter
- Backends
- Node, Spring, Django, Rails, Go
- API styles
- REST, GraphQL
- Datastores
- PostgreSQL, BigQuery, SQLite, Redis
- Containers
- Docker, Kubernetes
- CI/CD
- GitHub Actions, GitLab CI
- Linux
- Administrator level
None of this is a wish-list. Each line is a tool I have shipped with — chosen, in a given project, because it was the right answer for that layer and that failure mode.
The through-line across all of it is the same temperament: build the system before the file, design for the seams, keep delivery reproducible, and refuse the dependencies that would own the product later.
Decoupled by message, not by hope.
A producer / broker / consumer topology
Components talk through a broker so one failure slows the system instead of stopping it.
When services call each other directly, a single slow dependency turns into a cascade. I design around an event broker instead: producers publish facts about what happened, the broker durably holds them, and consumers process at their own pace. The producer never blocks on the consumer, and a consumer that falls behind drains its backlog without dropping work.
The events become the source of truth for what occurred — a dead-letter queue catches what cannot be processed, retries are bounded and idempotent, and a new consumer can subscribe to the same stream without anyone changing the producer. That is the property I am buying: independent evolution of parts that never have to know about each other.
- Producers publish, consumers subscribe — no direct coupling
- Durable broker absorbs bursts; consumers process at their own rate
- Dead-letter queue and bounded, idempotent retries for failures
From a commit to production, with the artifact promoting forward.
A CI/CD pipeline I treat as non-negotiable infrastructure. The image is built once and promoted unchanged through every gate, so what runs in production is byte-for-byte what passed the tests.
Commit to canary
- 01 Commit Push to a branch. Pre-commit hooks run format and lint locally so the obvious is caught before CI ever spins up.
- 02 Build & test Container image built once, unit and integration tests run against it, coverage gate enforced. The same artifact promotes forward.
- 03 Scan Static analysis, dependency audit, and image vulnerability scan. A failing scan blocks the merge, not a human reviewer's memory.
- 04 Stage Deploy the signed image to a staging environment that mirrors production, run smoke tests and a short soak.
- 05 Promote Roll out to production behind a canary or blue-green switch, watch the metrics, and keep the previous version one command away.
State carried over a socket, with the failure modes handled.
A socket service mesh
A WebSocket gateway in front, stateful connections behind, a shared store for durability.
Real-time work lives or dies on the unglamorous parts: what happens when a client's connection drops mid-message, when a consumer falls behind the producer, when the same event arrives twice. I terminate WebSocket and socket connections at a gateway, route each one to a stateful handler, and keep durable state in a shared store so a reconnecting client can resume rather than restart.
Backpressure is explicit — bounded buffers with a defined drop-or-coalesce policy, never an unbounded queue that quietly eats memory until the process dies. Presence is tracked with heartbeats and TTL keys, and delivery is idempotent so an at-least-once transport does not become at-least-twice behaviour for the user.
- WebSocket / socket gateway terminates and routes connections
- Stateful per-connection handlers; shared store for durability
- Heartbeats, TTL presence, resume-on-reconnect, idempotent delivery
Real-time primitives
- Transport
- WebSocket, raw TCP sockets, gRPC streams
- Fan-out
- Pub/Sub, Redis channels, SSE for one-way
- Backpressure
- Bounded buffers, drop / coalesce policies
- Delivery
- Idempotency keys, at-least-once + dedupe
- Presence
- Heartbeats, TTL keys, reconnect with resume
- State
- Per-connection actor, shared store for durability
Two more snippets from the working stack.
Rust ownership and Go channels solve the same problem — shared state under concurrency — from opposite ends: one moves ownership so there is nothing to share, the other shares by communicating.
// Ownership moves; the compiler proves no use-after-move.
fn consume(buf: Vec<u8>) -> usize {
buf.len() // buf is owned here, freed at the end
}
fn main() {
let data = vec![1u8, 2, 3];
let n = consume(data);
// using 'data' here would not compile: it was moved.
println!('consumed {} bytes', n);
// Borrow instead of move when the caller keeps the value.
let kept = vec![4u8, 5];
let total: u8 = kept.iter().sum();
println!('{} still owns {} items', total, kept.len());
} // Share by communicating: a generator feeds a stage over a channel.
func gen(nums ...int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for _, n := range nums {
out <- n
}
}()
return out
}
func square(in <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for n := range in {
out <- n * n
}
}()
return out
} Logic that runs close to the user.
A TypeScript edge handler — the kind I deploy to Cloudflare Workers or Vercel Edge. It runs at the point of presence nearest the request, so a cache check or auth gate never pays a round-trip to a central region.
// Runs at the edge: serve from cache, fall through to origin once.
export default {
async fetch(req: Request, env: Env, ctx: ExecutionContext) {
const cache = caches.default;
const hit = await cache.match(req);
if (hit) return hit;
const res = await fetch(req);
if (res.ok && req.method === 'GET') {
const cached = new Response(res.body, res);
cached.headers.set('cache-control', 'public, max-age=60');
ctx.waitUntil(cache.put(req, cached.clone()));
return cached;
}
return res;
},
}; The right store for the access pattern.
I do not pick a datastore by habit. I pick it by the shape of the reads and writes it has to absorb, and accept that most real systems use more than one.
A relational store earns the default position because most data is relational and transactions are worth the cost. But the hot path that a cache serves, the analytical question that scans billions of rows, and the local-first persistence on a phone are three different problems — and pretending one engine answers all three is how systems get slow in ways that are hard to undo later.
The discipline underneath all of them is the same: schema lives as versioned, reversible migrations in the repo, indexes are designed against the actual query plan rather than guessed, and the queries on the hot path get read and hand-tuned even when an ORM wrote the first draft.
PostgreSQL
The default relational store — transactions, JSONB where it earns its place, and indexes designed against the query plan rather than guessed.
BigQuery
Analytical scale. Columnar storage and partitioning for the questions that scan billions of rows but run rarely.
SQLite
On-device and embedded persistence for mobile and edge — a single file, zero server, the right tool for local-first apps.
Redis
The hot path — caching, rate limiting, queues, ephemeral presence and locks where microseconds and TTLs matter.
ORMs
Sequelize, Mongoose, GORM where they speed delivery — but the generated SQL stays readable, and the hot queries get hand-written.
Migrations
Schema as versioned, reversible migrations checked into the repo, so a database is reproducible rather than archaeological.
The kernel the container runs on, treated as part of the product.
A defence-in-depth stack
Security as layers, from signed boot up to off-box audit, not a single firewall rule.
I administer Linux down to the kernel, and I treat the host as part of the attack surface rather than an inert place to run a container. The stack reads from the bottom up: a minimal signed boot and a sysctl baseline, least-privilege users with SSH keys and no passwords, a default-deny firewall, process isolation through systemd sandboxing and cgroups, and audit logs shipped off the box so a compromise cannot quietly erase its own trail.
None of this is exotic — it is the ordinary discipline of running a host as if it will be attacked, because eventually one is. The same instinct that builds a small CLI for a recurring friction builds a hardened base image once and reuses it everywhere, so the secure path is also the easy path.
- Signed boot, minimal kernel modules, sysctl baseline
- nftables default-deny, systemd sandboxing, seccomp, cgroups
- auditd and off-box log shipping; unattended security updates
How the stack accumulated, layer by layer.
No single year taught all of this. It accumulated the way a stack should — from the glass the user touches, downward, until the kernel was as familiar as the view.
- Years 1–3 Native mobile, end to end Swift and Objective-C on iOS, Kotlin and Java on Android. Learned the discipline of shipping to devices in the field — fragmentation, offline, battery and memory budgets.
- Years 3–6 Backends behind the apps Node, Spring, Django and Rails serving REST and GraphQL, backed by PostgreSQL and Redis. The app stopped ending at the API and started at the kernel.
- Years 6–9 Cross-platform and cloud React Native and Flutter where a shared codebase paid off; containers, Kubernetes and multi-cloud delivery so the infrastructure became reproducible.
- Years 9–today Systems and distributed depth Rust and Go for cores that must be correct and fast, event-driven and real-time topologies, and a continuous personal Linux toolchain underneath all of it.
Open to the right work
If you need one engineer who can hold the whole stack — from the kernel to the cloud — that is the work I do.
If you are holding a problem that doesn't fit inside one field, that is the conversation I want.