Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
148646 stories
·
33 followers

Rust vs. Python: Finding the right balance between speed and simplicity

1 Share

This blog post was brought to you by Damaso Sanoja, draft.dev.

Deciding whether to use Python or Rust isn’t just a syntax choice; it’s a career bet. According to the StackOverflow Developer Survey, Python dominates in accessibility, with 66.4% of people learning to code choosing it as their entry point. Python usage skyrocketed from 32% in 2017 to 57% in 2024, making it the tool of choice for “more than half of the world’s programmers.” GitHub has also reported that Python overtook JavaScript as their most-used language for the first time in over a decade.

But Rust’s trajectory tells a different story. Despite its complexity, its usage has grown from 7% to 11% since 2020. It’s also been the “Most Admired programming language” for nine straight years, with over 80% of developers wanting to continue using it.

The real question isn’t which language will “win” – it’s which one positions you for the problems you want to solve. This article explores their syntax, performance, learning curves, type systems, memory management, concurrency, ecosystems, and tooling to help you make a decision that aligns with both market realities and your career goals.

TL;DR:
Python and Rust serve different goals. Python wins on accessibility, flexibility, and a vast ecosystem. Ideal for rapid development, data science, and automation. Rust shines in performance, safety, and concurrency, making it perfect for systems programming and large-scale, reliable software.
In short: choose Python to build fast, iterate faster, and reach users quickly; choose Rust to build software that runs fast, scales safely, and lasts. Many developers now combine both; using Rust for performance-critical parts inside Python apps.

Syntax and Readability

Python’s syntax is praised for its clean, English-like structure that closely resembles written algorithms or planning notes. It prioritizes developer approachability and rapid prototyping through dynamic typing, which means you don’t need to declare variable types explicitly. This allows for very concise code. Python’s PEP 8 style guide establishes consistent formatting conventions that make Python code predictable and easy to scan.

For example, you can define a simple add function, assign a string to a variable, and create a formatted message with just a few lines of code:

# Python: Flexible and concise

def add(a, b):

  return a + b

name = "World"

message = f"Hello, {name}!"

Rust uses C-style syntax that requires you to specify types (fn add(a: i32, b: i32) -> i32) and declare when variables can change (let mut message = String::new();). This looks more verbose and adds learning challenges like ownership (like &str for borrowed string slices), but it’s intentional. Rust’s explicit approach catches more errors at compile time and makes program structure clearer, even though you might write more code. Tools like rustfmt handle code formatting automatically.

Here’s how the same functionality looks in Rust, with explicit type declarations and mutability:

// Rust: Explicit types, mutability, and function signature

// Function definition

fn add(a: i32, b: i32) -> i32 {

    a + b

}

fn main() {

    let name: &str = "World";

    let message: String = format!("Hello, {}!", name);

    println!("{}", message);

    let mut count: i32 = 0;

    count += 1;

    println!("Count: {}", count);

    // Using the add function

    let sum = add(5, 10);

    println!("Sum: {}", sum);

}

This core difference – Python’s easy flow versus Rust’s strict structure – is often what developers first notice when choosing between them, and it affects both how you write code and how fast it runs.

Performance and Speed

The differences between Rust and Python become even more apparent when looking at their raw performance.

Rust compiles directly to machine code before you run it, so it’s fast and efficient. Python reads and translates your code line by line as it runs, which is more flexible but slower. Rust’s zero-cost abstractions and lack of a garbage collector also contribute to its speed advantage, especially for CPU-heavy tasks.

You can see the difference in real-world benchmarks where Rust consistently runs faster and uses less memory. However, it’s worth noting that benchmark results are frequently updated and can vary based on the specific tests and implementations used.

These are some commonly observed performance patterns:

  • Execution speed: Rust frequently outperforms Python by substantial margins in CPU-intensive tasks, with advantages ranging from modest improvements to dramatic speed-ups depending on the specific computational workload.
  • Memory efficiency: Rust’s zero-cost abstractions and lack of garbage collection typically result in significantly lower memory usage compared to Python’s runtime overhead.
  • Consistency: These performance characteristics remain relatively stable across different types of computational tasks, from mathematical simulations to algorithmic challenges.

Performance differences will depend on your specific setup and code, but Rust consistently wins for heavy computational work.

The speed gap means Rust works better for large, time-critical systems, while Python’s speed is often good enough when you want to build and test ideas quickly rather than squeeze out maximum performance.

Ease of Use and Learning Curve

Python is famous for its gentle learning curve; its simple syntax and flexibility make it great for beginners who want to build things quickly. For example, reading user input and displaying a personalized greeting takes just a few straightforward lines:

# Python: Reading and printing a name

name = input("Enter your name: ")

print(f"Hello, {name}!")

Rust’s strict compiler and ownership system require much more upfront learning. The same greeting task in Rust immediately throws several complex concepts at you. This code shows how you need explicit imports for basic input/output, function signatures that handle errors (io::Result<()>), mutable string creation (String::new()), explicit error checking (the ? operator), and details like flushing output and trimming input:

// Rust: Reading and printing a name with error handling

use std::io::{self, Write}; // Import necessary modules

fn main() -> io::Result<()> { // main function returns a Result for I/O errors

    print!("Enter your name: ");

    io::stdout().flush()?; // Ensure the prompt is displayed before input

    let mut name = String::new(); // Declare a mutable string to store input

    io::stdin().read_line(&mut name)?; // Read line, handle potential error

    println!("Hello, {}!", name.trim()); // Trim whitespace (like newline) and print

    Ok(()) // Indicate success

}

This heavy upfront learning makes Rust harder to start with, but it’s part of Rust’s design. The language acts like a strict teacher, forcing you to write safe, efficient code from the beginning. You get more control and fewer runtime crashes in exchange for the initial difficulty.

Python gets you started immediately, while Rust makes you work harder upfront for more reliable code.

Typing System (Static vs. Dynamic)

Python uses dynamic typing, where types are checked at runtime, providing great flexibility and accelerating initial development as you rarely need explicit type declarations. This freedom, however, means type-related errors (like TypeError) may only surface during execution, which is a potential risk in larger systems. To address this, Python developers often use tools like mypy, a static type checker that analyzes code before runtime to catch type-related errors early and improve code safety in larger projects.

Rust uses static typing, which means it checks all your types before your code even runs. While this demands more upfront effort to satisfy the compiler’s rigor, it’s a powerful early warning system and significantly reduces type-related runtime errors and improves code maintainability. Rust’s type system includes enums like Option<T> and Result<T, E> to handle the absence of values and recoverable errors explicitly. You need to weigh Python’s development speed against Rust’s early error detection and type safety checks that happen before the program runs.

Memory Management

Python uses automatic garbage collection with reference counting and a cycle detector to clean up memory automatically. This frees you from the burden of memory management, which simplifies coding. But it can cause unpredictable pauses and performance overhead, which can be a problem for time-critical applications like financial trading systems or robotics, where consistent timing is crucial.

Rust takes a completely different approach with its ownership system, which handles memory management while your code compiles. Through ownership rules, borrowing (utilizing & for immutable and &mut for mutable references), and lifetimes, Rust prevents common memory errors like null pointer dereferences, dangling pointers, and use-after-free errors in safe code – all without a runtime garbage collector. While unsafe blocks allow you to bypass these protections when needed, most Rust code benefits from these safety guarantees. This results in highly predictable performance and fine-grained control. However, mastering the borrow checker and its concepts can be challenging if you’re used to garbage-collected languages.

The choice here is stark: Python offers easy memory management, while Rust demands more work upfront for memory safety and performance.

Concurrency and Parallelism

The threading module in Python’s common CPython implementation allows you to easily create threads:

# Python: Basic thread creation

import threading

def python_task():

    print("Hello from a Python thread!")

# To run it:

# thread = threading.Thread(target=python_task)

# thread.start()

# thread.join()

However, Python’s Global Interpreter Lock (GIL) restricts multiple native threads from executing Python bytecode simultaneously for CPU-bound tasks, which limits true parallelism on multicore processors. This means that even on a system with multiple CPU cores, only one Python thread can actually execute at any given time – threads must take turns rather than running simultaneously on different cores. This makes Python’s threading module most effective for I/O-bound operations, where threads can release the GIL while waiting for external operations.

For efficient single-threaded concurrency in I/O tasks, Python offers asyncio. For true CPU parallelism, you need the multiprocessing module, which creates separate processes to bypass the GIL but uses more memory and requires complex communication between processes. While experimental efforts like PEP 703 aim to make the GIL optional, Python’s concurrency requires navigating these trade-offs.

Rust is known for its fearless concurrency, thanks to its ownership and type systems that prevent data races at compile time. Creating a basic thread is straightforward using std::thread:

// Rust: Basic thread creation

use std::thread;

fn rust_task() {

    println!("Hello from a Rust thread!");

}

// To run (e.g., in main function):

// let handle = thread::spawn(rust_task);

// handle.join().unwrap();

Critically, threads like this in Rust can run in true parallel on different CPU cores because Rust does not have a GIL. Compile-time checks involving Send and Sync traits ensure that data shared between threads is handled safely. For more complex scenarios, Rust offers powerful tools like async/await for asynchronous programming and types like Arc and Mutex for safe shared-state concurrency.

Summing up, Rust excels at building highly concurrent, multithreaded applications that demand maximum hardware efficiency.

Ecosystem and Libraries

Python has an extensive, mature ecosystem; PyPI (Python Package Index) offers an unparalleled array of libraries for nearly every domain, especially data science (eg NumPy, pandas, and TensorFlow), web development (eg Django, Flask, and FastAPI), and automation, complemented by a batteries-included standard library.

Rust’s ecosystem, managed via the Cargo build tool and package manager sourcing from crates.io, is younger but expanding. It’s particularly good for systems programming, WebAssembly, networking, and command line tools, with libraries engineered for performance and safety. Interestingly, Rust is increasingly used to build high-performance tools for the Python world (eg Polars, Ruff, and uv), showing how the two languages can also work together rather than just compete.

The ecosystem choice often depends on project requirements: Python’s mature, extensive libraries make it ideal for rapid development and established domains like data science and web development, while Rust’s performance-focused ecosystem shines when you need maximum efficiency and safety or are building foundational tools that others will depend on.

IDE and Tooling Support

Python’s great tooling matches its popularity; it’s been the #2 programming language since early 2020. Comprehensive IDEs like PyCharm and interactive tools like Jupyter Notebooks give you everything you need: debuggers, linters, test runners, and more. All of which makes Python even easier to use.

Rust’s tooling evolution reflects its remarkable developer satisfaction story. How does a systems language achieve 83% developer retention and “Most Admired” status for nine consecutive years? Partly through exceptional tooling that makes complex concepts manageable. Cargo revolutionized build management, rust-analyzer provides precise code intelligence, and Clippy offers intelligent linting. As more companies adopt Rust, IDEs are also becoming more mature, with tools like JetBrains RustRover providing specialized support for Rust’s ownership model.

Color-coded inlay error descriptions

It’s a classic feedback loop: Python’s popularity drives better tooling, which makes Python even more popular. Rust’s exceptional tooling experience explains why developers who try it stick with it. Both languages benefit from this virtuous cycle, serving different developer needs and project requirements.

Comparison Table: Rust vs. Python

Here’s an overview of the key differences between Rust and Python:

FeatureRustPython
Syntax & ReadabilityExplicit, C-family syntax; can be verbose but aims for clarity and may offer lower cognitive complexity; rustfmt for style.Minimalist, near-pseudocode style; generally beginner-friendly and concise; PEP 8 for style conventions.
Performance & SpeedCompiled to optimized machine code; very high raw performance and no GC pauses; ideal for CPU-bound and time-sensitive systems.Primarily interpreted; slower execution speed for CPU-bound tasks; potential GC pauses; often adequate for I/O-bound tasks and rapid development.
Ease of Use & Learning CurveSteeper learning curve due to its strict compiler, ownership model, and lifetimes; rewards effort with safe and efficient code.Gentle learning curve with simple syntax and dynamic typing; facilitates rapid development and prototyping; very accessible to beginners.
Typing SystemStatic, strong compile-time type enforcement; significantly reduces runtime type errors and enhances maintainability; expressive type system.Dynamic typing with runtime type checking; offers flexibility and reduces boilerplate, but type errors may surface at runtime; optional static type hints available.
Memory ManagementCompile-time enforcement via ownership, borrowing, and lifetimes; no garbage collector, leading to predictable performance and control.Automatic garbage collection (primarily reference counting with a cycle detector); simplifies memory management for developers but can introduce overhead and pauses.
Concurrency & Parallelism“Fearless concurrency” due to compile-time data-race prevention; no GIL, enabling true multicore parallelism for CPU-bound tasks.GIL in CPython limits true CPU-bound parallelism in threads; asyncio is effective for I/O-bound concurrency; multiprocessing for CPU-bound tasks.
Ecosystem & LibrariesRapidly growing ecosystem via Cargo and crates.io; strong in systems programming, WebAssembly, networking, and CLI tools; performance-focused libraries.Vast, mature ecosystem via PyPI; dominant in data science, machine learning, web development, and automation; extensive “batteries-included” standard library.
IDE & Tooling SupportSignificantly improved and maturing; strong support from rust-analyzer, Cargo, and Clippy; dedicated IDEs like RustRover by JetBrains enhance productivity.Excellent and mature tooling; widely supported by IDEs like PyCharm and VS Code, Jupyter Notebooks for data science, and a rich collection of linters and debuggers.


Career Insights

So, the differences are clear, but how do those differences actually influence which you should use?

The career implications of choosing Python versus Rust extend far beyond syntax preferences—they shape your earning potential and job market positioning. Python dominates the hiring landscape with 42% of recruiters actively seeking Python skills, translating into substantial opportunities and a more mature and stable job market. This demand drives competitive compensation, with Python developers earning up to $151,000 annually in the US.

Python’s career appeal lies in its versatility across high-growth sectors. The language powers data science, machine learning, web backends, enterprise applications, and automation, which are all in high demand.

Rust presents a different but compelling career trajectory. While job openings are fewer, the compensation could be higher: some Rust developers earn up to $198,375 annually. However, Rust’s lower market share also means more volatile job opportunities, with Rust developer wages ranging dramatically from $40.38 to $64.66 per hour and an average of $53 per hour. This wage variation suggests that Rust’s job market is less predictable than Python’s, which enjoys a consistent demand for developers.

Overall, Rust’s career advantage lies in specialization. 43% of organizations are currently using Rust for non-trivial production use cases like server backends, web3 services, and cloud technologies. These infrastructure roles have the potential to command premium salaries due to their critical nature and the specialized expertise required.

Your career choice comes down to whether you want Python’s broad job market or Rust’s growing specialization in high-performance, reliable systems.

Hybrid Development Strategies: Rust-Enhanced Python Development

The choice between Python and Rust isn’t always black and white. Many developers are going for a third option: use both languages strategically. Keep Python for your main application logic where readability matters, but drop in Rust for the parts that need serious speed.

This hybrid approach is gaining real momentum. Major Python tools are being rewritten in Rust with dramatic results. Pydantic-core rewrote its validation logic in Rust and got 17x faster performance. Ruff provides Python linting that’s 10–100x faster than existing tools while installing with a simple pip install ruff.

The PyO3 ecosystem makes this integration surprisingly easy. You can write performance-critical functions in Rust and call them directly from Python. Start by prototyping in Python’s friendly syntax, then optimize the slow parts with Rust. Tools like pyo3_bindgen even automatically generate the connections between your Python and Rust code.

This co-usage model is appearing in production across major projects, including FastAPI, Jupyter, Pydantic, and Polars, showing its viability at scale. This approach offers immediate performance gains without requiring complete codebase rewrites, making it an attractive middle ground between Python’s accessibility and Rust’s performance characteristics.

Conclusion: Choosing Your Language – Rust and Python

This comparison shows that Rust and Python are built for different needs and approaches. Modern software development increasingly reveals that their strengths work together rather than compete.

Python remains excellent for rapid application development, data analysis, machine learning, and scripting, propelled by its accessible syntax, flexibility, and mature ecosystem. On the other hand, Rust is the best choice for performance-critical applications, systems programming, embedded development, and projects where memory safety and efficient concurrency are essential.

If you’re trying to build reliable, high-performance systems with Rust or exploring the language professionally, JetBrains RustRover offers a minimally configured environment with deep language support. With a non-commercial license available for learners and hobbyists, it’s a superb tool to grow with as you delve into Rust.

Curious about how Rust can benefit from other programming languages? Check out our previous blog post comparing Rust with Java and Go.

Read the whole story
alvinashcraft
5 hours ago
reply
Pennsylvania, USA
Share this story
Delete

Fixing Enterprise Apps with AI: The T+n Problem

1 Share

We’ve been watching enterprises struggle with the same customer service paradox for years: They have all the technology in the world, yet a simple address change still takes three days. The problem isn’t what you think—and neither is the solution.

Last month, I watched a colleague try to update their address with their bank. It should have been simple: log in, change the address, done. Instead, they spent 47 minutes on hold, got transferred three times, and was told the change would take “3–5 business days to process.” This is 2025. We have AI that can write poetry and solve complex math problems, yet we can’t update an address field in real time.

This isn’t a story about incompetent banks or outdated technology. It’s a story about something more fundamental: the hidden mathematics of enterprise friction.

The Invisible Math That’s Killing Customer Experience

Every enterprise process has two numbers that matter: T and n.

“T” is the theoretical time it should take to complete a task—the perfect-world scenario where everything works smoothly. For an address change, T might be 30 seconds: verify identity, update database, confirm change.

“n” is everything else. The waiting. The handoffs. The compliance checks. The system incompatibilities. The human bottlenecks. “n” is why that 30-second task becomes a 47-minute ordeal.

According to Forrester, 77% of customers say that valuing their time is the most important thing a company can provide. Aberdeen Group found that companies with excellent service achieve 92% customer retention compared to just 33% for poor performers. Yet most enterprises are still optimizing for compliance and risk mitigation, not customer time.

The result? A massive “T+n” problem that’s hiding in plain sight across every industry.

Why Everything We’ve Tried Has Failed

We’ve seen enterprises throw millions at this problem. Better training programs. Process reengineering initiatives. Shiny new CRM systems. Digital transformation consultants promising to “reimagine the customer journey.” These efforts typically yield 10-15% improvements—meaningful but not transformative. The problem is architectural. Enterprise processes weren’t designed for speed; they were designed for control.

Consider that address change again. In the real world, it involves:

  • Identity verification across multiple systems that don’t talk to each other
  • Compliance flagging for anti-money-laundering rules
  • Risk assessment for fraud prevention
  • Routing to specialized teams based on account type
  • Manual approval for any exceptions
  • Updating downstream systems in sequence
  • Creating audit trails for regulatory requirements

Each step adds time. More importantly, each step adds variability—the unpredictable delays that turn a simple request into a multiday saga.

When AI Agents Actually Work

We’ve been experimenting with agentic AI implementations across several enterprise pilots, and we are starting to see something different. Not the usual marginal improvements but a genuine transformation of the customer experience.

The key insight is that intelligent agents don’t just automate tasks—they orchestrate entire processes across the three dimensions where latency accumulates.

People problems: Human agents aren’t available 24-7. They have specialized skills that create bottlenecks. They need training time and coffee breaks. Intelligent agents can handle routine requests around the clock, escalating only genuine edge cases that require human judgment. One financial services company we worked with deployed agents for card replacements. Standard requests that used to take 48 hours now complete in under 10 minutes. The customer types out their request, the agent verifies their identity, checks for fraud flags, orders the replacement, and confirms delivery—all without human intervention.

Process problems: Enterprise workflows are designed as sequential approval chains. Request goes to analyst, analyst checks compliance, compliance routes to specialist, specialist approves, approval goes to fulfillment. Each handoff adds latency. Intelligent agents can prevalidate actions against encoded business rules and trigger only essential human approvals. Instead of six sequential steps, you get one agent evaluation with human oversight only for genuine exceptions.

Technology problems: The average enterprise runs customer data across 12–15 different systems. These systems don’t integrate well, creating data inconsistencies and manual reconciliation work. Instead of requiring expensive system replacements, agents can orchestrate existing systems through APIs and, where APIs don’t exist, use robotic process automation to interact with legacy screens. They maintain a unified view of customer state across all platforms.

The AI Triangle: Why You Can’t Optimize Everything

But here’s where it gets interesting—and where most implementations fail.

Through our pilots and outcomes, we discovered what we call the AI Triangle: three properties that every agentic AI system must balance. Similar to the CAP theorem in distributed systems (where you can’t have perfect consistency, availability, and partition tolerance simultaneously), the AI Triangle forces you to choose between perfect autonomy, interpretability, and connectivity. Just as CAP theorem shapes how we build resilient distributed systems, the AI Triangle shapes how we build trustworthy autonomous agents. You can optimize any two of these properties, but doing so requires compromising the third. This is a “pick 2 of 3” situation:

Autonomy: How independently and quickly agents can act without human oversight

Interpretability: How explainable and audit-friendly the agent’s decisions are

Connectivity: How well the system maintains real-time, consistent data across all platforms

The AIC Triangle
The AI Triangle

You can pick any two, but the third suffers:

Autonomy + interpretability: Agents make fast, explainable decisions but may not maintain perfect data consistency across all systems in real time.

Interpretability + connectivity: Full audit trails and perfect data sync, but human oversight slows everything down.

Autonomy + connectivity: Lightning-fast decisions with perfect system synchronization, but the audit trails might not capture the detailed reasoning compliance requires.

This isn’t a technology limitation—it’s a fundamental constraint that forces deliberate design choices. The enterprises succeeding with agentic AI are those that consciously choose which trade-offs align with their business priorities. This isn’t a technical decision—it’s a business strategy. Choose the two properties that matter most to your customers and regulators, then build everything else around that choice.

The Hidden Costs Nobody Mentions

The vendor demos make this look effortless. Reality is messier.

Data quality is make-or-break: Agents acting on inconsistent data don’t just make mistakes—they make mistakes at scale and speed. Worse, AI errors have a different signature than human ones. A human might transpose two digits in an account number or skip a required field. An AI might confidently route all Michigan addresses to Missouri because both start with “MI,” or interpret every instance of “Dr.” in street addresses as “doctor” instead of “drive,” creating addresses that don’t exist. These aren’t careless mistakes—they’re systematic misinterpretations that can cascade through thousands of transactions before anyone notices the pattern. Before deploying any autonomous system, you need to master data management, establish real-time validation rules, and build anomaly detection specifically tuned to catch AI’s peculiar failure modes. This isn’t glamorous work, but it’s what separates successful implementations from expensive disasters.

Integration brittleness: When agents can’t use APIs, they fall back to robotic process automation to interact with legacy systems. These integrations break whenever the underlying systems change. You need robust integration architecture and event-driven data flows.

Governance gets complex: Autonomous decisions create new risks. You need policy-based access controls, human checkpoints for high-impact actions, and continuous monitoring. The governance overhead is real and ongoing.

Change management is crucial: We’ve seen technically perfect implementations fail because employees resisted the changes. Successful deployments involve staff in pilot design and clearly communicate how humans and agents will work together.

Ongoing operational investment: The hidden costs of monitoring, retraining, and security updates require sustained budget. Factor these into ROI calculations from day one.

A Roadmap That Actually Works

After watching several implementations succeed (and others crash and burn), here’s the pattern that consistently delivers results:

Start small, think big: Target low-risk, high-volume processes first. Rules-based operations with minimal regulatory complexity. This builds organizational confidence while proving the technology works.

Foundation before features: Build integration architecture, data governance, and monitoring capabilities before scaling agent deployment. The infrastructure work is boring but essential.

Design with guardrails: Encode business rules—it’s preferable to move them into a policy store so that agents can get them executed at run time using a policy decision point (PDP) like Open Policy Agent (OPA), implement human checkpoints for exceptions, and ensure comprehensive logging from the beginning. These constraints enable sustainable scaling.

Measure relentlessly: Track the most critical metrics in operations with a focus on reducing “n” toward zero:

  • Average handling time (AHT)
  • Straight-through processing rate (STP Rate %)
  • Service level agreement (SLA) performance
  • Customer satisfaction
  • Cost per transaction

These metrics justify continued investment and guide optimization.

Scale gradually: Expand to adjacent processes with higher complexity only after proving the foundation. Concentric circles, not big bang deployments.

The Experience That Changes Everything

We keep coming back to that colleague trying to change their address. In a world with properly implemented agentic AI, here’s what should have happened:

They log into their banking app and request an address change. An intelligent agent immediately verifies their identity, checks the new address against fraud databases, validates it with postal services, and updates their profile across all relevant systems. Within seconds, they receive confirmation that the change is complete, along with updated cards being shipped to the new address. No phone calls. No transfers. No waiting. Just the service experience that matches the digital world we actually live in.

The Bigger Picture

This isn’t really about technology—it’s about finally delivering on the promises we’ve been making to customers for decades. Every “digital transformation” initiative has promised faster, better, more personalized service. Most have delivered new interfaces for the same old processes.

Agentic AI is different because it can actually restructure how work gets done, not just how it gets presented. It can turn T+n back into something approaching T.

But success requires more than buying software. It requires rethinking how organizations balance speed, control, and risk. It requires investing in the unglamorous infrastructure work that enables intelligent automation. Most importantly, it requires acknowledging that the future of customer service isn’t about replacing humans with machines—it’s about orchestrating humans and machines into something better than either could achieve alone.

The technology is ready. The question is whether we’re prepared to do the hard work of using it well.



Read the whole story
alvinashcraft
5 hours ago
reply
Pennsylvania, USA
Share this story
Delete

This is How AI Changes Software Developer Roles

1 Share

AI coding tools are rapidly moving from “interesting but too unreliable for real projects” to useful, indispensable aids, despite their current problems. Even incremental progress expands the range of tasks they can handle.

We’re also improving the tools and processes that make AI agents more reliable and easier to integrate into development workflows.

What does that mean for developers, especially juniors entering the industry?

Craft vs. glue work

Most software development falls into two categories:

  • Finely crafted work of art

This demands pushing performance, scalability, or reliability to their absolute limits. In such cases, experts invest days, weeks, or even months perfecting a few lines of code that deliver massive impact. Research on new algorithms, protocols, or work close to the hardware requires a high level of skill, experience, expertise, and good judgment.

  • Standard, repeatable plumbing work

This isn’t glamorous, but it’s necessary for a working product and a good user experience. Things like billing, onboarding, API integrations, CRUD, analytics, custom business logic, and the boilerplate glue code that turns core functionality into a usable product.

The majority of software written today, both in-house and for external users, falls into the second category – ordinary, day-to-day work. The median developer spends most of their time on such tasks.

Those tasks are exactly where AI tools excel. Their training data contains countless examples of similar code, and there are accepted best practices, tools, and frameworks. On these tasks, an AI agent can efficiently generate boilerplate, translate requirements into working code, or scaffold an entire feature.

AI empowers product owners

AI tools now let product owners and product managers be more directly involved with implementation.

While they should avoid drive-by coding, they can experiment and create prototypes, mockups, or proof-of-concept changes to identify priorities, validate user experience, and uncover implementation details before asking developers for a production-quality version.

Meet AI-native product developer

Traditionally, product managers decide what to build, product owners prioritize and flesh out features, and developers implement them. With AI capable of producing more and more code of acceptable quality, the boundary between these roles is shrinking. Someone who understands both the product goals and the technical details can often go directly from idea to working code with AI.

That makes a developer who understands the product side far more valuable, as well as a technical product owner with the skills and experience to grasp the details. In many organizations these roles may increasingly overlap and combine into a unified “product developer” role.

The AI-native product developer understands the business context, knows the user and the problem being solved, and is empowered to define and prioritize work. They are also technical enough to work closely with AI on implementation, guide it, review the code, and take responsibility for the quality of the end result.

How junior developers can adapt

Many junior developers are experiencing existential dread: will AI replace them? Many technology leaders worry about the same thing: if nobody hires juniors because AI does the work, how will anyone gain experience and become a senior?

It’s an important question. The answer is surprisingly positive but requires commitment from the junior developer, their manager, and the organization.

The key is recognizing that one of the junior’s jobs – if not the most important – is to learn. AI is an excellent learning tool. Imagine an infinitely patient, knowledgeable onboarding buddy that can explain a codebase, project, framework, or language. Used this way, AI can be an immense help to a newcomer.

When using AI, ask it to explain how and why something works. LLMs may hallucinate, but they usually give a good first approximation. Dig deeper until you’re satisfied. Experiment to verify. Explore alternatives. Ask “Why not X instead?” Break down problems into smaller parts. Play the detective, treating AI as a somewhat unreliable witness.

Whatever you do, don’t use AI as a shortcut! That’s where the danger lies. If you let AI think for you, the results will be subpar and you’ll miss out on the learning. Above all, never use AI output as the final result without understanding how and why it works.

While it’s easy to blame “lazy juniors” for copy-pasting results without thinking, most of the blame lies with the organization.

If a manager expects a junior developer to be much quicker because they have access to AI, of course the junior will “cheat.” It’s not due to laziness; it’s due to unreasonable expectations.

Even with AI, juniors will still be slower because they’re constrained by the pace of their own learning, not typing speed. Pressuring them will impede learning and hurt productivity for the entire organization.

The tools are here – use them wisely

AI is changing how software gets built. Some roles will shift a lot; others only slightly. But everyone still has agency. Whether you go deeper into specialized engineering, broaden into product ownership, or use AI as a learning partner, how you adapt is the deciding factor. The tools are here. What you do with them is up to you.

The post This is How AI Changes Software Developer Roles appeared first on ShiftMag.

Read the whole story
alvinashcraft
5 hours ago
reply
Pennsylvania, USA
Share this story
Delete

How to Write Technical Specs That Actually Ship

1 Share

\ If you're reading this, chances are you've stared at a half-finished technical spec at 2AM, trying desperately to wrap it up before standup. Or maybe you've been on the receiving end — slogging through someone else's 47-page opus, wondering if unlimited PTO is worth it after all.

But here's the thing: when done well, technical specifications aren't homework from your tech lead. They're one of the most powerful tools you have to drive features from idea to production. I'm going to show you how I approach writing them, based on my experience at Monzo and lessons I've picked up from engineers at AWS, Google, and Meta.

My background is software engineering, but a lot of this transfers to other technical disciplines. Let's dive in.

Why write tech specs anyway?

I get it. Writing documentation feels like busywork when you could be shipping code. But stick with me here, because a good technical spec does way more than satisfy some process requirement.

When used well, technical specs help you:

  • Validate the idea before you waste weeks building it — I've killed more bad ideas during the spec phase than I care to admit. Writing forces you to think through edge cases you'd otherwise discover three sprints in.
  • Get buy-in from people who actually matter — Your staff engineer, your product manager, that principal engineer who's seen it all. Getting their thumbs up early means you won't be defending your approach in a contentious PR review later.
  • Iterate on the high-level plan with smarter people than you — I'm not the smartest person in the room, and if you're honest with yourself, neither are you. A spec is how you tap into collective intelligence.
  • Share knowledge with your team — Onboarding new engineers becomes "read this spec" instead of "let me explain this architecture for the third time this week."
  • Share knowledge with stakeholders outside engineering — Your product manager, compliance team, or finance folks need to understand what you're building and why. A well-written spec bridges that gap.
  • Use as validation when work is complete — Did you actually solve what you set out to solve? The spec is your scorecard.

Spec-driven development and LLMs

Here's where things get interesting. LLMs have changed how I think about technical specifications.

LLMs work best with strict, air-gapped technical specifications. The less you leave up to imagination, the better. A well-defined spec can be fed into an LLM for automatic ticket creation or even technical implementation. You can generate tests from your specifications. You can use LLMs to ideate on parts of your proposal, ask them to do research, or even help identify flaws in your logic.

I have another article coming in the next few weeks dedicated to spec-driven development with LLMs. Keep an eye out for it — I've been experimenting with this at scale and the results are genuinely impressive.

But even if you never touch an LLM, the discipline of writing a rigorous spec pays dividends. It forces clarity. And clarity is how good software gets built.

Anatomy of a good technical proposal (part 1)

A good technical spec is well-structured, easy to understand, and actionable. It should be skimmable for executives and detailed enough for the engineer implementing it. That's a narrow tightrope to walk, but I've found a pattern that works.

Here's the first half of the anatomy. This is what you write before you get buy-in.

What are you trying to solve?

Start with the problem statement. Not the solution — the problem. I've seen so many specs that jump straight into "we should build a microservice with GraphQL" without ever explaining what's broken.

Your problem statement should be clear enough that someone outside your team can understand it. At my startup, Tandem, I have a simple test: could Jack, my non-technical co-founder understand the problem, just by reading it? If not, rewrite it.

Some techniques that work:

  • Use concrete examples: Not "users experience latency" but "users wait an average of 8 seconds for their transaction history to load, and we're seeing 23% drop-off at this screen."
  • Show the business impact: "This costs us approximately £2.3M annually in lost conversions" is a lot more compelling than "this is slow."
  • Include quotes or data: If you have user research, customer complaints, or monitoring data, drop it in. Evidence beats opinion.

What are you NOT trying to solve?

This is criminally underused and incredibly valuable. Scope creep is real, and if you don't explicitly call out what's out of scope, you'll spend the next month arguing about edge cases that don't matter.

Imagine this: you get a proposal from the platform team to upgrade to their shiny new shared UI library. Seems straightforward, right? Just swap out some imports, update a few components. But then you discover the new library uses a completely different routing system underneath. Now you need to refactor every route in your application. Then you realise your product team's navigation patterns don't quite fit the new model, so you need custom wrappers. Oh, and every other product team that depends on this shared library? They're all blocked on the same changes. What started as a "simple utility update" just turned into a six-month migration effort that's holding up feature work across the entire organisation.

An explicit "what we're NOT solving" section could have caught this early. It gives you permission to say no. It keeps the culture of "why-notism" at bay. And it protects your timeline from those seemingly innocent suggestions that balloon into organisational nightmares.

Why should this problem be solved?

Not all problems need solving. Some can live with workarounds. Some aren't actually problems, just minor inconveniences. Why should this one get attention and engineering time?

Make the case clearly:

  • Impact on users: How many people does this affect? How badly?
  • Impact on business: Revenue, retention, conversion, operational costs?
  • Impact on engineering: Is this tech debt that's slowing down every other project?
  • Impact on compliance or risk: Will this blow up in six months if we don't fix it?

I'm a big believer in showing your work here. Don't just say "this is important." Show the math. We once based a proposal to change the text on a button on an experiment that proved a 23% uptick in conversion, which translated to +£12k ARR. That's a much easier sell than "this button copy could be better."

Why now?

Timing matters more than people think. I've seen perfectly good proposals get shelved because it wasn't the right time. And I've seen mediocre proposals get greenlit because the timing was perfect.

Why is now the best time to solve this problem? Think about:

  • External deadlines: Regulatory changes, marketing campaigns, conference demos
  • Internal readiness: Do you have the right infrastructure in place? The right team capacity?
  • Dependencies: Are there other projects that need to ship first?
  • Opportunity windows: Sometimes, there's a narrow window when solving something is easier than it'll ever be again.

Here's a specific example. We had a proposal to use local time zones on bank account statements instead of UTC. The timing was critical — we wanted to do it right after daylight savings ended in the UK. That gave us six months when UTC and local time were the same. We also had to keep historical statements in their original time zone for legal reasons. Starting right after the clocks changed meant we had the maximum window before the next transition, which greatly reduced the chances of someone needing to generate a statement with multiple logical timezone conversions.

If we'd done it in March instead of November, we'd have introduced way more complexity and edge cases. Timing wasn't just important — it was the difference between a clean migration and a nightmare.

Requirements

What's 100% non-negotiable? What are the absolute bare minimums that can't be excluded?

Think of requirements as the must-haves. Not the nice-to-haves, not the maybe-we-should-also, but the core things that must be true for this to be worth doing at all.

I structure requirements as testable statements:

  • "Transaction history must load in under 2 seconds at p95"
  • "Solution must work for users on iOS 14 and above"
  • "Data must be encrypted at rest and in transit"
  • "Must not require any customer to re-authenticate"

Notice these are specific and verifiable. You can test whether you've met them. Vague requirements like "must be fast" or "should be secure" are useless. Pin them down.

Bird's eye view of the solution

This is where most people screw up. They immediately dive into implementation details — which database, which API, which service mesh. Stop. Not yet.

The bird's eye view should be understandable by non-technical stakeholders. I'm talking product managers, operations folks, maybe even finance. If you can't explain your solution to someone who doesn't know what Kubernetes is, you don't understand it well enough yourself.

Some rules for this section:

No tech talk. Seriously. Not yet. Instead of "we'll deploy a new microservice with a Redis cache and expose it via GraphQL," say "we'll store frequently-accessed data closer to the user so it loads instantly instead of making a slow trip to the database every time."

Think user-centric. What changes from the user's perspective? What does success look like for them?

Anchor on outcomes, work backwards. Start with "users will see their transaction history load in under 1 second" and then explain at a high level how you'll achieve that.

If possible, offer multiple potential solutions. Sometimes there are genuinely different approaches. Lay out two or three options with pros and cons. This shows you've thought it through and gives stakeholders a real choice.

At this point, you should stop and reassess. Here's my checklist before moving forward:

  • [ ] Problem statement is clear and specific
  • [ ] Business impact is quantified
  • [ ] Non-goals are explicitly stated
  • [ ] Requirements are testable
  • [ ] High-level solution makes sense to a non-engineer
  • [ ] I'm confident this is worth doing

If you can't check all these boxes, don't proceed to part 2 yet. Seriously. I've wasted weeks writing detailed implementation plans for ideas that weren't actually worth doing.

Getting buy-in and collaboration

Your next step is to get feedback from key stakeholders. This is not a formality. This is where your spec either gets better or falls apart.

Some hard-won tips for a successful pitch:

Use a platform that makes collaboration easy. I'm a fan of Notion because of real-time comments and collaboration, but Google Docs works too. I've even seen some true pioneers use Figma for this once! The important thing is that people can leave inline comments and suggest changes. Avoid PDFs or Word docs attached to emails — you want this to be a living document.

Illustrate your points. If you're proposing changes to a user journey, create mockups or wireframes. If you're optimising a service, show current metrics with screenshots from your monitoring. I recommend Figma (high-fidelity) or Excalidraw (low-fidelity) for user journeys and screenshots from Grafana or Datadog for metrics. Visuals aren't decoration — they make your argument concrete.

Getting feedback should be your main objective. Leave your ego at the door. Every piece of criticism you get now is a production issue you won't have later. I've had specs torn apart in review, and it sucks. But you know what's worse? Building something for three months only to have it fail in production because you missed something obvious.

Guide your contributors. If someone leaves a comment like "I don't like this approach," kindly ask them to explain why and suggest an alternative. Vague negativity doesn't help anyone. But genuine concerns with reasoning? That's gold.

Be ready to defend your value proposition. Someone will ask "is this worth the engineering time?" You need an answer. The best answers have numbers. "This can lead to a 27% reduction in database hosting costs, resulting in annual savings of £1.5M" is a lot more compelling than "this will make things more efficient."

A plan worth implementing (part 2)

Do you treat tech specs as a solo effort? Write it alone, share it for review, get some comments, ship it? That's not how the best specs get written.

The best technical specifications are the result of strong collaboration. You write the first draft, sure. But then you invite smart people to make it better. You incorporate their feedback. You adjust based on what you learn. The spec evolves.

This second part of the anatomy is more dynamic. It varies depending on your company's culture, your team's practices, and the type of project. But there are principles that hold across contexts.

Best practices and company standards

Your implementation plan should be an extension of your company's established practices. If you're using React and Next.js for web frontends, your spec should use React and Next.js. If your company has a standard CI/CD pipeline, use that.

The exception is when you're explicitly proposing to change an established practice. Maybe you want to introduce a new framework or migrate to a different database. That's fine — but make your case with evidence.

When I've proposed changes to established practices, I always include:

  • Outside proof of concept: Has another company done this successfully? Show case studies.
  • Prior art from adjacent domains: Have we used similar approaches in different contexts?
  • Clear reasoning for why the current approach doesn't work: Don't just say "this is better." Show where the current approach fails.
  • Migration path: How do we get from here to there without breaking everything?

At Superbet, I led a team that built a dedicated UI library to replace the continuously problematic Ant Design we were using before. We made it a 100% drop-in replacement via a compatibility layer — technically perfect. But I still failed to convince most teams to adopt it, because I couldn't drive the business case home. That's the lesson here: replacing established tech is tough even when your solution is objectively better. You need the numbers, the case studies, the migration path. Technical excellence alone doesn't win the argument.

Prior art and alternatives considered

Is there something similar that already exists? Have you implemented features with similar attributes in the past? All of this is worth mentioning.

Best case scenario: you discover you don't need to build anything new. You can reuse an existing solution. If this happens, don't throw away your spec. Attach it to the knowledge base related to the solution you found. It helps the maintainers understand who their stakeholders are and why people need this. If they want to sunset, transfer ownership, or refactor their software, they'll know who to talk to.

I've saved months of engineering time by finding prior art. We used Statsig to ship experiments, but we'd written our own SDK on top of it instead of using the official one. Our implementation didn't support persisting allocations, which meant we couldn't run sticky experiments. I started speccing out how to add this feature — thinking I'd need to build it from scratch — and went on a bit of a wild goose chase. Then I found an existing, domain-specific service that already solved persistence for a different use case. I just copied the implementation pattern. What I thought would take weeks, only took two days.

What other approaches did you evaluate? Why didn't they make the cut?

This shows you've done your homework. It also prevents people from suggesting alternatives you've already considered and rejected. I usually include a short table:

| Approach | Pros | Cons | Why we rejected it | |----|----|----|----| | Use existing service X | Fast, proven | Doesn't support feature Y | Missing critical functionality | | Build custom solution | Perfect fit | High maintenance cost | Not worth the ongoing burden | | Third-party API | Easy integration | Vendor lock-in, high cost | £250K annually vs £30K to build |

The technical approach

Now — finally — you can get into the details. This is where you explain the actual implementation, architecture decisions, and tech stack choices.

Use diagrams. Architecture diagrams, sequence diagrams, data flow diagrams. I recommend Draw.io (now diagrams.net) because it's free and has decent collaboration features. The disadvantage is there's no live sharing like Figma, so you'll need to export and embed images.

For sequence diagrams, I like Mermaid because you can write them in markdown and they render automatically in most tools. Here's a simple example:

sequenceDiagram
    User->>API: Request transaction history
    API->>Cache: Check cache
    Cache-->>API: Cache miss
    API->>Database: Query transactions
    Database-->>API: Return results
    API->>Cache: Store in cache
    API-->>User: Return results

The above mermaid chart, rendered on mermaidchart.com

Explain the "why" behind technical decisions, not just the "what." Don't just say "we'll use Redis for caching." Say "we'll use Redis for caching because our access patterns are heavily read-biased (95% reads vs 5% writes) and we need sub-millisecond latency. We considered Memcached but chose Redis because we need data structures like sorted sets for timeline features."

Call out performance characteristics and scalability limits. Be honest about where this will break. "This approach works up to about 100,000 requests per second. Beyond that, we'll need to shard." That's way more useful than pretending your solution scales infinitely.

Mention data models, API contracts, key interfaces. Show what the data looks like. Show what the API requests and responses look like. I usually include JSON examples:

{
  "transaction_id": "tx_123",
  "amount": 1250,
  "currency": "GBP",
  "timestamp": "2025-01-15T14:30:00Z",
  "category": "groceries"
}

:::info A benefit of embedding models in a transferrable format, like JSON is that this can then be copy/pasted into a test or mock service later on. Saves some time and cuts down on ambiguity.

:::

Highlight any new infrastructure or services needed. If this requires spinning up new services, databases, or third-party integrations, call it out. These often have cost implications and procurement delays.

Breaking down the work

How do you decompose this solution into milestones and tasks that can be tracked? This is project management, but it's your job to provide the structure.

Split into phases that deliver incremental value. Ship something usable early. I'm a big fan of the MVP → MLP → Full Product progression:

  • MVP (Minimum Viable Product): The absolute smallest thing you can build to validate the approach. Usually internal-only or limited to a small percentage of users.
  • MLP (Minimum Lovable Product): The smallest version that's actually good enough for real users to use and enjoy.
  • Full Product: All the bells and whistles.

Don't try to ship everything at once. This is tough to admit, but in many cases, we ended up keeping the MLP as the end-result, because it was "good enough".

Identify critical path items vs nice-to-haves. What must ship for this to work? What's optional? Use a prioritisation framework. I like MoSCoW (Must have, Should have, Could have, Won't have).

Call out what can be parallelised vs must be sequential. Can the backend and frontend teams work simultaneously? Or does the backend need to ship first? Making dependencies explicit helps with planning.

Assign rough t-shirt sizes or story points if your team uses them. I actually hate sizing and I'm bad at it too. I prefer developer-hours, developer-days, developer-weeks as units of measurement, when discussing timelines. If you're unsure, go up one measurement, e.g.: not sure if 3 or 4 developer days? Use 1 developer-week.

Make dependencies between tasks explicit. Use a simple dependency graph or just list them: "Task B depends on Task A being complete. Task C can start in parallel with Task B."

Success metrics

How will you measure if the implementation actually worked? What are the KPIs?

This is where you define victory conditions. And you need to be specific. "Make it faster" is not a success metric. "Reduce p95 API latency from 500ms to under 200ms" is a success metric.

Define measurable outcomes. Examples:

  • Latency reduction: "p95 latency drops from 800ms to 200ms"
  • Error rate decrease: "5xx errors drop from 0.3% to below 0.1%"
  • Conversion improvement: "checkout completion rate increases from 73% to 80%"
  • Cost savings: "database costs decrease by £40K annually"

Set baseline metrics before implementation. You need to know where you're starting. I take screenshots of dashboards showing current performance and attach them to the spec. It's way too easy to forget what things were like before you started.

Specify how you'll track these metrics. Will you build a dashboard? Set up alerts? Run an A/B test? Be explicit about measurement methodology.

Include both business metrics and technical metrics. Product managers care about revenue and engagement. Engineers care about latency and throughput. Your spec should speak both languages.

Risk assessment, dependencies and mitigation

What could go wrong? What are you worried about? What do you need from other teams?

I've learned to be brutally honest in this section. Pretending risks don't exist doesn't make them go away. It just means you won't have a plan when they materialise.

List external dependencies. Do you need another team to ship something first? Do you rely on a third-party service that might have rate limits or downtime? Call it out. I've had projects delayed by weeks because of dependencies I didn't flag early enough.

Call out single points of failure in your design. Where can this break? What happens if that one critical service goes down? If you're introducing a new database, what happens if it fails?

Identify areas with high uncertainty. Are you using a technology for the first time? Is this an area of the codebase nobody really understands anymore? Are you making assumptions about user behavior that might be wrong?

For each major risk, propose a mitigation strategy or fallback plan. Don't just list risks — show how you'll handle them. Example:

  • Risk: Third-party API might have downtime
  • Mitigation: Implement circuit breaker with exponential backoff; cache responses for 5 minutes; have a degraded mode that works without the API

Be honest about what you don't know yet. This builds trust and invites help. "I'm not sure how we'll handle the migration of 500GB of existing data — looking for input here" is way better than pretending you have it all figured out.

Mention compliance, security, or data privacy concerns early. Don't wait until security review to discover you need a whole privacy impact assessment. If you're touching user data, call it out. If this needs SOC2 compliance, mention it.

Rollout strategy

How will this actually get deployed? Phased rollout? Feature flags? Canary deployments?

I never ship big changes to 100% of users on day one. Never.

If you only have two users, ship to just one.

Plan for gradual rollout. My usual progression: 5% → 10% → 50% → 100%. At each stage, you monitor metrics and watch for issues. If something breaks, you're only affecting a small subset of users.

Use feature flags to decouple deploy from release. Deploy the code to production but keep it behind a flag. This lets you control who sees the new feature independent of your deployment process. I've used LaunchDarkly, Statsig, and built homegrown solutions. They all work. Pick one and use it religiously.

Define rollback criteria. What conditions trigger a rollback? Be specific:

  • If error rate exceeds 0.5%, roll back
  • If p95 latency exceeds 1 second, roll back
  • If we get more than 10 customer complaints in an hour, roll back

Having these criteria defined in advance means you won't be making emotional decisions during an incident.

Consider dark launching to test in production without user impact. Send real traffic through the new code path but don't show users the results. Compare the output to the old code path (also called: shadow testing). This flushes out bugs before users see them.

Plan for data migrations separately from code deploys. Migrating millions of database records is risky. Do it separately from shipping new features. Ideally, migrate data first, then deploy code that uses the new schema, then clean up the old schema (expand-migrate-contract pattern).

Have a communication plan for internal stakeholders and customers. Who needs to know when this ships? Customer support team? Marketing? External partners? Write the comms plan into the spec.

Rollback strategy

What happens if you suddenly become unavailable when everything's on fire? Would your team know how to handle it?

Think of it like a preflight checklist for pilots. When stress is high and adrenaline is pumping, you don't want to be figuring things out. You want a list you can follow mechanically:

  1. Disable feature flag X
  2. Or: roll back to commit abc123 and deploy
  3. Run database script Y to revert schema changes
  4. Clear cache Z to remove stale data
  5. Monitor metrics A, B, C to confirm rollback successful
  6. Post in #incidents channel with status

This keeps stress to a minimum in incident scenarios.

Testing approach

Unit tests, integration tests, load testing, edge cases — how will you validate this actually works?

Define test coverage expectations. Critical paths need extensive tests. Nice-to-have features can have lighter coverage. Be explicit about what needs what level of testing.

Plan load/performance testing for high-traffic features. If this will serve millions of requests, you need to test it at scale. I use tools like k6 or Locust to simulate load. Don't wait until production to discover your database falls over at 10,000 QPS.

Consider chaos engineering for critical systems. Kill dependencies. Inject latency. Simulate network partitions. See what breaks. Netflix's Chaos Monkey is the canonical example, but you don't need anything that sophisticated. Just deliberately break things and see what happens.

Test edge cases and failure modes, not just happy paths. What happens when the database is slow? When the cache is empty? When a user sends malformed input? These are where bugs hide.

Plan for manual QA/exploratory testing where needed. Automated tests are great, but they only catch what you thought to test for. A human clicking around can find the weird stuff.

Include security testing for sensitive features. Pen testing, OWASP Top 10 checks, dependency scanning. If you're touching authentication, payments, or personal data, you need security review.

Timeline and milestones

Realistic estimates, buffer for unknowns, key delivery dates.

Here's an uncomfortable truth: your initial estimates will be wrong. They're always wrong. The question is how wrong.

Work backwards from hard deadlines if they exist. If there's a regulatory requirement or a conference demo, start there and work backwards to figure out if it's feasible.

Pad estimates for unknowns. If you're working in familiar territory with known tech, multiply estimates by 1.2x. If you're in unfamiliar territory or using new technology, multiply by 1.5x to 2x. I know it feels excessive, but I've never regretted padding estimates and I've always regretted being too optimistic.

Define clear milestones with demos or checkpoints. Every two weeks, you should be able to show something. It keeps momentum and surfaces problems early.

Call out external constraints. Holidays, conference season, compliance deadlines, marketing launches. If half your team is going to be out for a week, factor that in.

Be realistic about team capacity. Account for on-call rotations, other commitments, the fact that nobody is productive 8 hours a day. I usually assume 6 productive hours per engineer per day, and that's being generous.

Update estimates as you learn more. Initial estimates are educated guesses. As you get into implementation, you learn things. Update your timeline accordingly. A spec should be a living document, not a contract written in stone.

Open questions

What still needs to be figured out? What requires more investigation?

This section should shrink over time as you get answers, but it's crucial to have it. It shows intellectual honesty and invites collaboration.

List unknowns that need research or prototyping before committing. "We need to validate that approach X can handle our query volume" or "We should prototype the migration script on a test dataset first."

Call out decisions that need input from specific people or teams. "Need security team review on encryption approach" or "Need product input on what the fallback behavior should be."

Highlight areas where you need to validate assumptions with data. "We're assuming users will click this button, but we should A/B test to confirm" or "We think this cache hit rate will be 80%, but we should measure real traffic patterns first."

Be explicit about trade-offs you haven't resolved yet. "We could optimise for latency or throughput but not both — need to decide which matters more for this use case."

Assign owners to each open question with target resolution dates. Don't let open questions languish. Someone needs to be responsible for getting an answer, and there needs to be a deadline.

How culture plays a role

I've worked at companies where specs were treated as bureaucratic box-checking exercises, and I've worked at companies where they were genuinely valued as thinking tools. The difference is night and day.

Technical specifications should be based on a standard template. This reduces cognitive load and makes sure everyone's on the same page. You shouldn't have to reinvent the structure every time. We had a template in Notion. For Tandem, we use Google Docs templates, and at Docler, we had Confluence. The tool doesn't matter as much as the consistency.

Having a standard template also means reviewing specs is easier. You know where to look for the problem statement, the requirements, the risks. You're not hunting through a free-form essay trying to figure out basic information.

Make all proposals publicly available. Don't be afraid to share yours with a broader audience. I know it's scary putting your ideas out there, but transparency has massive benefits. Other teams might discover they're solving similar problems. Someone from a different vertical might have crucial context you're missing. Serendipity happens when information is public.

All specs were in a shared Notion space that anyone in engineering could read. The number of times someone from a completely different squad dropped a comment that changed the whole approach was remarkable.

Schedule regular review sessions of critical proposals. This serves multiple purposes: it helps others learn, it encourages collaboration, it makes people aware of what's being built across the organisation. We had monthly "architecture review" sessions where people would present proposals in progress and get feedback from a cross-functional group. Some of the best ideas came from those sessions.

Tell people they're doing great. Praise great proposals publicly. Acknowledge all participants, even if they just left a single comment. This encourages a culture of collaboration. I've seen teams where people were afraid to comment on specs because they thought they'd be seen as obstructionists. That's poison. You want people to feel like their input is valued, even if it's critical feedback.

When someone writes a particularly good spec, I call it out in a public channel. "This spec from Alex is an excellent example of how to structure a complex proposal — check it out for reference." It takes five seconds and it makes people feel good about the work they're doing.

The spec is the work

Here's my final thought, and it's probably the most important thing in this entire article: writing the spec IS doing the work.

Too many engineers treat the spec as overhead before the "real work" of coding begins. That's backwards. The spec is where you do the hardest thinking. It's where you discover what you don't know. It's where you find the fatal flaws before they become production incidents.

The best specs I've written have been collaborative, iterative, and occasionally contentious. They've been marked up with dozens of comments. They've gone through multiple revisions. They've forced me to reconsider assumptions I didn't even know I was making.

And every single one of them made the implementation smoother, faster, and more successful than it would have been otherwise.

So the next time you're tempted to skip the spec and just start coding, resist. Open up that document. Start with the problem. Work through the details. Invite smart people to poke holes in your logic. Iterate.

Your future self — and your on-call rotation — will thank you.


My product proposal template is available on GitHub. It captures most of the ideas discussed here. If your organisation doesn't have a technical proposal template yet, use this as a starting point.

Read the whole story
alvinashcraft
5 hours ago
reply
Pennsylvania, USA
Share this story
Delete

953: Why v0 creator left Vercel to fix GitHub (GOAT Jared Palmer)

1 Share

Scott and Wes sit down with Jared Palmer of GitHub (formerly of Vercel) to unpack all the biggest announcements from GitHub Universe 2025. They dive into the future of developer workflows with agents, how GitHub is rethinking project interfaces, and where there’s still room to improve the dev experience.

Show Notes

  • 00:00 Welcome to Syntax!
  • 00:21 Who is Jared Palmer?
  • 01:19 The developer workflow with agents.
  • 03:33 Opening ongoing tasks in VS Code.
  • 06:08 The benefit of agnostic agents.
  • 07:04 GitHub’s biggest opportunities for improvement.
  • 09:38 What’s your interface of choice for a new project?

Hit us up on Socials!

Syntax: X Instagram Tiktok LinkedIn Threads

Wes: X Instagram Tiktok LinkedIn Threads

Scott: X Instagram Tiktok LinkedIn Threads

Randy: X Instagram YouTube Threads





Download audio: https://traffic.megaphone.fm/FSI4441540097.mp3
Read the whole story
alvinashcraft
5 hours ago
reply
Pennsylvania, USA
Share this story
Delete

When Silence Becomes Your Most Powerful Coaching Tool | Alidad Hamidi

1 Share

Alidad Hamidi: When Silence Becomes Your Most Powerful Coaching Tool

Read the full Show Notes and search through the world's largest audio library on Agile and Scrum directly on the Scrum Master Toolbox Podcast website: http://bit.ly/SMTP_ShowNotes.

 

"I purposefully designed a moment of silence. Staying in the anxiety of being silenced. Do not interrupt the team. Put the question there, let them come up with a solution. It is very hard. But very effective." - Alidad Hamidi

 

Alidad walked into what seemed like a straightforward iteration manager role—what some use, instead of Scrum Master. The organization was moving servers to the cloud, a transformation with massive implications. When leadership briefed him on the team's situation, they painted a clear picture of challenges ahead. Yet when Alidad asked the team directly about the transformation's impact, the response was uniform: "Nothing."

 

But Alidad knew better. After networking with other teams, he discovered the truth—this team maintained software generating over half a billion dollars in revenue, and the transformation would fundamentally change their work. When he asked again, silence filled the room. Not the comfortable silence of reflection, but the heavy silence of fear and mistrust. Most facilitators would have filled that void with words, reassurance, or suggestions. Alidad did something different—he waited. And waited. For what felt like an eternity, probably a full minute, he stood in that uncomfortable silence, about to leave the room.

 

Then something shifted. One team member picked up a pen. Then another joined in. Suddenly, the floodgates opened. Debates erupted, ideas flew, and the entire board filled with impacts and concerns. What made the difference? Before that pivotal moment, Alidad had invested in building relationships—taking the team to lunch, standing up for them when managers blamed them for support failures, showing through his actions that he genuinely cared. The team saw that he wasn't there to tell them how to do their jobs. They started to trust that this silence wasn't manipulation—it was genuine space for their voices. This moment taught Alidad a profound lesson about Open Systems Theory and Socio-Technical systems—sometimes the most powerful intervention is creating space and having the courage to hold it.

 

Self-reflection Question: When was the last time you designed a moment of silence for your team, and what held you back from making it longer?

 

[The Scrum Master Toolbox Podcast Recommends]

🔥In the ruthless world of fintech, success isn't just about innovation—it's about coaching!🔥

Angela thought she was just there to coach a team. But now, she's caught in the middle of a corporate espionage drama that could make or break the future of digital banking. Can she help the team regain their mojo and outwit their rivals, or will the competition crush their ambitions? As alliances shift and the pressure builds, one thing becomes clear: this isn't just about the product—it's about the people.

 

🚨 Will Angela's coaching be enough? Find out in Shift: From Product to People—the gripping story of high-stakes innovation and corporate intrigue.

 

Buy Now on Amazon

 

[The Scrum Master Toolbox Podcast Recommends]

 

About Alidad Hamidi

 

Alidad is a strategic advisor in human-centred transformation, focused on organisational design for autonomy, ownership, and impact. A recovering Agility Coach, he draws on years across delivery and coaching roles to help build organisations truly fit for humans—resilient, adaptive, and designed for people, not just processes.

 

You can link with Alidad Hamidi on LinkedIn. You can also visit his website at desirablefutures.group.





Download audio: https://traffic.libsyn.com/secure/scrummastertoolbox/20251110_Alidad_Hamidi_M.mp3?dest-id=246429
Read the whole story
alvinashcraft
5 hours ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories