Posts Tagged ‘Go’
[GopherConUK2025] CPU Quota Semantics and Runtime Scheduler Behavior in Containerized Environments
Lecturer
Bill Kennedy is a software engineer, technical trainer, and Managing Partner at Ardan Labs. He has authored multiple technical books on Go programming and serves as a core organizer for developer communities worldwide. His professional work focuses on high-performance backend development, system design, and training software engineering teams on runtime internals and concurrent programming semantics.
Abstract
Deploying managed language runtimes into containerized orchestration frameworks requires a comprehensive understanding of how compute limits interact with application-level scheduling primitives. This article examines the behavior of the Go runtime scheduler when executed under Kubernetes CPU limits. By analyzing thread management, operating system context switching, and the mechanics of Completely Fair Scheduler (CFS) quota enforcement, this study highlights performance degradation scenarios caused by misalignment between thread allocation and container CPU constraints. Furthermore, empirically derived benchmarking demonstrates how adjusting runtime concurrency configurations mitigates kernel-level throttling and improves request throughput in CPU-bound and IO-bound application workloads.
Micro-Architecture, Concurrency, and Context Switching Mechanics
Modern multi-core processors execute operations via clock cycles, where instruction execution frequency depends on pipelined hardware architectures. On a standard processor core running at a 3 GHz clock rate, a single nanosecond corresponds to three clock cycles. Leveraging superscalar execution pipelines, modern hardware can process up to four instructions per clock cycle on average, yielding approximately twelve instructions per nanosecond. Consequently, operational latencies—whether originating from memory access, network round trips, or kernel thread context switches—directly translate into unexecuted instruction cycles.
| Operational Event | Approximate Duration | Lost Instruction Opportunities |
|---|---|---|
| OS Thread Context Switch | 1,000 ns (1 µs) | ~12,000 instructions |
| Datacenter Network Round Trip | 500,000 ns (0.5 ms) | ~6,000,000 instructions |
| Go Routine Context Switch | 200 ns | ~2,400 instructions |
In system software, workloads are categorized as either CPU-bound or IO-bound. CPU-bound tasks execute uninterrupted mathematical or logical operations, utilizing their full operating system time slice. Under CPU-bound conditions, context switches incur overhead that degrades throughput unless application thread counts strictly match available physical cores. Conversely, IO-bound workloads frequently transition threads into blocked or waiting states due to asynchronous network calls or file interactions.
The Go runtime abstracts operating system (OS) threads through an M:N scheduler, mapping M goroutines (application-level lightweight threads) onto N OS threads managed across logical processors known as P structures. Physical CPU cores are abstracted into these P units, which hold local run queues for goroutines. The Go scheduler operates as a work-stealing system: idle P structures steal runnable goroutines from other local queues or a global run queue.
+-----------------------------------------------+
| OS Kernel |
| [Core 0] [Core 1] [Core 2] [Core 3] |
+-----------------------------------------------+
^ ^ ^ ^
| | | |
[ M ] [ M ] [ M ] [ M ]
| | | |
[ P ] [ P ] [ P ] [ P ]
/ \ ... ... ...
[ G ] [ G ]
To maximize thread utilization, asynchronous system calls (such as network operations) are handled via a dedicated network poller thread. When a goroutine initiates a network read, the runtime detaches the goroutine from its current M thread and registers it with the network poller. This frees the underlying M thread to immediately execute other goroutines assigned to that logical P processor. Synchronous operations, such as blocking file system IO, force the runtime to decouple the blocking M thread from its assigned P structure and allocate or unpark a separate OS thread to keep the P processor active.
Through this abstraction, the Go runtime transforms application-level IO-bound tasks into CPU-bound operational streams from the operating system’s perspective. The OS kernel observes saturated worker threads (M), allowing them to consume allocated time slices efficiently without premature thread parking.
Kubernetes Completely Fair Scheduler (CFS) Quota Semantics
Kubernetes enforces compute resource limits using Linux control groups (cgroups) via the Completely Fair Scheduler (CFS) quota system. A CPU resource limit specified in millicores (such as 250m) translates into a time-based allocation per enforcement period. By default, the Linux kernel CFS operates on a 100-millisecond period.
Allocated Time Formula:
Allocated Time = CFS Period * (Millicores / 1000)
For an allocation of 250m across a 100 ms period, the container receives exactly 25 ms of cumulative execution time:
Allocated Time = 100 ms * (250 / 1000) = 25 ms
Crucially, the Linux CFS tracks CPU quota consumption cumulatively across all running OS threads within the container’s thread group. If an application spawns 16 threads that execute concurrently on a multi-core host system, each thread consumes physical core time simultaneously.
Quota Exhaustion Rate:
Quota Exhaustion Rate = Number of Threads * Elapsed Time
With 16 active OS threads, a 25 ms CPU quota is depleted in less than 2 ms of real time:
Exhaustion Time = 25 ms / 16 = 1.5625 ms
Once the total execution time across all threads reaches the 25 ms ceiling, the kernel CFS throttles the entire container. The container processes remain paused until the 100 ms cycle resets, resulting in severe latency spikes and degraded service throughput.
Architectural Misalignment: Go Max Procs in Container Runtimes
By default, the Go runtime initializes the number of logical processors (P) via the GOMAXPROCS variable based on system calls that query host core availability. In standard Kubernetes pod deployments without explicit runtime tuning, the runtime inspects the host node rather than container cgroup boundaries.
If a pod configured with a 250m limit is scheduled on a 16-core physical node, GOMAXPROCS defaults to 16. The runtime creates 16 logical P processors and corresponding OS threads (M).
Container Configuration: CPU Limit = 250m (25ms per 100ms cycle)
Host Infrastructure: 16 Physical Cores
Default Go Runtime Behavior: GOMAXPROCS = 16
+-------------------------------------------------------+
| 16 OS Threads (M) Executing Simultaneously |
| [M1] [M2] [M3] [M4] [M5] [M6] ... [M16] |
+-------------------------------------------------------+
|
v
Consumes 25ms Quota in ~1.56ms of Real Time
|
v
+-------------------------------------------------------+
| Kernel CFS Throttles Container for Remaining ~98.4ms |
+-------------------------------------------------------+
When incoming requests hit the container, all 16 worker threads wake up to process goroutines. The cumulative CPU time consumed by these 16 concurrent threads exhausts the 25 ms cgroup quota almost instantly. The application spends the vast majority of every 100 ms enforcement window in a kernel-throttled state.
To resolve this misalignment, the application runtime must match its logical thread capacity to its cgroup quota boundaries. Setting GOMAXPROCS=1 forces the Go scheduler to utilize a single logical P processor and one primary operating thread, executing sequential instructions over the full 25 ms window without premature multi-threaded quota depletion.
apiVersion: apps/v1
kind: Deployment
metadata:
name: sales-service
spec:
template:
spec:
containers:
- name: service
image: sales-service:1.0
env:
- name: GOMAXPROCS
valueFrom:
resourceFieldRef:
resource: limits.cpu
resources:
limits:
cpu: "250m"
In Go deployments, setting GOMAXPROCS via container environment variables applies a mathematical ceiling function to convert fractional core limits into discrete thread bounds.
Experimental Evaluation and System Optimization
Empirical performance tests were conducted on a Kubernetes cluster managed via kind hosted on an 16-core machine. The microservice stack comprised an HTTP API service backed by a PostgreSQL database. Load testing was executed using automated benchmark tools transmitting synthetic HTTP workloads.
Test Configuration A: Default Core Allocation
- CPU Limit: 250m (25 ms per 100 ms)
- Host Cores Detected: 16
- GOMAXPROCS: 16 (Default)
Test Configuration B: Matched Runtime Constraints
- CPU Limit: 250m (25 ms per 100 ms)
- Host Cores Detected: 16
- GOMAXPROCS: 1 (Explicitly configured)
Measured Experimental Results
| Metric | Config A (GOMAXPROCS=16) | Config B (GOMAXPROCS=1) | Performance Impact |
|---|---|---|---|
| Throughput | ~126 req/sec | ~2,746 req/sec | ~21.7x Increase |
| P99 Latency | ~200 ms | ~3.6 ms | ~98.2% Reduction |
Constraining the runtime thread count to align with container limits produced a 21-fold throughput increase while eliminating excessive tail latency caused by kernel CFS throttling.
Cascading Latency and Upstream Service Dependencies
In distributed microservice topologies, runtime throttling can cascade across service boundaries. During secondary experimentation, an authentication service dependency (auth-service) was assigned a restricted CPU limit (100m).
Even when the primary edge service (sales-service) was provisioned with unrestricted CPU allocations, overall request throughput dropped to baseline throttled levels. Blocking latencies introduced by the throttled upstream dependency bottlenecked the unconstrained downstream service. Diagnosing performance anomalies requires evaluating total system execution graphs rather than isolating individual application metrics.
Links:
[GopherConUK2025] A Gopher’s Guide to Vibe Coding: Evaluating LLM-Assisted Software Engineering in Go
Lecturer
Daniela Petruzalek works as a Developer Relations Engineer at Google. Originally from Brazil and residing in the United Kingdom since 2019, Daniela has worked extensively with the Go programming language since 2017. Her professional background spans software development, system architecture, agile technical consulting, and developer advocacy, with a primary focus on cloud-native technologies, developer experience, and language tooling.
Abstract
The rapid evolution of Large Language Models (LLMs) has popularized “vibe coding”—a development paradigm where software engineers rely on natural language prompts to drive automated code generation. While early iterations centered on unchecked code synthesis, professional adoption requires incorporating LLM tools into structured engineering workflows. This paper examines the integration of LLM coding agents into idiomatic Go development, evaluating productivity, correctness, and code quality. Drawing from practical implementations—including the creation of testquery and Model Context Protocol (MCP) servers—this study details techniques such as context engineering, Retrieval-Augmented Generation (RAG), tool-based grounding, and automated peer-review loops to produce maintainable, production-ready Go code.
Taxonomy of AI-Assisted Development Tools
AI-assisted software engineering tools range from simple inline completion utilities to fully autonomous agents:
+-------------------------------------------------------------------+
| AI ASSISTED DEVELOPMENT SPECTRUM |
+-------------------------------------------------------------------+
| Inline Completion | Contextual Chat | CLI Agents | Autonomous|
| (GitHub Copilot) | (VS Code Chat) | (Gemini CLI) | (Jules) |
+-------------------------------------------------------------------+
Low Autonomy <-------------------------------------> High Autonomy
- Inline Code Completion: Algorithms that complete single lines or block structures using local code context.
- Contextual Chat Interfaces: In-IDE conversational assistants capable of querying localized workspace snippets.
- CLI-Based Coding Agents: Interactive command-line tools (e.g., Gemini CLI, Aider) capable of running local shell commands, inspecting file systems, executing tests, and applying multi-file edits directly.
- Autonomous Execution Agents: Fully asynchronous environments (e.g., Jules, Devin) that clone repositories within isolated virtual machines, resolve GitHub issues, execute test suites, and submit pull requests with minimal human intervention.
// Sample Model Context Protocol (MCP) tool declaration in Go
package main
import (
"context"
"fmt"
)
type GoDocRequest struct {
Package string `json:"package"`
Symbol string `json:"symbol,omitempty"`
}
func HandleGoDoc(ctx context.Context, req GoDocRequest) (string, error) {
if req.Package == "" {
return "", fmt.Errorf("package name is required")
}
// Tool execution logic returning package documentation
return fmt.Sprintf("Documentation for %s", req.Package), nil
}
Integrating autonomous agents into professional workflows requires a structured framework based on business value and technical certainty. High-value tasks with clear technical steps demand real-time human oversight via interactive CLI tools. Conversely, low-risk, repetitive tasks—such as updating open-source license headers or formatting readmes—can be delegated to asynchronous background agents.
Business Value vs Technical Certainty
+-------------------+-------------------+
| Research / Spikes | Top Priority |
High Value | (Deep Research / | (Synchronous CLI |
| Hands-on-Keys) | Development) |
+-------------------+-------------------+
| Not Doing | Nice-to-Have |
Low Value | (Ignore / Backlog)| (Asynchronous |
| | Autonomous Agent) |
+-------------------+-------------------+
Low Certainty High Certainty
Context Engineering, Grounding, and Context Degradation
Generative models encounter several structural limitations when handling source code, including out-of-date training data, non-deterministic outputs, and a tendency to hallucinate invalid package APIs. Overcoming these limitations requires precise context engineering and tool grounding.
- Context Engineering: Supplying targeted technical documentation directly within the prompt scope. Fetching fresh package definitions prevents models from using deprecated function signatures or obsolete import paths.
- Tool Grounding: Providing external capabilities—such as file system readers, web scrapers, or language server protocols—via structured mechanisms like the Model Context Protocol (MCP). Grounding allows LLMs to query documentation dynamically rather than relying solely on parametric memory.
// Example Go code illustrating explicit error handling for LLM tasks
package main
import (
"errors"
"fmt"
)
var ErrInvalidSymbol = errors.New("requested symbol not found")
func LookupSymbol(doc, symbol string) (string, error) {
if symbol == "" {
return doc, nil
}
// Explicit string processing logic
return "", ErrInvalidSymbol
}
Prolonged agent sessions suffer from context degradation (or context rot), where accumulated error logs, discarded attempts, and verbose outputs pollute the model’s working memory. This degradation can cause the model to repeat failed edits or enter infinite refactoring loops. Engineers must actively manage context length by resetting sessions (/clear), providing fresh context, or summarizing state transitions before continuing development.
The Iterative TDD Refactoring Loop and Multi-Agent Code Reviews
Unchecked vibe coding often results in fragile, unmaintainable implementations. To ensure production-grade software quality, vibe coding should follow the disciplined Test-Driven Development (TDD) cycle:
+-------------------------------------------------+
| |
v |
+-----------+ +-----------+ +-----------+ |
| RED Phase | -----> | GREEN | -----> | REFACTOR | -+
| Write Test| | Pass Test | | Code Review|
+-----------+ +-----------+ +-----------+
- Red Phase: Define failing tests or precise specification prompts detailing constraints, input structures, and expected outcomes.
- Green Phase: Direct the LLM to generate the minimal implementation required to pass the test suite.
- Refactor Phase: Enforce code readability, performance optimizations, and idiomatic Go practices before starting new features.
// Example table-driven test to enforce green-phase verification
package main
import "testing"
func TestLookupSymbol(t *testing.T) {
tests := []struct {
name string
doc string
symbol string
wantErr bool
}{
{"empty symbol", "package doc", "", false},
{"missing symbol", "package doc", "Foo", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := LookupSymbol(tt.doc, tt.symbol)
if (err != nil) != tt.wantErr {
t.Errorf("LookupSymbol() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
A key strategy for maintaining code quality is decoupling implementation from code review. Allowing the same LLM session to review its own output often yields false positives due to lingering context bias. Instead, developers should pass the newly generated code to a fresh, isolated LLM instance equipped with dedicated code-review system prompts. This independent review step effectively surface edge-case bugs, unhandled errors, unreachable code, and style violations.
+----------------------+ +----------------------+
| Primary Coding Agent | | Isolated Review Agent |
| (Generates Solution) | | (Fresh Context Window)|
+----------------------+ +----------------------+
| |
| Output Source Code | Analyzes AST & Patterns
+------------------------------->|
|
v
+----------------------+
| Structured Feedback |
| (JSON Diagnostics) |
+----------------------+
By pairing automated multi-agent code reviews with explicit project guidelines (e.g., AGENTS.md or GEMINI.md), developers create a self-improving feedback loop. As the agent runs reflection prompts to analyze past session mistakes, it systematically updates its instruction rules, raising code quality in subsequent sessions.
Links:
[GopherConUK2025] Conceptual XPDB Custom Resource definition
apiVersion: policy.form3.tech/v1alpha1
kind: CrossClusterPodDisruptionBudget
metadata:
name: cockroachdb-global-pdb
spec:
maxUnavailable: 1
clusters:
– aws-region-1
– gcp-region-1
– azure-region-1
selector:
matchLabels:
app: cockroachdb
XPDB enforces global disruption caps across cluster boundaries. When node drains or pod evasions occur in one cloud provider, XPDB evaluates the global state across all environments. By guaranteeing that only a single CockroachDB pod is disrupted across the entire global infrastructure at any given moment, XPDB ensures data consensus remains fully protected during routine infrastructure maintenance.
## Operator-Driven Infrastructure and Rolling Node-Pool Management
Managing multi-tenant infrastructure across multiple jurisdictions—each containing distinct development, staging, and production environments—results in a massive expansion of node pools. Updating Kubernetes worker nodes across this matrix using traditional Infrastructure-as-Code tools like Terraform creates extreme operational friction, requiring dozens of sequential pull requests and complex deployment pipelines.
To address this management overhead, Form3 built a custom Kubernetes operator called the Cluster Lifecycle Operator. The operator runs natively inside each managed cluster and abstracts raw node-pool management behind Custom Resource Definitions (CRDs).
// Conceptual snippet of CRD controller reconciliation loop
package main
import (
“context”
“fmt”
)
type ClusterSpec struct {
Version string json:"version"
}
func ReconcileNodePool(ctx context.Context, spec ClusterSpec) error {
fmt.Printf(“Reconciling node pools to version: %s\n”, spec.Version)
// Operator logic handles sequential node drains
return nil
}
Instead of modifying declarative infrastructure files for every individual node pool across every provider, engineers update a single CRD spec controlling the target cluster version. The Cluster Lifecycle Operator handles the rolling replacement of worker nodes asynchronously, adhering to defined disruption parameters and health checks. Upgrading the global fleet requires only three sequential pull requests—promoted systematically through development, staging, and production.
## Continuous Disaster Recovery via Automated Production Chaos Injection
Conventional disaster recovery (DR) practices often rely on periodic manual failover tests driven by static documentation. In rapidly changing microservice environments, compliance-focused DR exercises fail to validate real-world resilience, as system changes can render manual playbooks obsolete immediately after testing.
Form3 enforces continuous disaster recovery validation directly within staging environments through automated fault injection. A dedicated test harness deploys synthetic client applications outside the primary infrastructure perimeter. These synthetic actors continuously execute end-to-end payment workflows against simulated payment scheme interfaces at fixed intervals.
// Custom chaos injection test runner in Go
package main
import (
“context”
“log”
“time”
)
func InjectProviderOutage(ctx context.Context, targetCloud string) error {
log.Printf(“Simulating complete network partition for provider: %s”, targetCloud)
// Inject network block rules via Chaos Mesh API
time.Sleep(30 * time.Second)
return nil
}
“`
During automated test windows, the platform uses Chaos Mesh alongside custom Go-based orchestrators to introduce disruptive scenarios:
- Severing inter-cloud network connectivity.
- Terminating entire database nodes or message brokers.
- Programmatically isolating an entire cloud provider for 24 hours.
If synthetic payment processing encounters errors or breaches latency thresholds, automated alerts notify on-call platform teams. Automated daily reports detail platform behavior during fault injection cycles, verifying that the loss of an entire cloud vendor produces zero client impact.
Links:
[DevoxxFR2025] Go Without Frills: When the Standard Library Suffices
Go, the programming language designed by Google, has gained significant popularity for its simplicity, efficiency, and strong support for concurrent programming. A core philosophy of Go is its minimalist design and emphasis on a robust standard library, encouraging developers to “do a lot with a little.” Nathan Castelein, in his presentation, championed this philosophy, demonstrating how a significant portion of modern applications can be built effectively using only Go’s standard library, without resorting to numerous third-party dependencies. He explored various native packages and compared their functionalities to well-known third-party alternatives, showcasing why and how returning to the fundamentals can lead to simpler, more maintainable, and often equally performant Go applications.
The Go Standard Library: A Powerful Foundation
Nathan highlighted the richness and capability of Go’s standard library. Unlike some languages where the standard library is minimal, Go provides a comprehensive set of packages covering a wide range of functionalities, from networking and HTTP to encoding/decoding, cryptography, and testing. He emphasized that these standard packages are well-designed, thoroughly tested, and actively maintained, making them a reliable choice for building production-ready applications. Focusing on the standard library reduces the number of external dependencies, which simplifies project management, minimizes potential security vulnerabilities introduced by third-party code, and avoids the complexities of managing version conflicts. It also encourages developers to gain a deeper understanding of the language’s built-in capabilities.
Comparing Standard Packages to Third-Party Libraries
The core of Nathan’s talk involved comparing functionalities provided by standard Go packages with those offered by popular third-party libraries. He showcased examples in areas such as:
– Web Development: Demonstrating how to build web servers and handle HTTP requests using the net/http package, contrasting it with frameworks like Gin, Echo, or Fiber. He would have shown that for many common web tasks, the standard library provides sufficient features.
– Logging: Illustrating the capabilities of the log/slog package (introduced in Go 1.21) for structured logging, comparing it to libraries like Logrus or Zerolog. He would have highlighted how log/slog provides modern logging features natively.
– Testing: Exploring the testing package for writing unit and integration tests, perhaps mentioning how it can be used effectively without resorting to assertion libraries like Testify for many common assertion scenarios.
The comparison aimed to show that while third-party libraries often provide convenience or specialized features, the standard library has evolved to incorporate many commonly needed functionalities, often in a simpler and more idiomatic Go way.
The Benefits of a Minimalist Approach
Nathan articulated the benefits of embracing a “Go without frills” approach. Using the standard library more extensively leads to:
– Reduced Complexity: Fewer dependencies mean a simpler project structure and fewer moving parts to understand and manage.
– Improved Maintainability: Code relying on standard libraries is often easier to maintain over time, as the dependencies are stable and well-documented.
– Enhanced Performance: Standard library implementations are often highly optimized and integrated with the Go runtime.
– Faster Compilation: Fewer dependencies can lead to quicker build times.
– Smaller Binaries: Avoiding large third-party libraries can result in smaller executable files.
He acknowledged that there are still valid use cases for third-party libraries, especially for highly specialized tasks or when a library provides significant productivity gains. However, the key takeaway was to evaluate the necessity of adding a dependency and to leverage the powerful standard library whenever it suffices. The talk encouraged developers to revisit the fundamentals and appreciate the elegance and capability of Go’s built-in tools for building robust and efficient applications.
Links:
- Nathan Castelein: https://www.linkedin.com/in/nathan-castelein/
- Shodo Lille: https://shodo.io/
- Devoxx France LinkedIn: https://www.linkedin.com/company/devoxx-france/
- Devoxx France Bluesky: https://bsky.app/profile/devoxx.fr
- Devoxx France Website: https://www.devoxx.fr/
[PHPForumParis2022] FrankenPHP: Diving into PHP’s Interpreter, Virtual Machines, and More – Kévin Dunglas
Kévin Dunglas, a seasoned developer at Les-Tilleuls.coop and creator of API Platform, presented an innovative exploration of FrankenPHP at PHP Forum Paris 2022. Blending PHP with Go, Kévin introduced a groundbreaking server solution that pushes PHP’s boundaries. His talk delved into the technical intricacies of integrating Go’s threading model with PHP’s interpreter, offering a glimpse into a future where PHP applications achieve unprecedented performance and flexibility.
Introducing FrankenPHP
Kévin opened with the origins of FrankenPHP, a project born from his passion for both PHP and Go. Inspired by Les-Tilleuls’ developer Loris Sorio, who designed its logo, FrankenPHP aims to combine PHP’s ease of use with Go’s performance capabilities. Kévin explained how it leverages Go’s threading to overcome PHP-FPM’s limitations, enabling features like concurrent request handling. This fusion, he argued, unlocks new possibilities for PHP applications, particularly in high-performance scenarios.
Overcoming Technical Challenges
Delving into the technical core, Kévin described the complexities of integrating PHP’s Zend Thread Safe (ZTS) mode with Go’s threading model. He highlighted challenges like signal conflicts and the lack of OPcache support, which required custom modifications to PHP’s source code. By isolating PHP processes within Go threads, Kévin’s team achieved stable communication, though he noted the solution remains experimental. His transparency about these hurdles provided valuable insights for developers exploring similar integrations.
Performance and Future Directions
Kévin showcased FrankenPHP’s performance potential, demonstrating how enabling OPcache by modifying PHP’s SAPI list significantly reduced compilation overhead. He outlined future goals, including support for Laravel Octane and Symfony’s CLI, while acknowledging Windows compatibility challenges. Kévin’s call for community contributions to refine FrankenPHP underscored its open-source ethos, inviting developers to explore its code and report issues to enhance its stability.
Community Engagement and Collaboration
Concluding, Kévin emphasized the collaborative spirit driving FrankenPHP’s development. He encouraged developers to contribute via GitHub, highlighting the project’s experimental nature and potential for growth. By sharing Les-Tilleuls’ vision, Kévin inspired attendees to experiment with FrankenPHP, fostering a community-driven effort to redefine PHP’s role in modern web development.
Links:
[GopherCon UK 2022] Leading in Tech
Leading in Tech – Michael Cullum
At GopherCon UK 2022, Michael Cullum, Head of Engineering at Bud, delivered an engaging talk on the multifaceted nature of leadership in the tech industry. With a wealth of experience in engineering leadership, Cullum explored what it means to be a leader, the diverse forms leadership takes, and how individuals can cultivate and identify effective leadership. His talk underscored that leadership is not confined to titles but is a universal opportunity to inspire and support others, making it a critical skill for all tech professionals.
Defining Leadership: Beyond Titles and Tasks
Cullum began by tackling the elusive definition of leadership, noting that even dictionaries and academic papers struggle to pin it down. He proposed that leadership is about promoting movement or change in others, not through coercion but by encouraging and supporting them. Unlike management, which often involves tasks like hiring or performance oversight, leadership focuses on the individuals being led, prioritizing their growth over the leader’s ego. Cullum emphasized that leadership is not about issuing orders but about fostering an environment where people are motivated to excel. This distinction is vital in tech, where roles like tech leads or managers can blur the line between task-oriented management and people-centric leadership.
Exploring Leadership Roles in Tech
Leadership in tech manifests in various forms, each with unique responsibilities. Cullum highlighted mentorship as a foundational leadership role, accessible to all regardless of seniority. Mentoring, whether formal or informal, involves sharing experiences to guide others, yet the industry often falls short in formalizing these relationships. Tech leads, another key role, translate business needs into technical direction but frequently focus on tasks like project management rather than inspiring their teams. Principal or staff engineers lead by example, serving as go-to experts who inspire through technical excellence. Public leaders, such as bloggers or conference speakers, drive change by sharing knowledge, while managers and senior leaders (e.g., CTOs) balance individual support with organizational goals. Cullum stressed that all these roles, when executed with a focus on others, embody leadership.
Traits of Effective Leaders
What makes a leader exceptional? Cullum outlined several critical traits. Listening—not just hearing but understanding—is paramount, as it fosters empathy and uncovers others’ needs. Leaders must communicate clearly, giving people time to digest complex ideas, and be mindful of power dynamics, speaking last in discussions to avoid stifling input. Generating energy and inspiring others, whether through actions or enthusiasm, is essential, as is maintaining a team-oriented mindset to avoid “us vs. them” divides. For tech leaders, staying technical—within reason—keeps them grounded, while managing team stress involves shielding members from undue pressure without hiding critical information. Cullum’s “poop analogy” illustrated this: great leaders act as umbrellas, filtering stress, not fans that scatter it chaotically.
Becoming and Finding Great Leaders
Cullum concluded with practical advice for aspiring leaders and those seeking them. Mentoring others, even informally, is the first step toward leadership, while seeking mentors outside one’s company provides unbiased guidance. Observing both good and bad leaders offers valuable lessons, and resources like books (e.g., The Manager’s Path by Camille Fournier) and communities like the Rands Leadership Slack enhance growth. When job hunting, Cullum urged asking about leadership style, vision, and team dynamics, as these outweigh transient tech stacks in importance. Great leaders respect, mentor, and prioritize your growth, fostering environments where you feel valued and inspired. By holding leaders to high standards and embracing leadership opportunities, everyone can contribute to a thriving tech ecosystem.
Links:
Hashtags: #Leadership #TechLeadership #Mentorship #GopherCon #MichaelCullum #Bud