Skip to content

What I wish I had known when I was starting out as a developer

As I get older, I wish I could reach back and give myself advice. Some of it is life advice (take that leap, kiss that girl, take that trip) but a lot of it is career advice.

There’s so much about being a software developer that you learn along the way. This includes technical knowledge like how to tune a database query and how to use a text editor. It also includes non technical knowledge, what some people term “soft skills”. I tend to think they are actually pretty hard, to be honest. Things like:

  • How to help your manager help you
  • Why writing code isn’t always the best solution
  • Mistakes and how to handle them
  • How to view other parts of the business

These skills took me a long time to learn (and I am still learning them to this day) but have had a material impact on my happiness as a developer.

I am so passionate about this topic that I’ve written over 150 blog posts. Where are these? They’re over at another site I’ve been updating for a few years.

And then I went and wrote a book. I learned a bunch of lessons during that process, including the fact that it’s an intense effort. I wrote it with three audiences in mind:

  • The new developer, with less than five years of experience. This book will help them level up.
  • The person considering software development. This book will give them a glimpse of what being a software developer is like.
  • The mentor. This book will serve as a discussion guide for many interesting aspects of development with a mentee.

The book arrives in August. I hope you’ll check it out.

Full details, including ordering information, over at the Letters To A New Developer website.

AI Can’t Care

I saw one post recently discussing how AI can’t substitute for judgment. There are many others if you search. And they all make an interesting argument.

But to me what feels more true is that AI can’t substitute for caring.

AI doesn’t care if the answer’s right or wrong. It doesn’t care if you were led down a rabbit hole only to find a dead end. It doesn’t care if a reader wasted their time or got a response that doesn’t actually address their question or need.

However, AI is good at giving feedback, checking details, and letting you create more quickly. AI should be used to help you draft but never publish. Use AI as a thought partner, a research assistant, someone who can help give you synonyms, reword a tough passage, review your work; but never take something AI creates and just publish it.

Why? AI can’t care.

But caring about your reader is the root of communication.

You can feel it when someone doesn’t care about their audience. We’ve all seen those LinkedIn posts full of emojis, or blog posts that have a smell that screams AI. That doesn’t mean the concepts explored are useless. It doesn’t indicate that the poster is incorrect. It doesn’t mean the post is not going to get engagement.

A post that AI creates may get seen or shared. But it also devalues the reader. When someone does this, it means they don’t give a damn. They don’t care enough to realize what they’re putting out there is not valuing their readers’ time.

Why would you pay attention to someone who doesn’t care about your time?

Everyone who reads something online, whether they scan it or read it deeply, is giving you the precious gift of their time.

Even oil we can make more of. But not time.

So use AI as a tool to help you move faster. But don’t forget to care about what you publish. That means carefully reviewing AI output to ensure correctness.

Otherwise you’re burning your reader’s trust.

Meetings are forcing functions

A recurring meeting serves as a powerful forcing function for long-running projects.

Many organizations face a common challenge: a complex project that requires effort and perspectives from multiple people, moves through definition and execution phases, and unfolds over weeks, months, or years. But one where the tasks to accomplish the project are not anyone’s full-time job.

Everyone has other obligations, fires to put out, and emails to answer. It’s easy for long-term strategic, high-impact work to sink to the bottom of everyone’s todo list.

One effective solution is to schedule a standing meeting. Whether in person or video, it doesn’t matter. The key to making progress is maintaining an agenda and, critically, opening each meeting by reviewing the to-dos from the previous one. This creates pressure on everyone to make progress. When people know they’ll be asked “what’s the status of X that we talked about last week?” at an upcoming meeting, it is easier, though not easy, to carve out time for that work amid the daily chaos.

This approach works across organizational boundaries too. If you’re a consulting firm, a regular cadence of meetings with your client is especially valuable. You’re  motivated to deliver., but people on the client’s team may be less so. A meeting where you consistently show progress while they haven’t made any creates gentle but real accountability.

If you’re managing a large, complex, multi-person effort, consider the standing meeting. As far as schedule, weekly, bi-weekly, or monthly all have worked for me in the past. Pick whatever fits the urgency.

Use a meeting as a forcing function to ensure people actually make time to move the project forward.

The Nepotism That Isn’t

This is something I’ve learned over the years, having watched it happen at company after company. I thought it was worth documenting for future me.

When I was a young programmer in the early 2000s, I was at a consulting company that took on investment money and brought in a new CEO and new executives. The original founder and other members of the old executive team were slowly pushed aside.

I remember watching the new executives bring in their own people, and I remember thinking: wow, that’s really crappy:

  • They don’t want to leverage what got us here.
  • They don’t value the current talent.
  • They don’t understand the company.

And frankly, I thought it was nepotism. New folks were hired into the company at a high level because of who they knew.

Boy, was I wrong.

The honest truth is that running a company is hard. Coming in to run a company is very hard. You’re typically brought in precisely because what worked before isn’t working anymore. (There’s the occasional case where someone is ready to leave for personal or professional reasons.)

I don’t think most VC firms or investors have any desire to have friends or former colleagues running their portfolio companies. They would much rather those companies be doing great and not requiring any additional time or thought.

But that doesn’t always happen, and sometimes a company needs some attention.

When you bring someone new in at the top, you often cast a reasonably wide net. An executive search for a new CMO, CRO, or especially a CEO starts with your network, but search teams also look in other places.

However, the executives who are brought in don’t always have the same scope when it comes to hiring the people right below them:

  • someone to lead demand generation
  • a new engineering manager
  • a VP of something

So what do they do?

Think about it: what would you do if you were under significant pressure, still getting up to speed, and needed to find someone you could trust to execute on a vision you were still figuring out yourself?

I know what I would do. I would tap my network. I would go find people I’d worked with before, people I trusted. There are people I can think of right now who, if I had the budget, I would hire without question. There aren’t many people I trust wholeheartedly, but they exist.

That is what I was watching play out back in the early 2000s, without understanding.

I was seeing executives come in under real pressure to deliver, still learning the environment, and doing what any human being would do: hiring people they knew, people they trusted.

Reflections from the February AI Builders Meetup

I just went to the latest AI Builders Meetup and it was really fascinating. It reminded me of the years of the Boulder-Denver New Tech Meetup, where there was so much energy in the room. There were people who flooded the meetup talking about all of the interesting things they were building.

This meetup was similar.

There was also the same kind of supporting infrastructure with companies willing to throw around money. For instance, at this meeting, there was plenty of beer and pizza. It was at Founder Central and there was plenty of space.

I have been part of other meetups where it’s very, very hard to find space or money for pizza. AI is such a hot topic right now that it is relatively easy to find space and sponsors, such as service providers or VC firms who want to pony up.

There were over 500 RSVPs. I don’t know how many people attended, but there were hundreds of people there.

As far as the presentations, they were demos and varied quite a bit.

Some were over my head. For instance, the folks that use the transformer architecture to predict and build audiences for drug trials. I understood the general concepts, but some of the technical details went beyond me. Still a really cool technical overview from Branchlab.

There was also a presentation about building personalized apps using Wabi.ai. I thought that was interesting but a little bit hard for me to understand why people would care about building their own apps. But I was never someone with a personalized ringtone or even stickers on my laptop, so I might not be the target market.

The next presentation was a demo from FreePlay, one of the sponsors. They were filling in for a talk that bailed; it wasn’t intentional. They covered how they built an agent to help people improve their prompts inside their eval system. They shared a Notion doc which had some learnings on building this agent, which were pretty useful. I think one of the key points that I took away was that cross-model optimization is really hard. Each of the models has their own wrinkles and idiosyncrasies, and it’s really hard to build cross-model applications or agents. You can work around some of the issues by aiming for lowest common denominator.

Another presentation was about Mai-Tai, an open source framework which lets you access coding agents from your phone. This was kind of a cool, interesting way to use a phone before Claude’s or OpenAI’s tools existed. It seemed really developer-focused; they’re looking for PRs.

The final project was really interesting to me. It was from one of the founders of Slider. It showed how you can automate constraints around agents and how that leads to better outcomes. In this case, they were doing that with design systems. They built a way to have an agent have ground rules and understanding around building applications with a certain design system, like a Workday application.

This was interesting to me from a number of perspectives. The first is that I’ve been thinking a lot about authorization as one of those guardrails and how that interacts. The second thing that struck me was when he talked about compilation checks versus runtime checks. Runtime checks are better-crafted prompts, and compilation checks are enforceable, deterministic guardrails like linters or authorization. He kind of didn’t want to give away all the secret sauce, so he didn’t dig into all of those deterministic checks. But to me, that seems like it’s where we’re headed. With this approach, you get the best of both worlds: the non-determinism to let an agent accomplish ill-defined tasks, and good guardrails to keep it from doing things it should not.

All in all, it was a great meetup.

Top Professional Accomplishments

Not to get maudlin, but it is fun to think about accomplishments over the years. The days are long, but the years are short, and I can’t believe I’ve been working in software development for more than two and a half decades.

My top professional accomplishments include:

  • Writing a book for a known publisher
  • Co-founding a startup that is still alive and kicking (5+ years after I left)
  • Running my own consulting business for about 8 years, and making a decent living from it
  • My time at FusionAuth; I’ve worn a bunch of different hats with real impact on the direction and culture of the company
  • Presenting at marquis conferences like Identiverse
  • Co-organizing the Boulder Ruby for about 5 years
  • Supporting the website of one of the biggest sporting events in the world
  • Keeping this tech blog running for over 20 years

MVP = embarrassing

This is a re-post of an article I wrote in 2019 for the Gocode Colorado blog which is no longer available. Thank you Wayback Machine!

“So, we need this feature to work, and it has to tie into this API, and we should put it all on the blockchain.”

“What about feature X? And we need admin screens, and roles and groups for different kinds of users.”

“You’re right! Let’s add those to the list. We need to make something we’re proud of.”

I heard some version of this conversation over and over again at my last Go Code Colorado mentoring session. And I sympathize with the sentiment, I really do.

But instead of hitting it out of the park the goal should be to create a piece of software that achieves the bare minimum, or a minimum viable product (MVP). With a high-risk venture team members should aim to show features to the end user as soon as possible, and to let their interactions guide the future of development.

It’s far too easy to get distracted by all the possibilities and build features that won’t be used. Even worse, developers may compare their application to other applications they use and find it wanting. The level of polish an MVP needs is far lower than a production application like Gmail, but because you use production ready apps every day, their UX and polish can feel like a requirement. Building features or adding UX polish can delay shipping an MVP. You want to wait until you are “ready”.

You are never “ready”, my friend.

If you’re not embarrassed by the first version of your product, you’ve launched too late.

– Reid Hoffman, LinkedIn Founder

Keep your focus not on the software but on the user.  Spend time talking to your target market and putting either mockups or working code in front of them as frequently as you can. Finding these people is another blog post entirely, but hopefully, you have some idea who they are and where they hang out.

It can be scary to put what you’ve built in front of people. It’s often much easier to sit back and build new features than it is to ship. I have felt it myself–as a startup co-founder, I built a web app that I was, frankly, embarrassed to show potential customers. It was missing features I considered crucial, was full of holes and bugs, and didn’t have a consistent user interface.

But showing it to potential customers early and often was the best choice. They pointed out missing features and also explained what was unnecessary. We got great feedback and I was better able to understand the types of problems the customer faced.

There are many ways you can show potential users what you are planning to build or are building without having a fully finished product.

  • You can show them mockups, either a paper draft, a series of powerpoint screens or a clickable prototype (Adobe XD or Balsam IQ are solutions). This is the cheapest way to get feedback because changing a screen in powerpoint is far easier than changing a screen in code.
  • Enroll potential customers in a beta program. Customers are more forgiving if they know this isn’t the final product, and they’ll give you suggestions. Don’t take each suggestion as truth, but do try to find out what problem the suggestion is aiming to solve–that’s gold. Offer people in your beta program a discount when you start charging–that gives them the incentive to give good feedback and can seed your customer base.
  • Build out mock features. Instead of building a full file upload facility, I have added a screen to an app with a link to “email us to add a file”, and fulfilled it manually. If enough people mailed a file, we’d know we needed to build the feature.
  • Have someone walk through the application in a video chat (using GoToMeeting or Zoom). Similar to a beta, people are more forgiving when they are shown something and you will be able to see where issues arise (“how do I do task A?”). This experience can be humbling and frustrating at the same time, like watching this.

When building an MVP, use tools you know. Whenever I’m working on a project, I balance technical risk and business risk. If you’re building a true MVP, the business risk is very high, because you don’t know if the market actually exists. Therefore, minimize the technical risk by building with what you know.

But wait, aren’t customers expecting a polished application?

Some may. Early adopters who are looking to have a problem solved often can look past the rough edges and see the potential. It also depends on the domain and the competition. For instance, if you are starting an Instagram competitor aimed at consumers, the quality bar will be pretty high. If you are building a scheduling tool for tattoo parlors and your main competition is a spreadsheet, a web application built with any modern framework will likely wow your potential customers. It’s also important to show your customers that the application is continuing to improve–that will make them more forgiving of the inevitable issues.

You’d be surprised by how forgiving people can be, especially if you are building something to help them do their job better. Remember, if you aren’t a little bit embarrassed when you show someone your application, you should have shown it to them sooner.

Additional resources:

What New Developers Need to Know About Working with AI

It’s been a few years since I wrote Letters to a New Developer, about what I wish I’d known when I was starting out. The industry has changed with the advent and acceleration of generative AI and the implications of these tools on coding and software creation.

So I wanted to write a quick update to give advice to developers who are entering this world with AI.

It’s important to understand what developers actually do.

They are not responsible for writing code. They are not responsible for liking technology.

Like other employees, developers are responsible for  taking their particular skill set and using it to solve problems that a business or organization needs solved. Whether that’s a one-person shop organizing their customers’ data or a large organization like Walmart, Amazon or the US military trying to improve their logistics, there are goals to achieve. For a developer building, maintaining and improving software is the main means to achieve those goals.

This does not change in the world of AI.

The role of a developer is still to understand technology, how it can be applied, where its limits are and to build it with the quality and appropriate flexibility for the business situation.

What do I mean by the last part? If you’re building a script to transfer data from one system to another one time, then a throwaway script that doesn’t have error checking, that doesn’t have proper variable names, that doesn’t have abstraction is appropriate. If, on the other hand, you’re creating foundational architectural components of a long-lived system, you need to think about all the things that make software more maintainable.

In either case as a developer your role is clear. It’s not to code the software. It’s to take the business requirements, understand the domain and build a solution that meets the business’s requirements for a given level of flexibility, complexity and completeness.

That job doesn’t change whether you’re using:

  •  machine code assembly
  • a non-memory managed language like C
  • a memory managed language like Java or
  • spec-driven development.

As a dev, your job is to understand the technical trade-offs, use the right tools and meet the business or organization’s needs.

Now as a new developer, how do you learn to leverage genAI in a way that is going to help your career rather than hurt it? It’s tough out there to get your job as a new dev and ignoring AI is going to make it even tougher.

It’s important that you learn how to use this tool and use it well. But AI as a tool is much more like Google search results than it is like a compiler error. A compiler error is deterministic and will give you the same message each time you compile the code.

The output of an LLM is not deterministic, just as when you search for guidance for building software on Stack Overflow or your team. With these sources of knowledge, you as a developer need to learn judgment. You need to learn when to trust genAI and when not to trust it.

Do this by starting small, asking for links, and checking the output of an AI against other sources. These include other AIs and published documentation. You’re building your sense of judgment and intuition about the system you are improving. Use it to augment your understanding, not replace it.

When an AI hallucinates, don’t throw the baby out with the bathwater and never touch genAI again. Instead learn to sniff out when an AI is generating garbage and when it is generating helpful code that will accelerate things.

A good course of action is to use AI to generate easily verifiable code where errors are low impact. An example is writing tests with AI, especially unit tests, especially in a statically typed language. It’s very easy to tell if the tests that are written are working or not. Don’t forget to instruct the AI to fully flesh out the tests, you don’t want any “return true” nonsense.

Another example is read-only queries. If you have an understanding of the data you can verify whether or not the SQL the LLM creates gives you the correct answer. Write multiple queries because they are so low effort, and use them to double check answers. If you were looking for a count of a particular set of customers, ask it for multiple different ways, including a count of one particular kind of customer and a count of all customers grouped by type. This lets you see if things match up.

The goal is not trusting blindly but instead using the tool to accelerate delivery of solutions to the problems the business wants you to solve. But you want to do so in a way that is going to give you confidence that the solutions you deliver are real.

By the way, the value of such intuition and judgement is high for all developers.

I think that it’s even more valuable for newer developers.

If you would like to purchase my book, “Letters To a New Developer” for more of this kind of insight, there’s a sale going on right now through the end of the year. You can use this link to buy my book for just $24. (This is a better deal than I get with my author’s discount.)

 

Thankful For Memory Managed Languages

I’m thankful my software career started when memory managed languages were first available and then dominant. Or at least dominant in the areas of software that I work in–web application development.

I learned BASIC, WordPerfect macros, and Logo before I went off to college. But my first real programming experience was with Pascal in a class taught by Mr. Underwood (who passed away in 2021). I learned for loops, print debugging and how to compile programs. Pascal supports pointers but I don’t recall doing any pointer manipulations–it was a 101 class after all. I took one more CS class where we were taught C++ but I dropped it.

But my real software education came in the WCTS; I was a student computer lab proctor. Between that and some summer internships, I learned Perl, web development and how to deal with cranky customers (aka students) when the printer didn’t work.

I also learned how to install Linux (Slackware, off of something like 16 3.5-inch disks) on a used computer with a 40MB hard drive, how to buy hardware off eBay, and not to run while (true) fork in a C program. That last one: not good.

I was also able to learn enough Java through a summer internship that I did an honors thesis in my senior year of college. I used Java RMI to build a parallelizable computation system. It did a heck of a job of calculating cosines.

My first job out of school was slinging perl, then Java, for web applications at a consultancy in Boulder. I learned a ton there, including how to grind (one week I billed 96 hours), why you shouldn’t use stored procedures for a web app, how to decompile a Java application with jad to work around a bug, and how to work on a team.

One throughline for all that was getting the work done as fast as possible. That meant using languages and frameworks that optimized for developer productivity rather than pure performance. Which meant using memory managed languages. Which are, as Joel Spolsky wrote, similar to an automatic transmission in terms of letting you just go.

I have only the faintest glimmer of the pain of writing software using a language that requires memory management. Sure, it pops up from time to time, usually when I am trying to figure out a compile error when building an Apache module or Ruby gem. I google for an incantation, blindly set environment variables or modify the makefile, and hope it compiles. But I don’t have to truly understand malloc or free.

I’m so thankful that I learned to program when I didn’t have to focus on the complexities of memory management.

It’s hard enough to manage the data model, understand language idiosyncrasies, make sure you account for edge cases, understand the domain and the requirements, and deliver a maintainable solution without having to worry about core dumps and buffer overflows.

Say Goodbye

In this time of increasing layoffs, there’s one thing you should do as a survivor. Okay, there’s many things you should do, but one thing in particular.

Say goodbye.

When you hear someone you know is let go, send them a message. If you have their email address, send them an email from your personal account. If you don’t, connect on LinkedIn or another social network.

The day or two after they are gone, send them a message like this:

“Hi <firstname>, sorry to hear you and <company> parted ways. I appreciated your efforts and wish you the best!”

Of course, tune that to how you interacted with them. If you only saw them briefly but they were always positive, something like this:

“Hi <firstname>, sorry to hear you and <company> parted ways. I appreciated your positive attitude. I wish you the best!”

Or, if you only knew them through one project, something like this:

“Hi <firstname>, sorry to hear you and <company> parted ways. It was great to work on <project> with you. I wish you the best!”

You should do this for a number of reasons.

It is a kind gesture to someone you know who is going through a really hard time. (I wrote more about that.) Being laid off is typically extremely difficult. When it happens, you are cut off from a major source of identity, companionship, and financial stability all at once. Extending a kindness to someone you know who is in that spot is just a good thing to do. It reaffirms both your and their humanity.

It also doesn’t take much time; it has a high impact to effort ratio.

There may be benefits down the road, such as them remembering you kindly and helping you out in the future. The industry is small–I’m now working with multiple people who I’ve worked with at different companies in the past.

But the main reason to do this is to be a good human being.

Now, the list of don’ts:

  • Don’t offer to help if you can’t or won’t. I only offer to help if I know the person well and feel like the resources and connections I have might help them.
  • Don’t trash your employer, nor respond if they do. If they start that, say “I’m sorry, I can imagine why you’d feel that way, but I can’t continue this conversation.”. Note I’ve never had someone do this.
  • Don’t feel like you have continue the conversation if they respond. You can if you want, but don’t feel obligated.
  • Don’t state you are going to keep in touch, unless you plan to.
  • Don’t say things that might cause you trouble like “wish we could have kept you” or “you were such a great performer, I don’t know why they laid you off”. You don’t know the full details and you don’t want to expose yourself or your company to any legal issues.
  • Finally, don’t do this if you are the manager who laid them off. There’s too much emotional baggage there. You were their manager and you couldn’t keep them on. They almost certainly don’t want to hear from you.

Be a good human being. When someone gets laid off, say goodbye.

Career Leverage as a Developer

I was recently on the “I’m a Software Engineer, What’s Next?” podcast. You can view the whole podcast episode and you can subscribe and learn more about the podcast as well. (You can see all my podcast appearances.)

We covered a lot of interesting ground, but one thing we talked about was undifferentiated but scary problems. When you find one of these in the business world, that makes for a good software company. FusionAuth is one example of this. There, we focus on authentication. Authentication is undifferentiated because:

  • most online apps need it
  • it’s not a competitive advantage for most applications
  • there are well known standards (OIDC, SAML, OAuth)

Authentication is scary and risky because:

  • it impacts conversion and user experience
  • the risk of user data being exposed impacts reputation and bottom line
  • there’s jargon
  • there’s security risk

Of course the deeper you get into any area, the less scary it seems, but for the average developer, I think authentication is imposing. There are other undifferentiated but scary areas of software development, including:

  • security
  • performance
  • legacy code upkeep
  • real time systems
  • distributed systems

But one insight that came out of the discussion is that this applies to your career as well. If you focus on undifferentiated scary problems, then you have a lucrative career ahead of you, because the problem is important to solve (because it is scary) and transfers between companies (because it is undifferentiated). If you focus on differentiated problems, such as a scary area of the code base that is unique to the project, you’ll be tied to a particular company. If you focus on safe problems, you can switch between companies but you’re unlikely to make a lot of money, because the problems you are working on won’t be that important.

For new developers, I wouldn’t recommend immediate specialization into a scary, undifferentiated problem. There’s an explore-versus-exploit continuum in careers, and early on, exploration is crucial. You have to find what you are interested in. But at some point, choosing an undifferentiated scary problem and solving it in a relatively unique way gives you significant career leverage. And leverage makes you valuable in the workplace.

It also helps you avoid being a cog. Every employer wants fungible employees, but every employee should resist being fungible. Don’t be “Java Engineer 2” or “React Developer with 3 years experience.” Be someone people want to work with. The goal is for people to say, “I want to work with [your name],” not “I want to work with any React developer.”

By tackling problems that are both scary (high-impact) and undifferentiated (universally applicable), you build expertise that travels with you while positioning yourself as someone who can handle what others avoid.