What I'd Tell My Younger Self About Building Software and Scaling Teams

If I could go back in time, what would I tell myself? Lessons from a career of building software and scaling teams, in four pillars: product, process, engineering, and people.

A desert highway seen through a car's rearview mirror
Photo by Mark Neal / Unsplash

I've spent a good part of my career writing code, consulting for dozens of companies, launching products, and managing engineering teams. Along the way I collected a set of lessons I wish someone had told me much earlier. The framing is simple: if I could go back in time, what would I tell myself?

Beware of dogmas

A dogma is "a principle laid down by an authority as incontrovertibly true". Someone more experienced says: I've been down that road, it doesn't work, do it this way. And there's real value in that. Rules and best practices are cognitive shortcuts. They exist because someone tested the paths before you, and if you had to think everything from scratch you'd never leave the starting line.

The problem is letting go. Your context might be different from the context where the rule was born, and best practices have a way of hardening into dogmas. So before applying any rule, including everything in this post, understand where it came from and check it against your situation.

For the record, my context: I co-founded a software consultancy that started with four partners and grew past 50 people, building web software for clients ranging from half a dozen devs to a hundred devs on the same project, plus launching our own SaaS product. I've also worked at a unicorn fintech, building financial AI agents and merchant acquiring (the rails that let businesses accept card payments), and these days I'm working on agentic commerce. That's the experience these opinions come from.

I organize the lessons into four pillars: product, process, engineering, and people. Whether you're a dev or managing the team, you should care about all four.

Product

Technology is a means, not an end. Nobody's paying us to write code for the advancement of science. There's a business attached, and it needs to make money. Obvious when spelled out, yet we keep making decisions that make perfect sense from an engineering point of view while being completely disconnected from the business.

What fixed this in my head was studying the startup lifecycle. David Skok's framework: first you search for product-market fit, then for a scalable business model, then you scale the business. Before that last phase, the company is trying to save money and extend its runway. After it, the game flips and the company invests aggressively. If you're not sure which phase you're in, the funding round the company just raised is a decent proxy: seed money funds the search for fit, a Series A funds the search for the scalable model, and Series B onward funds the scaling.

The three startup phases as overlapping boxes over a timeline of funding rounds, with what engineering looks like in each: MVPs and technical debt during Product-Market Fit, re-architecture and firefighting during the Search for a Scalable Business Model, specialization and hiring while Scaling the Business

Each phase means something different for engineering. Searching for fit, you're building MVPs and prototypes, writing low quality code you might throw away, and piling up technical debt. Once you find fit, you're re-architecting to pay for the previous phase, growing squads, and putting out fires you didn't have before because you didn't have customers before, all while thinking "how the hell did we get here?". When you're scaling, you specialize into components, re-engineer, hire like crazy, and try to "zero out" the debt (impossible, but that's the mindset) to consolidate the base.

Two lessons fall out of this. First: a successful MVP will have technical debt. I've watched many founders and CTOs be genuinely embarrassed by their codebase, like they're the only ones. Everyone has this. If your MVP has no technical debt, you probably over-invested in something you hadn't validated yet. Second: what got you here won't be enough to take you to the next level. Each phase demands new skills, and the company that doesn't notice the phase change gets stuck.

There's a special case that scrambles everyone's brain: the company that nailed product A starts building products B and C. Those new products are back in phase one, and their teams should be prototyping and throwing code away. But the culture, shaped by the product A experience, wants structure and quality everywhere. Adapt how you build to where each product is, not to where the company as a whole is.

And borrow from product management culture. That community, mostly engineers who moved toward the business side, is obsessed with outcomes: revenue, market share, churn. The agile community left that vacuum open, and I think it's the right thing to focus on. Less agile ritual, more results.

Process

Everything I learned from Lean and Kanban compresses into two words: eliminate waste.

Estimating in story points is waste. I ran a lot of planning poker as a developer and as a project manager, and here I go laying down a rule: you're spending two or three hours a week on an activity that produces almost no value. Exaggerated? A little, for didactic purposes. But estimation exists to give visibility, and there's a better way to get it.

Measure lead time: the days between starting a piece of work and it being done. Agree with your team on what "done" means, deployed or in the user's hands. Plot the histogram and it tells you more than any velocity chart. On one project we had a mean of 14.8 days, a mode of 8, and a p80 of 20 days.

Histogram of lead time in days, right-skewed with outliers, marking the mode at 8 days, the mean at 14.8, and the p80 at 20

Those three numbers explain a classic scene. Someone from the business asks "when will this be ready?" and the developer answers from the mode, the value that repeats the most in their memory: "about 8 days". The honest answer comes from the percentiles: "in the past, 80% of the time, we delivered in 20 days or less. Want 90% confidence? More like 35." Way more precise, and it's just your own history talking. You can go further and run Monte Carlo simulations on the backlog: "if we keep delivering like we have, there's an 85% chance these 100 items are done by mid July". Rich information, zero estimation sessions.

To make lead time behave, you need two disciplines. The first is controlling the size of what enters the flow: score each item on uncertainty and complexity, and if either is high, break it down or refine it before it starts.

A 3x3 matrix of uncertainty versus complexity: low-to-medium on both axes is the ideal size of a work item, too complex means break it down, too uncertain means refine it first

The second is limiting work in progress. When the limit stops you from starting another story, you go help QA, help the PO validate something, or push over the line that story that's been "just needs testing" for a week. Start finishing, stop starting. Subtle change, huge effect on the flow. (I keep failing at this in my own products, by the way. The cobbler's children have no shoes.)

None of this is new. Manufacturing and construction solved these problems decades ago with quality programs and statistical process control. We like to think software is special because it has variability and uncertainty. Ask a foundation engineer if digging ever goes exactly as planned. We're not that special, and other industries have decades of answers we refuse to look at.

Engineering

In tests, readability beats elegance. You know the clever test file: nested contexts, values defined by helpers three levels up, shared setup pulled in from another file. Each piece looks elegant in isolation, but reading a single test means scrolling around and opening other files to reconstruct what's actually going on. Tests are documentation under pressure. Write them for the reader.

Same goes for code in general. The code is for other people, or for you in the future. We've all run git blame to find out who wrote this crap, and found our own name.

If you don't practice TDD, don't say you do. TDD is a specific discipline: red, green, baby steps, minimum code to pass. It's a slog, and a lot of people claim it while doing something else. The big TDD debate DHH started in 2014 with "TDD is dead. Long live testing." landed, for me, in the right place: write automated tests, whether before or after matters little, but don't lie to yourself about which methodology you're following.

Write documentation comments. A well-documented class tells you what it's for, what the arguments mean, what comes back. "But the code already says that." The code says it to the machine. Documentation says it to a person, pre-digested. When people quote Uncle Bob saying comments are a smell, they mean the comment narrating logic in the middle of a method. Separate the two: skip the narration, keep the documentation. Open source projects with a strong docs culture do this as a matter of course. Your internal codebase deserves the same.

Be honest about continuous integration. Fowler is right: if your feature branches live longer than a day, you have continuous building, not continuous integration. And here's my heresy: that can be fine. A pull request is a communication tool, code plus context plus discussion, where syntax, architecture, scalability concerns, and project knowledge get shared, and where "my code" becomes "our code". Pushing straight to trunk gives you none of that. A former coworker of mine, Rodrigo Flores, named this style discrete integration: integrate when it's ready and reviewed. Do it if it serves you, just call it what it is and own the trade-off.

On architecture: study DDD, CQRS, event sourcing, and functional programming. I think they're converging on the same way of thinking about systems. CQRS splits reads from writes. Event sourcing stores the events instead of the final state (your bank doesn't want a row saying my balance is 10, it wants every event that led to 10). Put them together and you get event-oriented systems, where one part emits events and other parts react, with no coupling to a specific HTTP endpoint.

Which is why, if you do microservices, you should base them on events, not on HTTP calls between services. And ask whether you need microservices at all. Once things communicate through events, you can often decouple inside the monolith and skip the distributed systems tax entirely.

People

There are no technical problems. Technical problems we sit down, research, experiment, and solve. That's the job, and it's the easy part. The hard problems are about people and organizations. Even the "technical" ones: when a team says "we need microservices" and the reason is "too many people are touching the same codebase", that's a team organization problem wearing an architecture costume. It might still be a valid reason! Just decide consciously, knowing which problem you're actually solving.

The core of team culture, for me, is being non-individualistic. Not deciding alone, not building things because you personally felt like it. Debating and reaching solutions together.

Group decisions have their own failure modes though, and psychology has studied them for decades. Group polarization: discussion inside a team tends to push members toward more extreme versions of what they already believed. Groupthink, Irving Janis's term: the drive to keep the team cohesive overrides the realistic appraisal of alternatives, leading to hasty, irrational decisions that everyone feels great about.

Two lines diverging between before and after a discussion: a group slightly in favor becomes strongly in favor, a group slightly against becomes strongly against

Janis identified the conditions that produce it: a cohesive team, isolation, no impartial leadership, homogeneous backgrounds. Now compare that with what we prescribe in agile: build a tight team, isolate it, let it self-organize, check back in two weeks. We're speedrunning the preconditions for groupthink. And notice the homogeneity item: diversity in your team is a groupthink defense, straight from the research.

The prevention playbook exists too: force a real evaluation of all the alternatives, split into independent groups and compare answers, bring outside people into the discussion (I did this a lot when building my own product, precisely to escape my own bias), and rotate someone into the devil's advocate seat.

The meta-lesson: this is organizational behavior, and HR and management researchers have been studying it for decades. Tech loves staring at its own navel. If your company has good HR people, walk over and say "I'm trying to do this and it's not working, help me". Same as with manufacturing: the answers are often next door.

Closing

These four pillars aren't the CTO's job. Junior or senior, dev or manager, the teams that work well are the ones where everybody keeps an eye on product, process, engineering, and people.

There's a Peter Ustinov quote I keep coming back to: "Beliefs are what divide people. Doubt unites them." Great line to drop in a meeting, by the way, if you want everyone to stare at you.

The specific technologies in these lessons will age the way technologies always do. The lessons about phases, waste, honesty, and groups age much better. Hold your beliefs loosely, question the best practices you inherited, and pay attention to context.


George Guimarães builds agentic commerce infrastructure at New Generation. Previously: Principal Engineer at a unicorn fintech, co-founder of Plataformatec (acqui-hired by Nubank).


Great! You’ve successfully signed up.

Welcome back! You've successfully signed in.

You've successfully subscribed to George Guimarães..

Success! Check your email for magic link to sign-in.

Success! Your billing info has been updated.

Your billing was not updated.