<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Michael Masterson</title>
    <description>The latest articles on DEV Community by Michael Masterson (@mgmaster24).</description>
    <link>https://dev.to/mgmaster24</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1300750%2F2d591879-6b24-4dc2-a78d-61aa946859d7.jpeg</url>
      <title>DEV Community: Michael Masterson</title>
      <link>https://dev.to/mgmaster24</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mgmaster24"/>
    <language>en</language>
    <item>
      <title>What a Fractional Engineering Leader Actually Does</title>
      <dc:creator>Michael Masterson</dc:creator>
      <pubDate>Thu, 23 Apr 2026 00:34:42 +0000</pubDate>
      <link>https://dev.to/mgmaster24/what-a-fractional-engineering-leader-actually-does-3jnp</link>
      <guid>https://dev.to/mgmaster24/what-a-fractional-engineering-leader-actually-does-3jnp</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fatc4nu660lt3zwwjeiiv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fatc4nu660lt3zwwjeiiv.png" alt="m2s2 eng leadership" width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most founders who reach out to a fractional engineering leader have the same question underneath whatever they actually ask: &lt;em&gt;is this a real thing, and is it for me?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It’s a fair question. The term gets used loosely — sometimes it means a part-time hire, sometimes a consultant, sometimes an advisor who shows up to a monthly call. The ambiguity makes it hard to evaluate. So let me be direct about what it actually means in practice.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it is
&lt;/h3&gt;

&lt;p&gt;A fractional engineering leader is a senior technical partner who embeds with your team on a part-time basis — typically a set number of days per week — and takes on real ownership of the engineering function. Not advisory. Not oversight. Actual responsibility for the decisions, direction, and execution quality of what your team builds.&lt;/p&gt;

&lt;p&gt;That means setting technical standards, making architecture calls, running the hiring process, managing engineers, communicating with stakeholders, and being accountable for delivery. The same things a full-time CTO or VP of Engineering would own — delivered at a scope and cost that matches where you actually are.&lt;/p&gt;

&lt;h3&gt;
  
  
  Who it’s for
&lt;/h3&gt;

&lt;p&gt;The companies that get the most out of this model tend to look like one of these:&lt;/p&gt;

&lt;h3&gt;
  
  
  The technical founder who’s outgrown the role.
&lt;/h3&gt;

&lt;p&gt;You built the first version. You’re still the most senior engineer on the team. But you’re also running product, talking to customers, and trying to close deals. Engineering decisions are getting deferred, standards are slipping, and you know it. You need someone to own the technical function so you can focus on the business.&lt;/p&gt;

&lt;h3&gt;
  
  
  The non-technical founder with engineers.
&lt;/h3&gt;

&lt;p&gt;You have a team building something. You can evaluate business outcomes but not the quality of the decisions underneath them. You need a trusted technical partner who can tell you what’s actually going on, make the calls you can’t, and be honest with you when something is heading in the wrong direction.&lt;/p&gt;

&lt;h3&gt;
  
  
  The growing team that needs structure.
&lt;/h3&gt;

&lt;p&gt;You’ve scaled past the point where everything fits in one person’s head, but you’re not ready — or don’t need — a full-time executive. You need patterns, standards, and someone who’s built at this stage before to set the foundation before it becomes expensive to fix.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it actually looks like
&lt;/h3&gt;

&lt;p&gt;The specifics vary by engagement, but the shape is usually consistent: a defined number of days per week, clear ownership of the engineering function, and regular communication with whoever is running the business.&lt;/p&gt;

&lt;p&gt;In practice that means being present in the work — in code reviews, architecture discussions, hiring conversations, and planning sessions — not just showing up to give opinions. It means knowing the codebase, knowing the team, and being accountable for the output. An engagement that stays at the advisory layer isn’t really fractional leadership. It’s expensive consulting.&lt;/p&gt;

&lt;p&gt;The best engagements feel like a founding team member who happens to work a structured schedule. The worst ones have unclear ownership and a leader who isn’t close enough to the work to make good decisions. The difference is usually defined scope and real accountability from the start.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it isn’t
&lt;/h3&gt;

&lt;p&gt;It isn’t a shortcut. A fractional leader can move fast because they’ve seen these problems before, but they still need time to understand your context, your team, and your constraints. Expect a ramp period. Expect to invest in the relationship.&lt;/p&gt;

&lt;p&gt;It also isn’t a replacement for eventually building a full-time engineering leadership function if that’s where you’re headed. The best fractional engagements either solve a specific problem and conclude cleanly, or they set the foundation well enough that bringing on a full-time leader becomes a natural next step — with a codebase, team, and standards in good shape when they arrive.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to know if it’s the right call
&lt;/h3&gt;

&lt;p&gt;A few honest signals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You’re making technical decisions you’re not confident in, and you know it.&lt;/li&gt;
&lt;li&gt;Engineering is moving slower than it should and you can’t pinpoint why. Things are moving fast — maybe too fast — and there’s no senior voice in the room to slow down, ask the hard questions, and make sure the foundation holds up under the pace.&lt;/li&gt;
&lt;li&gt;Your team is growing and nobody is setting the bar for how things get built.&lt;/li&gt;
&lt;li&gt;You’ve interviewed for a full-time CTO and haven’t found the right person, or the role doesn’t justify a full-time salary yet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Any of those is a reasonable starting point for a conversation.&lt;/p&gt;

&lt;p&gt;At M²S² Engineering Group, fractional engineering leadership is one of the core ways we engage — embedded, accountable, and focused on the work that actually moves things forward. If any of this sounds like where you are, &lt;a href="https://m2s2.io/contact" rel="noopener noreferrer"&gt;let’s talk&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>engineeringleadershi</category>
      <category>softwareengineering</category>
      <category>leadership</category>
      <category>fractionalexecutive</category>
    </item>
    <item>
      <title>My Terminal-Native Dev Setup: WezTerm and Neovim</title>
      <dc:creator>Michael Masterson</dc:creator>
      <pubDate>Wed, 22 Apr 2026 13:48:45 +0000</pubDate>
      <link>https://dev.to/mgmaster24/my-terminal-native-dev-setup-wezterm-and-neovim-21h</link>
      <guid>https://dev.to/mgmaster24/my-terminal-native-dev-setup-wezterm-and-neovim-21h</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuhgj13ejzwm8cowksggw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuhgj13ejzwm8cowksggw.png" alt="wezneo" width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;April 20, 2026&lt;/p&gt;

&lt;p&gt;Most developers reach for VS Code by default. It’s a reasonable choice — good ecosystem, low friction, gets out of your way. I used it for years. At some point I started paying attention to how much time I spent with my hands off the keyboard, reaching for the mouse to navigate, click through file trees, manage tabs. It adds up.&lt;/p&gt;

&lt;p&gt;I’ve been running WezTerm and Neovim as my primary development environment for a while now. I work across Go, TypeScript, and Angular — a reasonably demanding setup that requires solid LSP support, fast navigation, and a terminal that doesn’t slow me down. Here’s what I’ve learned.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why WezTerm over everything else
&lt;/h3&gt;

&lt;p&gt;The terminal landscape is crowded. iTerm2, Alacritty, Kitty, Ghostty — all solid. I landed on WezTerm for a few specific reasons.&lt;/p&gt;

&lt;p&gt;It’s configured in Lua. That sounds minor but it matters: the config file is a real programming language, not an INI file with magic strings. You can write functions, conditionals, and abstractions. Here’s a minimal starting point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;wezterm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'wezterm'&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wezterm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config_builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;font&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wezterm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'FiraCode Nerd Font'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Regular'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;font_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color_scheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Tokyo Night'&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enable_tab_bar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hide_tab_bar_if_only_one_tab&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;window_padding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bottom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;WezTerm also has a built-in multiplexer, and I use it heavily. Tab management is handled entirely through keyboard shortcuts — opening, closing, and switching tabs without touching the mouse. Each tab is a context: a project, a running process, a scratch environment. When I’m inside Neovim, I hand off navigation entirely to its internal buffer management and fuzzy search tooling. The two layers stay clean and don’t fight each other.&lt;/p&gt;

&lt;p&gt;GPU rendering means it stays fast even with large outputs. And it works identically on macOS and Linux, which matters when you’re working across machines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Neovim over VS Code
&lt;/h3&gt;

&lt;p&gt;This is the more loaded question, and I want to be honest about it: Neovim is not better than VS Code in every way. The plugin ecosystem is more fragmented, setup takes real time, and new contributors to a project can’t just open your config and understand it immediately.&lt;/p&gt;

&lt;p&gt;What Neovim does better: speed, keyboard-centricity, and composability. Once you internalize modal editing — the idea that you’re either navigating or editing, not both simultaneously — the efficiency difference is real. Motions like ciw (change inner word), va{ (select around braces), or % (jump to matching bracket) stop being things you remember and start being things you reach for without thinking.&lt;/p&gt;

&lt;p&gt;The LSP support in modern Neovim is excellent. With mason.nvim handling server installation and nvim-lspconfig wiring them up, you get the same language intelligence VS Code has — autocompletion, go-to-definition, inline diagnostics, rename across files — without a separate application running.&lt;/p&gt;

&lt;h3&gt;
  
  
  The plugin stack that actually matters
&lt;/h3&gt;

&lt;p&gt;There are a thousand Neovim plugin lists. Most include too much. Here’s what I actually use daily:&lt;/p&gt;

&lt;p&gt;lazy.nvim — plugin manager. Lazy-loads plugins so startup stays fast.&lt;/p&gt;

&lt;p&gt;telescope.nvim — fuzzy finder for files, grep, LSP symbols, git history. This is the single highest-leverage plugin in the ecosystem. ff to find a file and fg to grep across the project replace most file tree navigation.&lt;/p&gt;

&lt;p&gt;nvim-treesitter — syntax highlighting and code understanding via AST parsing rather than regex. Makes highlighting accurate and enables smart text objects.&lt;/p&gt;

&lt;p&gt;nvim-cmp — completion engine. Pulls from LSP, snippets, buffer words. Feels like IDE completion once configured.&lt;/p&gt;

&lt;p&gt;gitsigns.nvim — inline git blame, hunk staging, diff previews in the gutter. The kind of thing you don't know you need until you have it.&lt;/p&gt;

&lt;p&gt;conform.nvim — formatting on save, language-aware. Runs gofmt on Go files, prettier on TypeScript, without you thinking about it.&lt;/p&gt;

&lt;p&gt;A minimal plugin setup with these six gets you 90% of the way to a full IDE experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  How WezTerm and Neovim work together
&lt;/h3&gt;

&lt;p&gt;The short version: WezTerm is the container, Neovim is the environment. WezTerm handles font rendering, color, and tab management. Neovim handles editing, navigation, and LSP.&lt;/p&gt;

&lt;p&gt;I use WezTerm’s tab system for context switching — one tab per project or major task — and Neovim’s buffer and split system for everything within a project. The combination keeps me in the keyboard almost entirely. A typical navigation sequence: ff to jump to a file, gd to go to a definition,  to jump back, fg to grep for a usage. No mouse involved.&lt;/p&gt;

&lt;p&gt;Terminal commands stay in a dedicated WezTerm tab rather than Neovim’s built-in terminal, which keeps the separation clean.&lt;/p&gt;

&lt;h3&gt;
  
  
  The learning curve is real, and that’s fine
&lt;/h3&gt;

&lt;p&gt;I’m not going to undersell this. Getting productive in Neovim takes weeks, not hours. Vim motions have to become muscle memory, and that only happens through deliberate repetition — not reading about them.&lt;/p&gt;

&lt;p&gt;The path that worked for me: I started with VS Code in Vim mode. Not Neovim, not a terminal editor — just the Vim keybindings extension in an environment I already knew. That removed the “where is everything” friction and let me focus entirely on building motion muscle memory. I also spent time with &lt;a href="https://vim-adventures.com/" rel="noopener noreferrer"&gt;Vim Adventures&lt;/a&gt;, a browser game that teaches Vim navigation through actual gameplay. It sounds gimmicky. It works.&lt;/p&gt;

&lt;p&gt;By the time I moved to Neovim full-time, the motions were already there. The transition became about configuration and workflow, not simultaneously learning a new editor and a new input model.&lt;/p&gt;

&lt;p&gt;The other thing that helped: don’t try to replicate your VS Code setup in Neovim. Start with less than you think you need. Add things when you feel a specific friction, not because a blog post said you should. The instinct to add plugins until it feels familiar is how you end up with a config you don’t understand.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it worth it?
&lt;/h3&gt;

&lt;p&gt;For me, yes — clearly. The speed, the composability, the satisfaction of a workflow that fits exactly how I think. But the honest answer is: it depends on what you optimize for. If you want to minimize setup time and maximize immediate productivity, VS Code wins. If you want an environment you can tune to exactly how your brain works and that will feel faster every month you use it, Neovim is worth the investment.&lt;/p&gt;

&lt;p&gt;At M²S² Engineering Group, the tools are always in service of the work — not the other way around. If you’re thinking through your team’s developer experience or how tooling choices affect velocity, &lt;a href="http://localhost:4200/contact" rel="noopener noreferrer"&gt;let’s talk&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>vim</category>
      <category>wezterm</category>
      <category>tooling</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Building a Serverless REST API with Go and AWS Lambda</title>
      <dc:creator>Michael Masterson</dc:creator>
      <pubDate>Tue, 21 Apr 2026 17:20:23 +0000</pubDate>
      <link>https://dev.to/mgmaster24/building-a-serverless-rest-api-with-go-and-aws-lambda-30ce</link>
      <guid>https://dev.to/mgmaster24/building-a-serverless-rest-api-with-go-and-aws-lambda-30ce</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F42nazjgboohm6x7jxcw6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F42nazjgboohm6x7jxcw6.png" alt="go-serverless" width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;November 11, 2025&lt;/p&gt;

&lt;p&gt;I’ve built Go Lambda APIs a few different ways over the years. Some of those experiments are in production right now — including the API that powers this site. What follows is the pattern I’ve landed on: clean, minimal, and straightforward to operate.&lt;/p&gt;

&lt;p&gt;The short version: Go is one of the best Lambda runtimes available. Fast cold starts, a single static binary, a strong standard library, and a compiler that catches whole categories of runtime errors before they reach production. If you’re starting a new serverless API and aren’t already committed to another language, this is the stack I’d reach for.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Go specifically
&lt;/h3&gt;

&lt;p&gt;Lambda cold starts are real. A Go function compiled to a Linux arm64 binary typically initializes in under 100ms — often under 50ms. Compare that to Node.js or Python runtimes that need to load an interpreter and dependencies before your first line of code runs. For a public-facing API, that difference matters.&lt;/p&gt;

&lt;p&gt;The other thing Go gives you is a single self-contained binary. No node_modules, no virtual environments, no dependency resolution at deploy time. You compile locally, upload a zip file, and the Lambda runtime executes it directly. That simplicity pays dividends when you're debugging at 2am.&lt;/p&gt;

&lt;h3&gt;
  
  
  Project structure
&lt;/h3&gt;

&lt;p&gt;Here’s how I structure a Go Lambda project in a monorepo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apps/api/
├── contact/
│ └── main.go # POST /contact handler
├── dashboard/
│ └── main.go # Auth-gated routes
└── shared/
    ├── models.go # DynamoDB item structs
    ├── dynamo.go # Query/put helpers
    ├── mailer.go # SES wrapper
    └── util.go # ID generation, CORS headers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each Lambda function gets its own directory with a main.go. Common code lives in shared/ as an internal package. This avoids the mistake of deploying a massive monolith Lambda that does everything — instead, each function has a focused responsibility and independent deploy surface.&lt;/p&gt;

&lt;p&gt;The shared package is where you put things that would otherwise be copy-pasted: DynamoDB helpers, the SES wrapper, CORS headers, ID generation. Keep it lean. If something is only used in one function, it stays in that function's package.&lt;/p&gt;

&lt;h3&gt;
  
  
  The handler pattern
&lt;/h3&gt;

&lt;p&gt;Every Lambda handler has the same shape: receive an events.APIGatewayV2HTTPRequest, return an events.APIGatewayV2HTTPResponse. I put all routing inside a single handler function using a switch on method + path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;APIGatewayV2HTTPRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;APIGatewayV2HTTPResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CORSHeaders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allowedOrigin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"GET, POST, OPTIONS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Content-Type, Authorization"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"OPTIONS"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;APIGatewayV2HTTPResponse&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;204&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"POST"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"/contact"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;handleContact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"GET"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"/health"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;APIGatewayV2HTTPResponse&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;`{"ok":true}`&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;errResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"not found"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is simpler than it looks. You don’t need a router library for a Lambda that handles 3–5 routes. The switch is readable, the cases are explicit, and there’s no magic. If a function grows beyond ~10 routes, that’s a signal it should be split.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reading the JWT for auth-gated routes
&lt;/h3&gt;

&lt;p&gt;API Gateway v2 with a Cognito JWT authorizer makes auth simple. The claims are injected into the request context — no token validation code needed in your Lambda:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;jwtEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;APIGatewayV2HTTPRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Authorizer&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Authorizer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JWT&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Claims&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;isAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;APIGatewayV2HTTPRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Authorizer&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;groups&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Authorizer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JWT&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Claims&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"cognito:groups"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"admin"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Cognito authorizer rejects requests with invalid or expired tokens before they reach your code. Your handler only runs for requests that passed auth — so a missing or empty email is a programming error, not a security gap.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building and deploying with CDK
&lt;/h3&gt;

&lt;p&gt;The build step is straightforward. For each Lambda, you cross-compile to Linux arm64 and zip the binary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;GOOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;linux &lt;span class="nv"&gt;GOARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;arm64 go build &lt;span class="nt"&gt;-o&lt;/span&gt; bootstrap ./apps/api/contact/
zip contact.zip bootstrap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CDK stack in Go defines the function pointing at that zip:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;contactFn&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;awslambda&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jsii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ContactFn"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;awslambda&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FunctionProps&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Runtime&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;awslambda&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Runtime_PROVIDED_AL2023&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;jsii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"bootstrap"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Code&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;awslambda&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Code_FromAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jsii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"../../apps/api/contact.zip"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"TABLE_NAME"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;jsii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TableName&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
        &lt;span class="s"&gt;"FROM_EMAIL"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;jsii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fromEmail&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s"&gt;"ALLOWED_ORIGIN"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;jsii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allowedOrigin&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wire the function to API Gateway, add a Cognito JWT authorizer for protected routes, and deploy. The whole stack — Lambda, API Gateway, DynamoDB table, IAM roles — comes up in a single cdk deploy.&lt;/p&gt;

&lt;h3&gt;
  
  
  The gotchas
&lt;/h3&gt;

&lt;p&gt;A few things that aren’t obvious until you hit them:&lt;/p&gt;

&lt;p&gt;The binary must be named bootstrap. The provided.al2023 runtime looks for a file named exactly bootstrap in the zip. Name it anything else and you'll get a cryptic runtime error on first invocation.&lt;/p&gt;

&lt;p&gt;CORS preflight has to be handled explicitly. API Gateway v2 can handle CORS for you, or your Lambda can. Do one or the other — not both. If your Lambda returns CORS headers and the gateway also injects them, you’ll get duplicate headers and browsers will reject the response.&lt;/p&gt;

&lt;p&gt;Cold start is not your P99. Cold starts only happen when a new execution environment is provisioned. Under steady traffic, most requests hit warm instances. Don’t optimize for cold start at the cost of readability — it’s rarely the bottleneck you think it is.&lt;/p&gt;

&lt;p&gt;DynamoDB errors are usually IAM. If your Lambda can’t read or write to DynamoDB, check the execution role permissions before assuming your code is wrong. The AWS SDK swallows permission errors in ways that make them look like connection issues.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it worth it?
&lt;/h3&gt;

&lt;p&gt;For the right workload — yes, absolutely. A Go Lambda API with DynamoDB behind API Gateway has essentially zero operational overhead. No servers to patch, no auto-scaling groups to tune, no capacity planning for moderate traffic. You pay for what you use, and Go’s efficiency means you use very little.&lt;/p&gt;

&lt;p&gt;Where it breaks down: workloads that need persistent connections (WebSockets, long-running jobs), very high concurrency with unpredictable bursts (Lambda has account-level concurrency limits), or anything that genuinely benefits from a stateful server process. Know the constraints going in and it’s a powerful tool.&lt;/p&gt;

&lt;p&gt;At M²S² Engineering Group, we help teams navigate exactly these kinds of architecture decisions — whether that’s serverless, containers, or something in between. If you’re weighing your options or just want a second opinion, &lt;a href="https://m2s2.io/contact" rel="noopener noreferrer"&gt;let’s talk&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>go</category>
      <category>awslambda</category>
      <category>serverless</category>
      <category>aws</category>
    </item>
    <item>
      <title>Engineering Leadership: What Nobody Tells You Before You Take the Job</title>
      <dc:creator>Michael Masterson</dc:creator>
      <pubDate>Tue, 21 Apr 2026 12:23:29 +0000</pubDate>
      <link>https://dev.to/mgmaster24/engineering-leadership-what-nobody-tells-you-before-you-take-the-job-5f1b</link>
      <guid>https://dev.to/mgmaster24/engineering-leadership-what-nobody-tells-you-before-you-take-the-job-5f1b</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fke2zvltmknvdckhohrwr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fke2zvltmknvdckhohrwr.png" alt="el" width="800" height="534"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Engineering Leadership&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I didn’t plan to become an engineering manager. I was a senior engineer who cared deeply about how the team worked, and at some point that care turned into a title. What followed was a crash course in everything a technical background doesn’t prepare you for.&lt;/p&gt;

&lt;p&gt;I’ve led teams of many engineers (full-time, onshore, nearshore, and offshore) through greenfield builds, legacy migrations, platform rewrites, and ambiguous mandates with moving deadlines. These are the things I wish someone had told me before I started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your output is no longer code
&lt;/h2&gt;

&lt;p&gt;This sounds obvious. but it isn’t, really. Not until you truly feel it. Early in my first management role I kept finding myself drifting back to the IDE. It felt productive. It was familiar. It was also the wrong use of my time.&lt;/p&gt;

&lt;p&gt;As an IC, your output is what you ship. As a manager, your output is what your team ships. Those are different jobs. The sooner you internalize that, the sooner you start spending your time on the things that actually multiply the team’s output — clearing blockers, making decisions, setting direction, hiring well, and building the kind of environment where engineers can do their best work without constantly checking with you first.&lt;/p&gt;

&lt;p&gt;That last part — reducing your own necessity — is the hardest thing to learn and the most important.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ambiguity is the job, not an obstacle to it
&lt;/h2&gt;

&lt;p&gt;Engineers are trained to solve well-defined problems. Management is largely the practice of operating in conditions where the problem isn’t well-defined, the requirements will change, and someone above you wants a date.&lt;/p&gt;

&lt;p&gt;I’ve been handed teams mid-rebuild with mandates to modernize platforms that had been accumulating technical debt for years. Scope changed constantly. Stakeholder priorities shifted week to week. There was no clear finish line. The job was to keep the team moving productively despite that, not to wait for clarity that was never coming.&lt;/p&gt;

&lt;p&gt;The engineers who struggled most in that environment were the ones waiting for permission to move. The ones who thrived understood that forward motion with imperfect information beats paralysis while waiting for a perfect spec. Your job as a leader is to model that and to create enough psychological safety that your team feels the same way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trust is built in small moments, not big ones
&lt;/h2&gt;

&lt;p&gt;I’ve seen managers try to build team trust through all-hands talks, offsites, and team-building events. Those things aren’t bad, but they’re not where trust actually gets built.&lt;/p&gt;

&lt;p&gt;Trust is built when you fight for someone’s promotion and they find out. When you take the blame in a postmortem instead of pointing at the engineer who made the call. When you remember what someone said in a 1:1 three weeks ago and follow up on it without being asked. When you give direct feedback early instead of letting something fester into a performance conversation six months later.&lt;/p&gt;

&lt;p&gt;None of those are dramatic moments. They’re small, repeated actions over time. That’s what trust is made of.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical direction has to be set, not consensus-built
&lt;/h2&gt;

&lt;p&gt;There’s a version of technical leadership where every architecture decision goes through the whole team, everyone has a voice, and nothing gets decided until there’s buy-in. I understand the instinct. It backfires constantly.&lt;/p&gt;

&lt;p&gt;Teams move fast when they have clear guardrails — approved patterns, defined standards, explicit rules about what’s off-limits — and then autonomy inside those guardrails. What they can’t do is move fast when every decision requires a committee.&lt;/p&gt;

&lt;p&gt;I’ve defined frontend and backend standards that became the baseline for all platform development across teams. Engineers didn’t need to ask whether to use a certain pattern, the answer was already documented. That eliminated an enormous amount of friction. The team made more decisions independently, shipped more consistently, and had better code quality than any team I’d seen operate by pure committee.&lt;/p&gt;

&lt;p&gt;This doesn’t mean you don’t listen. It means you listen, decide, document, and then hold the line — revisiting when there’s real evidence the direction needs to change, not just because someone prefers a different approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hiring is the highest-leverage thing you do
&lt;/h2&gt;

&lt;p&gt;Everything else on this list (direction, trust, standards, culture) compounds if you have the right people and collapses if you don’t. One mis-hire in a key role can consume more of your time and team energy than any technical problem you’ll face.&lt;/p&gt;

&lt;p&gt;I’ve hired dozens of engineers across multiple organizations. The most effective change I made was introducing AI-assisted assessments that gave consistent, role-specific evaluations for every candidate. The result was a more defensible hiring bar, less prep time, and — critically — fewer late-stage surprises when someone’s actual ability didn’t match how they presented in an interview.&lt;/p&gt;

&lt;p&gt;What I look for beyond technical skill: how does someone operate when they don’t have enough information? Do they ask good questions or just wait? Can they disagree directly without being difficult? Have they shipped something that actually worked? Those signals matter more than algorithm fluency at a whiteboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managing up is a skill, not a concession
&lt;/h2&gt;

&lt;p&gt;Early on I thought managing up — keeping stakeholders informed, shaping expectations, making the team’s work legible to leadership — was a distraction from real work. I was wrong.&lt;/p&gt;

&lt;p&gt;Leadership above you is making decisions with incomplete information. If you’re not providing that information, someone else is — and their version of events may not reflect what’s actually happening. Proactively communicating status, surfacing risks before they’re crises, and framing technical trade-offs in business terms isn’t politics. It’s protecting your team from decisions made in a vacuum.&lt;/p&gt;

&lt;p&gt;The cleaner your communication upward, the more autonomy you earn downward. That’s the deal.&lt;/p&gt;

&lt;h2&gt;
  
  
  The thing that actually determines whether a team ships
&lt;/h2&gt;

&lt;p&gt;After years of this, I’ve come to believe that the single biggest predictor of team output isn’t process, tooling, or even talent. It’s psychological safety — the degree to which engineers feel safe raising problems, asking questions, and being wrong in front of each other.&lt;/p&gt;

&lt;p&gt;Teams with high safety surface bugs earlier. They escalate sooner. They try things that might not work. They tell you when a deadline isn’t realistic instead of staying quiet and missing it. Teams without safety hide problems until they’re catastrophic, defer hard conversations, and optimize for looking good over doing good work.&lt;/p&gt;

&lt;p&gt;You create it by being honest about what you don’t know. By treating mistakes as information rather than failures. By rewarding the person who raised a concern early over the person who stayed quiet and got lucky. It takes a long time to build and almost no time to destroy.&lt;/p&gt;

&lt;p&gt;At M²S² Engineering Group, engineering leadership is part of every engagement — not an afterthought. Whether you need an embedded partner or someone to help set direction, click the link and &lt;a href="http://m2s2.io/contact" rel="noopener noreferrer"&gt;let’s talk about what that looks like&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhs0oa9lxumo3xmreblww.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhs0oa9lxumo3xmreblww.png" width="800" height="534"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;M2S2 Engineering Group&lt;/em&gt;&lt;/p&gt;

</description>
      <category>managementandleaders</category>
      <category>leadership</category>
      <category>softwareengineering</category>
      <category>engineeringmanagemen</category>
    </item>
  </channel>
</rss>
