Is this you? As a journalist, you can create a free Muck Rack account to customize your profile, list your contact preferences, and upload a portfolio of your best work.
Claim your profile
Articles by Doogal Simpson
Java Date Formatting Bug: YYYY vs yyyy Explained
In Java date formatting, uppercase YYYY represents the week-based year, while lowercase yyyy represents the calendar year. Using YYYY can cause dates in late December to print as the following calendar year depending on the system locale, leading to silent end-of-year production bugs. I always recommend auditing date patterns for lowercase yyyy. I see this exact bug hit production every single year around late December: an application suddenly starts printing dates from the future.
Measuring Execution Time Safely with Monotonic Clocks
Quick Answer: Measuring execution time using wall-clock functions like Date.now() or System.currentTimeMillis() can yield negative durations if your server synchronizes its clock mid-operation. To measure elapsed code performance accurately, always use monotonic clock APIs like performance.now() in JavaScript or System.nanoTime() in Java, which strictly count forward from an arbitrary baseline.
Why a Single Emoji Quadruples Python String Memory
TL;DR: CPython stores strings using PEP 393 (Flexible String Representation) rather than UTF-8. It selects a fixed width of 1, 2, or 4 bytes per character based on the widest character present. If an ASCII string contains just one 4-byte emoji, Python instantly upgrades every single character in that string to 4 bytes, quadrupling its memory footprint. Imagine loading a massive log file into memory, doing basic parsing, and watching your server's RAM jump from 100 MB to 400 MB.
How Hashcash Works: Proof of Work Before Bitcoin
Quick Answer: Hashcash, created by Adam Back in 1997, introduced Proof of Work to combat email spam by requiring senders to solve a computationally asymmetric puzzle. By forcing senders to compute a hash with leading zero bits, it imposed a tiny delay on single emails while rendering mass spam computationally infeasible—a principle Satoshi Nakamoto later adapted for Bitcoin consensus.
The $460M Feature Flag: Knight Capital Software Disaster
In 2012, Knight Capital lost $460 million in just 45 minutes due to an incomplete software deployment and dead code execution. Reusing an old feature flag across eight servers allowed an unverified legacy routine to execute 4 million unauthorized trades. Strict deployment verification and aggressive dead code deletion are essential safeguards for modern production systems.
Collections.unmodifiableList vs List.copyOf in Java
Quick Answer: Collections.unmodifiableList in Java creates a read-only view wrapper over an existing collection rather than an immutable copy. If the underlying collection mutates, those changes immediately reflect in the unmodifiable view. To create a true, isolated snapshot of your data that resists changes to the original source, use List.copyOf() introduced in Java 10.
How Cell Towers Share Spectrum: LTE Resource Blocks
Quick Answer: Mobile towers manage thousands of concurrent phone connections across limited radio spectrum by slicing a single channel (such as a 20 MHz band) across two dimensions: frequency subcarriers and 1-millisecond time slots. This produces thousands of discrete "resource blocks" per second, which the tower dynamically assigns only to devices with active data queues. When I look at modern cellular network scheduling, I'm constantly reminded of operating system kernel thread scheduling.
How to Prevent Retry Storms in Microservices
Quick Answer: A retry storm occurs when deeply nested microservices independently retry failed downstream requests, causing exponential traffic amplification. If five services in a chain each retry three times upon failure, a single user click generates 243 requests against an already struggling database. Exponential backoff delays the traffic, but solving the issue requires retry budgets and circuit breakers. Microservices give us isolation, scalability, and independent deployment cycles.
Fixing Node.js Localhost ECONNREFUSED Connection Errors
Quick Answer: Node 17 broke local connections by switching from hardcoded IPv4 (127.0.0.1) lookups to the operating system’s default order, which prioritizes IPv6 (::1). If your server listened only on IPv4, connections failed immediately. Node 20 resolved this by implementing the "Happy Eyeballs" algorithm, which automatically falls back to IPv4 if IPv6 fails.
Prevent Thundering Herd: Retry Jitter in Microservices
When services fail, standard exponential backoff causes retries to sync up, creating a "thundering herd" that repeatedly crashes your servers. Adding "jitter"—a small dose of randomness to your wait times—spreads the retry load over time, allowing struggling systems to recover and slashing total call volume by more than half. Whenever I audit a distributed system that keeps falling over after a minor blip, the first thing I look at is the retry logic.
How Java HashMap Prevents Hash Collision DoS Attacks
Java's HashMap mitigates Hash Collision Denial of Service (DoS) attacks by automatically converting congested linked list buckets into Red-Black trees once a bucket exceeds 8 entries and the total map capacity reaches 64. This transition reduces lookup complexity from O(N) to O(log N), preventing attackers from exhausting CPU resources. Imagine sending a tiny 2MB payload to a web server and completely freezing a CPU core for nearly three-quarters of an hour.
SHA-256 vs Bcrypt: Why Speed Kills Password Security
Quick Answer: Standard cryptographic hashes like SHA-256 are built for speed, making them dangerous for password storage. Bcrypt defeats offline brute-force attacks by introducing a configurable cost factor that exponentially increases computational work, turning a one-minute crack time into seven years while keeping legitimate logins fast. A top-tier graphics card can crack 22 billion SHA-256 hashes every second. Let that number sink in.
Python Integer Caching: Why 256 is 256 but 257 is Not
Why does 256 is 256 evaluate to True, but 257 is 257 return False? Python pre-allocates small integer objects (traditionally -5 to 256) inside a global array to save memory. Because this optimization is version-dependent, using the is operator for numeric comparisons introduces subtle, breaking bugs. Run a = 256; b = 256; a is b in your Python REPL, and it returns True. Run x = 257; y = 257; x is y on separate lines, and it returns False.
Why rm Doesn't Free Disk Space in Linux (and How to Fix)
When you run rm on a file in Linux, the OS only removes the directory entry (the pointer). If a running process still has an open file descriptor to that file, the disk space is not reclaimed until the process is restarted or the file descriptor is closed. Picture this: It is 2:00 AM. A critical production alert wakes you up because a bare-metal server has run out of disk space. You SSH in, run df -h, and confirm the disk is at 100% capacity.
How to Calculate DB Connection Pools for Auto-Scaling
When your application auto-scales, your database connections can quickly saturate. If your service replica connection pool size multiplied by the number of active replicas exceeds your database's max connection limit, the database will reject new connections. Always calculate your connection limits dynamically based on your scaling ceilings. Imagine a sudden spike in traffic hits your web service. Your horizontal pod autoscaler responds beautifully, spinning up new instances to handle the load.
Debugging Docker Crash Loops: A Practical Guide
TL;DR: A Docker crash loop occurs when a containerized process repeatedly exits with a non-zero code (like exit code 1), prompting Docker to restart it. By persisting state via volume mounts, developers can inspect crash logs across restarts and resolve the underlying bugs to achieve a clean exit code 0. I often think about the climax of Marvel's Doctor Strange when dealing with broken deployments.
Run LLMs Locally: Ollama Setup & Hardware Requirements
If you want to run LLMs locally, I recommend using Ollama to manage model weights and serve a local API. While you will need substantial hardware—typically a minimum of 16GB of VRAM or an Apple Silicon Mac with unified memory—this setup lets you run private, free agentic workflows on your own machine. Let's be honest: if you want to run an LLM locally, your first major hurdle isn't writing code—it's convincing your workstation not to melt.
Handling Partial Failures in Distributed Systems
Quick Answer: To handle partial failures in distributed systems, avoid complex distributed transactions. Instead, design for the "unhappy path" using an orchestration pattern (like the Saga pattern). This ensures that if a downstream service fails after a payment is processed, compensating actions automatically roll back the transaction and refund the user. Let's be honest: a lot of the code running in production right now is built on a prayer.
Fix AI Agent Jargon with Simplified Technical English
Tired of Claude Code generating bizarre, overly dramatic jargon like "load-bearing spine"? You can fix this by enforcing Simplified Technical English (STE) in your system instructions or .claudemd files. This 1970s aerospace standard restricts vocabulary, forcing your AI agent to communicate in clear, direct, and highly actionable prose.
How AI Automates Fast Decisions in Software Engineering
AI is shifting the software engineering landscape by taking over "fast decisions"—the automatic, high-context but low-complexity tasks like running tests and catching syntax errors. This frees up developers to focus entirely on "slow decisions" like system architecture, API boundaries, and product design. I’ve been thinking a lot about the book Thinking, Fast and Slow lately. The core point of the book is that your brain operates in two distinct modes.
TDD vs. Spike-Driven Development: When to Test First
Test-Driven Development (TDD) is highly effective for well-defined, predictable software tasks where the inputs and outputs are known upfront. However, TDD fails during exploratory "spikes" or research phases when you don't yet know how to solve the problem. In those cases, write tests after finding the solution. I recently had the roof on my house replaced. The crew showed up, climbed up a ladder, and immediately saw it was shot.
Graph Databases for Real-Time Fraud Detection
Quick Answer: Graph analytics detects fraud by mapping connections between seemingly unrelated entities—like people, addresses, and transactions—into a unified network. Unlike traditional relational databases, graph databases use relationship-focused algorithms to uncover hidden fraud rings, sometimes revealing connections that are a little too close to home. I want to tell you a quick story about bankers, software, and corruption.
Is 100% Test Coverage Worth It? Realistic Testing Targets
Many developers treat test coverage as a game where the only acceptable high score is 100%. But in the real world of software delivery, chasing absolute perfection often introduces diminishing returns. While 100% test coverage sounds ideal, achieving it often introduces diminishing returns. For most applications, aiming for 70% to 80% coverage secures the happy paths and critical business logic without draining development time.
Why JavaScript Has Quirks (And Why TC39 Can't Fix Them)
TL;DR: JavaScript has notorious quirks because it was designed in just ten days as a prototype to help Netscape survive the early browser wars. Because the internet relies on absolute backwards compatibility, these rushed design flaws became permanent APIs that we must support forever to avoid breaking the web. I've lost count of the times I've stared at a console, questioning my career choices because of some bizarre JavaScript coercion behavior.
Game Theory in Software Engineering: How Cooperation Wins
In software engineering, optimizing for individual metrics at the expense of your team is a losing strategy. I believe that while "selfish" coding yields quick short-term wins, long-term career compounding and technical success come exclusively from consistent, reputation-building cooperation. I've always found game theory fascinating, especially when it maps directly to how we build software.
How the V8 Engine Optimizes JavaScript at Runtime
.The V8 engine speeds up JavaScript by dynamically compiling frequently run bytecode into optimized native machine code. However, if you pass inconsistent argument types to these optimized functions, V8 panics and deoptimizes back to bytecode. Keeping your functions monomorphic (single-typed) prevents this costly deoptimization loop, ensuring maximum runtime execution speed.
A* Search Algorithm: Game Pathfinding Explained
Quick Answer: Video game characters use the A* (A-Star) search algorithm to navigate complex environments without getting stuck on walls. A* achieves this by calculating the most efficient route using a simple formula: the actual movement cost from the starting point plus the estimated distance to the destination. This prevents unnatural wall-hugging and finds the true shortest path. Imagine your team is building a top-down game.
Handling ISO Currency Codes in Software
Quick Answer: When processing financial transactions over the internet, systems cannot rely on ambiguous symbols like the dollar sign ($) because dozens of countries share it. Instead, banking APIs use ISO 4217, a global standard that assigns a unique three-letter code (like USD, CAD, or AUD) to unambiguously identify every currency. If you were to write "$129" in a text message, your friend probably knows exactly what you mean based on where you live.
How to Represent Money in Software
Quick Answer: Never use floating-point numbers to represent money in software. Because floating-point math cannot perfectly represent base-10 decimals, it introduces microscopic inaccuracies that compound over time. I recommend using either a dedicated arbitrary-precision decimal library or storing currency as integers representing micro-units to guarantee exact calculations.
Correct by Design vs Correct by Coincidence
Quick Answer: Code is "correct by coincidence" when it relies on duplicated, fragile details—like matching magic strings—that just happen to align perfectly to make the application work. Code is "correct by design" when you structurally enforce stability, such as centralizing shared values, ensuring the system cannot easily break from simple typos. You can build an application a hundred different ways.
Agile is a Mindset, Not Endless Sprint Meetings
Quick Answer: I firmly believe true Agile is a mindset focused on shipping small, incremental changes based on direct user feedback to navigate uncertainty. If I see a team spending more time sitting in sprint planning and retrospective meetings than actually writing working code, I know they are executing a rigid administrative process, not practicing Agile. I often talk to engineers who stare at ticketing boards for so long they forget what their IDE looks like.
Debugging Microservices with Distributed Tracing
Quick Answer: Distributed tracing solves microservice debugging by attaching a unique Trace ID to every HTTP request. This ID is passed through headers to every downstream service, allowing you to correlate logs and track exactly where a request failed across dozens of different servers. When I'm talking to engineers about debugging, I always like to use this scenario: imagine your team is building a massive retail app. A user clicks the "Buy" button, gets a loading spinner, and then...
Blue-Green Deployments: Instant Rollbacks & Zero Downtime
A blue-green deployment is a release strategy where you maintain two identical production environments. Only one serves live traffic at a time. When releasing new software, you deploy to the idle environment, test it, and then switch the traffic router. If the release fails, you can instantly roll back by routing traffic back to the original environment. Deploying new code to a live system is inherently risky.
Microsoft Project Silica: Glass Storage Explained
Quick Answer: Traditional hard drives and SSDs have a maximum lifespan of roughly 5 to 15 years due to mechanical wear and flash memory degradation. To solve this long-term cold storage problem, Microsoft's Project Silica uses lasers to permanently encode data inside glass slabs, creating extreme-durability drives designed to last over 10,000 years. There is an uncomfortable truth about the hardware we rely on: all of it is actively dying.
Single Responsibility Principle for AI Sub-Agents
Quick Answer: When building multi-agent AI systems, assigning human personas with broad responsibilities is a trap. Instead, apply the single-responsibility principle. By breaking complex tasks down into highly focused sub-agents—like dedicated file searchers and isolated code writers—you reduce context exhaustion and make debugging significantly easier. I've noticed a trap we fall into when designing AI sub-agents: we tend to imagine them as people.
AI Code Refactoring: Why Testing Still Matters
Quick Answer: While AI agents can rewrite or refactor thousands of lines of code in hours, generating the code is only half the battle. Verifying that the new system works safely still relies on foundational software engineering practices. To manage deployment risk, you must rely on comprehensive testing suites and feature flags. Imagine your team is maintaining a legacy microservice that handles backend data routing.
How Database Inserts Work: WAL and Buffer Pools
Quick Answer: When you insert data into a database, it doesn't immediately write to the main data table on disk. Instead, it caches the data in a RAM-based buffer pool and writes to an append-only Write-Ahead Log (WAL) to ensure durability. The database confirms the insert instantly, moving data to the actual table minutes later. You write an INSERT statement, hit execute, and the database returns a success message.
Optimize Your Codebase for AI Coding Agents
TL;DR: Quick Answer If your codebase doesn't contain the exact nouns and verbs you use to prompt your AI coding agent, the agent will do a much worse job. By ensuring your code reflects your spoken domain language, agents can map instructions directly to components, preventing them from generating messy, monolithic scripts. If you're using a coding agent and your codebase does not contain the nouns and the verbs that you use to prompt the agent with, your agent will do a worse job. Period.
How OLED & LTPO Enable Always-On Displays
TL;DR: Always-on displays don’t drain your phone battery because they rely on two hardware advancements: OLED panels and LTPO technology. OLEDs save power by physically turning off pixels to display black backgrounds. Meanwhile, LTPO allows the screen’s refresh rate to drop to 1Hz, preventing the CPU from constantly waking up. I often glance down at my phone sitting on my desk. The screen is technically "sleeping," but the time and a few notification icons are still glowing brightly.
TCP Exponential Backoff: Why Your Retries are Doubling
TCP prevents network meltdowns by doubling its wait time (Exponential Backoff) every time a packet fails to acknowledge. Instead of spamming a congested link, I look at how the protocol calculates a dynamic Retransmission Timeout (RTO) and then backs off to allow hardware buffers to clear and avoid total congestion collapse. I find it wild that we can just download a file from a physical computer on another continent through a chaotic web of underwater cables and intermediary servers.
What About Second HTTP? Solving the 100-File Connection Bottleneck
TL;DR: HTTP/2 replaces the inefficient six-connection limit of HTTP/1.1 with a single, multiplexed stream. By breaking assets into small, interleaved chunks, it eliminates head-of-line blocking and prevents multiple connections from fighting for bandwidth, allowing browsers to request and download hundreds of files simultaneously without the overhead of repeated TCP handshakes. We’ve had first HTTP, but what about second HTTP? Back in the "olden days," websites were tiny.
The 600ms Tax: Why Every TCP Connection Starts with a State Negotiation
TL;DR: A TCP handshake is a mandatory three-step negotiation—SYN, SYN-ACK, and ACK—required to synchronize sequence numbers and reserve memory buffers before data transfer. This protocol overhead adds a minimum of 1.5 round-trips of latency, making persistent connection reuse a critical optimization for reducing time-to-first-byte (TTFB). Fetching a file from a remote server isn't just a matter of bandwidth; it’s a battle against the physics of the network.
Your JavaScript Array is a Hash Map in Disguise
TL;DR: JavaScript arrays are fundamentally objects where integer keys are treated as strings. To save performance, engines like V8 attempt to optimize these into contiguous memory blocks (Elements Kinds). However, mixing types or creating sparse "holes" triggers a de-optimization to Dictionary Mode, moving data from the 1ns CPU cache to 100ns RAM lookups. If you have ever wondered why typeof [] returns "object", the answer isn't just "JavaScript is weird." It is an architectural warning.
Beyond the Haversine Formula: Why I Use Geohashing for Spatial Search
TL;DR: Geohashing encodes 2D coordinates into hierarchical string prefixes, transforming expensive O(n) geometric calculations into efficient indexed lookups. By mapping geographic areas to unique string identifiers, databases can execute high-speed prefix scans rather than performing floating-point evaluations on every record. This allows applications to scale proximity searches to millions of concurrent users without hitting CPU bottlenecks.
Scalable Proximity Search: Why Geohashing Beats Radius Queries
TL;DR: Geohashing maps 2D coordinates to a 1D string using recursive binary partitioning and bit interleaving. By encoding these bits into a Base-32 string, we leverage B-Tree prefix matching for efficient spatial lookups, bypassing the high CPU costs of Haversine distance calculations at scale. Calculating Haversine distances for 10,000 moving objects per tick is a disaster for database performance.
How Big Tech Scales View Counts: The Power of HyperLogLog and Harmonic Means
TL;DR: Scaling unique view counts for millions of posts requires more than just a COUNT(DISTINCT) query. Modern platforms use HyperLogLog, a probabilistic data structure that estimates cardinality using hashing and bucketing. By applying a harmonic mean across thousands of independent buckets, engineers can maintain high accuracy with a tiny memory footprint. When we talk about scale, we often focus on throughput or latency, but memory consumption for unique metrics is a silent killer.
Stop Joining Millions of Rows for Every Single Swipe
TL;DR: Dating apps avoid the architectural nightmare of joining millions of left-swipe records by using Bloom filters. By hashing user IDs into a bit array, they get a 100% guarantee that a '0' means a user is new, while accepting a rare false positive as a necessary trade-off for high-concurrency performance. I’ve seen plenty of teams try to scale discovery feeds by throwing more hardware at SQL joins, and it is a losing game.
How to build a profanity filter that actually works
TL;DR: A production-ready profanity filter isn't just a list of banned words; it's a pipeline. You start with sanitization to normalize character substitutions, followed by a Trie for efficient prefix matching. To avoid the Scunthorpe problem, you cross-reference matches against an allow-list or use context-aware ML models to score intent, balancing raw speed with semantic accuracy.
How Docker Actually Works: A Deep Dive into Namespaces and Cgroups
TL;DR: Docker containers are just standard Linux processes restricted by Namespaces and Cgroups. Namespaces provide visibility isolation by partitioning kernel resources like PIDs and networking, while Cgroups (Control Groups) provide resource isolation by enforcing hard limits on CPU, memory, and I/O usage to prevent host exhaustion. Docker feels like magic until your container gets OOMKilled or you can’t reach a port you swore was open.
Scaling Profanity Filters: Why I Use Tries for Real-Time Chat
TL;DR: When I'm building high-traffic chat systems, a standard list lookup for profanity is too slow because search time grows with the size of the dictionary. I use a Trie (prefix tree) to move to O(K) performance. This ensures that filtering speed depends entirely on the length of the word being checked, not the number of banned words.
Show More
loading
Actions
Is this you?
As a journalist, you can create a free Muck Rack account to customize your profile, list your contact preferences, and upload a portfolio of your best work.Get in touch with Doogal
Contact Doogal, search articles and posts on X, monitor coverage, and track replies from one place.
Learn more about Muck Rack