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 credential

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.

0+

years shipping mobile applications to real users

0

mobile platforms in production: iOS, Android, Windows

0

clouds operated without vendor lock-in: GCP, Azure, AWS

0

primary languages held at depth: Rust, Go, Python, TypeScript

The language stack

A stack chosen by layer, not by fashion.

surface — users, models, data TypeScript / JS Node, browser, edge. Python AI, data, scientific computing, automation. Go Concurrent network services. Rust Memory-safe, concurrent cores. C / C++ Hardware-near. hardware — silicon, registers, firmware

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
A small piece of the real thing

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
}
Four areas, in depth

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
Multi-cloud topology

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.

App core Docker · K8s · IaC GCP Cloud Run · GKE · Vertex AI Azure AKS · Functions · Apps AWS Lambda · EKS · S3 · RDS Edge CF Workers · Vercel Edge
01

GCP

Vertex AI, Cloud Run, Cloud Functions, GKE, Pub/Sub, BigQuery, Firestore — my default for AI workloads and managed container delivery.

02

Azure

AKS, Functions, Cognitive Services, Container Apps — used where an organisation already lives in the Microsoft estate.

03

AWS

Lambda, ECS/EKS, S3, RDS, SQS/SNS behind CloudFront — the breadth of primitives for traditional production stacks.

04

Edge

Cloudflare Workers and Vercel Edge — logic that runs close to the user, for latency and global reach.

05

BaaS

Supabase and Firebase (Auth, Firestore, Realtime DB) — when a product needs a backend in days, not weeks.

06

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.

The instinct of a tool-builder
friction done twice small tool ~/.local/bin the toolchain compounds

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
The stack, at a glance

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.

Event-driven architecture

Decoupled by message, not by hope.

producer app · device producer service broker Pub/Sub · queue consumer consumer consumer dead-letter

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
Delivery pipeline

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

  1. 01 Commit Push to a branch. Pre-commit hooks run format and lint locally so the obvious is caught before CI ever spins up.
  2. 02 Build & test Container image built once, unit and integration tests run against it, coverage gate enforced. The same artifact promotes forward.
  3. 03 Scan Static analysis, dependency audit, and image vulnerability scan. A failing scan blocks the merge, not a human reviewer's memory.
  4. 04 Stage Deploy the signed image to a staging environment that mirrors production, run smoke tests and a short soak.
  5. 05 Promote Roll out to production behind a canary or blue-green switch, watch the metrics, and keep the previous version one command away.
rollback — previous version one command away commit build+ test scan stage canaryprod rollout
Real-time services

State carried over a socket, with the failure modes handled.

client · ws client · ws client · ws gatewayterminate · route handler handler shared state store

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
A second piece of the real thing

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
}
Edge handler

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 datastore layer

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.

01

PostgreSQL

The default relational store — transactions, JSONB where it earns its place, and indexes designed against the query plan rather than guessed.

02

BigQuery

Analytical scale. Columnar storage and partitioning for the questions that scan billions of rows but run rarely.

03

SQLite

On-device and embedded persistence for mobile and edge — a single file, zero server, the right tool for local-first apps.

04

Redis

The hot path — caching, rate limiting, queues, ephemeral presence and locks where microseconds and TTLs matter.

05

ORMs

Sequelize, Mongoose, GORM where they speed delivery — but the generated SQL stays readable, and the hot queries get hand-written.

06

Migrations

Schema as versioned, reversible migrations checked into the repo, so a database is reproducible rather than archaeological.

Linux hardening stack

The kernel the container runs on, treated as part of the product.

Updates Unattended security patches, reproducible images Audit auditd, journald retention, log shipping off-box Process systemd sandboxing, seccomp, namespaces, cgroups Network nftables default-deny, fail2ban, segmented ports Identity Least-privilege users, sudo policy, SSH keys only Boot & kernel Signed boot, minimal modules, sysctl baseline outer — exposed surface, watched and logged

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
The arc

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.

  1. 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.
  2. 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.
  3. 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.
  4. 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.

NextDefense Telecom