<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Kondaurov | Blog</title><description/><link>https://kondaurov.site/</link><language>en</language><item><title>Building a simpler AWS deploy tool</title><link>https://kondaurov.site/blog/simpler-aws-deploy-tool/</link><guid isPermaLink="true">https://kondaurov.site/blog/simpler-aws-deploy-tool/</guid><pubDate>Fri, 10 Apr 2026 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I’m a software engineer with over 12 years of experience across different languages, teams, and dozens of projects. I love what I do, and I’m drawn to solving real problems with clean tools.&lt;/p&gt;
&lt;p&gt;I’ve been building serverless applications on AWS for years. Across multiple jobs and projects, I’ve used &lt;strong&gt;Serverless Framework&lt;/strong&gt; and &lt;strong&gt;AWS CDK&lt;/strong&gt;. And there was always this friction — the gap between having an idea and getting it running in the cloud felt wider than it should be.&lt;/p&gt;
&lt;p&gt;This is the story of why I finally snapped and built my own deployment tool — one that skips &lt;strong&gt;CloudFormation&lt;/strong&gt; entirely and deploys AWS resources with &lt;strong&gt;direct API calls&lt;/strong&gt;.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;the-background&quot;&gt;The Background&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;My serverless journey started with &lt;strong&gt;Serverless Framework&lt;/strong&gt; — it was the go-to tool at the time, and it worked. Then &lt;strong&gt;AWS CDK&lt;/strong&gt; came along promising infrastructure as “real code,” and I jumped in. I spent a lot of time with &lt;strong&gt;CDK&lt;/strong&gt; commercially, building stacks, configuring resources, trying to get everything just right.&lt;/p&gt;
&lt;p&gt;I’ve also worked with other clouds — &lt;strong&gt;Google Cloud&lt;/strong&gt; and &lt;strong&gt;Firebase&lt;/strong&gt; in particular. And honestly, &lt;strong&gt;Firebase&lt;/strong&gt; left an impression on me. The way you could describe your functions in code, run a single CLI command, and have everything deployed — that felt like how things should work.&lt;/p&gt;
&lt;p&gt;That said, I always preferred AWS itself. &lt;strong&gt;Google Cloud&lt;/strong&gt; felt overcomplicated — containers everywhere, things weren’t obvious. &lt;strong&gt;AWS Lambda&lt;/strong&gt; was simpler: no container builds, functions spun up fast, and the whole model just made more sense to me. So I loved &lt;strong&gt;Firebase’s&lt;/strong&gt; developer experience, but I wanted it on AWS.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-pain&quot;&gt;The Pain&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;With &lt;strong&gt;AWS CDK&lt;/strong&gt;, I constantly found myself solving infrastructure puzzles instead of shipping features. How do I split stacks so that Lambda functions deploy faster, separate from the databases? How do I configure bundling — should I use tsup externally, or let CDK handle it? Every project started with the same yak-shaving.&lt;/p&gt;
&lt;p&gt;And then there was &lt;strong&gt;CloudFormation&lt;/strong&gt;. Every deployment meant waiting for &lt;strong&gt;CloudFormation&lt;/strong&gt; to diff, plan, and roll out changes — even for a one-line code fix. For large projects with hundreds of resources, maybe that’s an acceptable trade-off. But for serverless, where the whole point is agility, it felt like dragging an anchor.&lt;/p&gt;
&lt;p&gt;I wanted a &lt;em&gt;fast loop&lt;/em&gt;: change code, deploy, see results. Instead, I was spending my time debugging stack configurations and staring at &lt;code dir=&quot;auto&quot;&gt;UPDATE_IN_PROGRESS&lt;/code&gt; for minutes.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-inspiration&quot;&gt;The Inspiration&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;At some point I discovered something on my own: you can create AWS resources directly through the &lt;strong&gt;AWS SDK&lt;/strong&gt;. No &lt;strong&gt;CloudFormation&lt;/strong&gt; needed. And it’s &lt;em&gt;much&lt;/em&gt; faster — the end result is exactly the same, but you skip the entire provisioning engine. That was the moment it clicked: combine &lt;strong&gt;Firebase’s&lt;/strong&gt; code-as-config model with direct &lt;strong&gt;AWS SDK&lt;/strong&gt; calls, and you get the best of both worlds.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-i-built&quot;&gt;What I Built&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;What if you could just write your Lambda handler, declare its configuration right next to the code, and run a single command to deploy everything? &lt;strong&gt;No CloudFormation templates. No stack definitions. No bundler config. No IAM boilerplate.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The biggest challenge was clear from the start: I’d need to do what &lt;strong&gt;CloudFormation&lt;/strong&gt; does — compare current state with desired state and sync the difference — but without &lt;strong&gt;CloudFormation&lt;/strong&gt;, using direct &lt;strong&gt;AWS SDK&lt;/strong&gt; calls. That’s not a trivial problem.&lt;/p&gt;
&lt;p&gt;Three libraries gave me the confidence to try.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ts-morph&lt;/strong&gt; — a fantastic library for analyzing TypeScript AST. It’s essentially &lt;em&gt;metaprogramming&lt;/em&gt;: I could extract all the infrastructure information directly from handler code instead of keeping it in separate YAML or JSON files.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Effect-TS&lt;/strong&gt; — the project was going to be complex, orchestrating API calls, managing errors, handling concurrency. &lt;strong&gt;Effect&lt;/strong&gt; gave me a way to write that kind of code without the cognitive load spiraling out of control as the codebase grew.&lt;/p&gt;
&lt;p&gt;And a &lt;strong&gt;typed AWS SDK wrapper&lt;/strong&gt; I had built a couple of years ago — a generator that reads JSDoc from &lt;strong&gt;AWS SDK&lt;/strong&gt; source, extracts all possible error types, and produces &lt;strong&gt;Effect&lt;/strong&gt; wrappers where every error is known at the type level.&lt;/p&gt;
&lt;p&gt;With these tools in hand, I set out to build &lt;strong&gt;effortless-aws&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Finding the right API took time. &lt;strong&gt;Firebase&lt;/strong&gt; inspired the direction, but I didn’t love everything about it. I wanted something where every handler is a single function call that takes one options object — path, method, handler logic, dependencies, parameters, everything in one place. No curried functions, no builder chains. Just one object that’s easy to read and extend.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Effect-TS&lt;/strong&gt; inspired me here too — specifically its approach to context and built-in &lt;em&gt;dependency injection&lt;/em&gt;. In &lt;strong&gt;Effect&lt;/strong&gt;, you can verify at the type level that all dependencies are satisfied before running anything. I wanted the same for my handlers: you declare a &lt;code dir=&quot;auto&quot;&gt;setup&lt;/code&gt; factory at the Lambda level, and it’s injected into every request handler with full type safety. No runtime surprises where something is undefined because you forgot to wire it up.&lt;/p&gt;
&lt;p&gt;This matters because some handlers need references to other resources — a Lambda that writes to a DynamoDB table, for example. A single config object makes that natural: you just add a &lt;code dir=&quot;auto&quot;&gt;deps&lt;/code&gt; field. Everything — setup, deps, params — is &lt;em&gt;type-checked&lt;/em&gt; and injected automatically.&lt;/p&gt;
&lt;p&gt;Here’s what the simplest handler looks like:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { defineApi } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;effortless-aws&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export const &lt;/span&gt;&lt;span&gt;hello&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;defineApi&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{ basePath: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/hello&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; ({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;status: &lt;/span&gt;&lt;span&gt;200&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;body: { message: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Hello World!&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That’s your &lt;strong&gt;Lambda&lt;/strong&gt; function, its &lt;strong&gt;Function URL&lt;/strong&gt;, and its &lt;strong&gt;IAM&lt;/strong&gt; role — all in one file. Run &lt;code dir=&quot;auto&quot;&gt;eff deploy&lt;/code&gt; and it’s live.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;before-and-after&quot;&gt;Before and After&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;To appreciate the difference, here’s what it takes to create a simple HTTP endpoint with a DynamoDB table in CDK:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// CDK: stack definition (separate file)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;table&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dynamodb&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Table&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Orders&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;partitionKey: { name: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, type: &lt;/span&gt;&lt;span&gt;dynamodb&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;AttributeType&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;STRING&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;billingMode: &lt;/span&gt;&lt;span&gt;dynamodb&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;BillingMode&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;PAY_PER_REQUEST&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;fn&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;nodejs&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;NodejsFunction&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;GetOrders&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;entry: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;src/handlers/get-orders.ts&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;runtime: &lt;/span&gt;&lt;span&gt;lambda&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Runtime&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;NODEJS_20_X&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;environment: { TABLE_NAME: &lt;/span&gt;&lt;span&gt;table&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;tableName&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;bundling: { minify: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;, sourceMap: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;table&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;grantReadData&lt;/span&gt;&lt;span&gt;(fn);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;api&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;apigateway&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;HttpApi&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Api&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;api&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;addRoutes&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;path: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/orders&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;methods: [apigateway&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;HttpMethod&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;GET&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;integration: &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;HttpLambdaIntegration&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;GetOrdersIntegration&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, fn),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That’s just the infrastructure. You still need the handler file, the stack wiring, the app entry point, and &lt;code dir=&quot;auto&quot;&gt;cdk deploy&lt;/code&gt; with &lt;strong&gt;CloudFormation&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;With &lt;strong&gt;effortless-aws&lt;/strong&gt;, the same thing is:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// That&apos;s it. One file.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { defineApi, defineTable } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;effortless-aws&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export const &lt;/span&gt;&lt;span&gt;orders&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;defineTable&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;pk: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export const &lt;/span&gt;&lt;span&gt;api&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;defineApi&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;basePath: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/orders&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;deps&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; =&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;orders&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setup&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;deps&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; ({ orders: deps&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;orders&lt;/span&gt;&lt;span&gt; }))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;orders&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;items&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;orders&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;scan&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; { status: &lt;/span&gt;&lt;span&gt;200&lt;/span&gt;&lt;span&gt;, body: items };&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Run &lt;code dir=&quot;auto&quot;&gt;eff deploy&lt;/code&gt;. Done. The table, the function, the route, the IAM permissions — all created in &lt;strong&gt;seconds&lt;/strong&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-it-works&quot;&gt;How It Works&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Under the hood, &lt;code dir=&quot;auto&quot;&gt;eff deploy&lt;/code&gt; goes through four stages. Orchestrating them was the hardest part of the project — each stage feeds into the next, errors can happen anywhere, and resources depend on each other. &lt;strong&gt;Effect-TS&lt;/strong&gt; made this manageable: the whole pipeline is composable, each step is an &lt;strong&gt;Effect&lt;/strong&gt; you can reason about independently.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;1-scan&quot;&gt;1. Scan&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;ts-morph&lt;/strong&gt; reads your TypeScript source and extracts every &lt;code dir=&quot;auto&quot;&gt;defineApi&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;defineTable&lt;/code&gt; call — basePath, routes, deps, config, static files — straight from the AST. This was the part where I realized &lt;em&gt;metaprogramming&lt;/em&gt; in TypeScript is actually viable. Instead of maintaining separate YAML or JSON config files, &lt;strong&gt;the code itself is the source of truth&lt;/strong&gt;. &lt;strong&gt;ts-morph&lt;/strong&gt; made it surprisingly elegant.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;2-bundle&quot;&gt;2. Bundle&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;esbuild&lt;/strong&gt; compiles each handler into a single ESM file. I had experience with &lt;strong&gt;esbuild&lt;/strong&gt; before — it’s fast and does its job well. But I also wanted to solve the dependency problem: in a project with many Lambda functions, they usually share the same production dependencies. Bundling &lt;code dir=&quot;auto&quot;&gt;node_modules&lt;/code&gt; into every handler ZIP felt wasteful. So I put shared dependencies into a &lt;strong&gt;Lambda Layer&lt;/strong&gt; — one layer for the whole project. Each handler bundles into a clean single JS file, and the layer provides &lt;code dir=&quot;auto&quot;&gt;node_modules&lt;/code&gt; at runtime. This was its own challenge to get right, but it works reliably — at least with &lt;code dir=&quot;auto&quot;&gt;pnpm&lt;/code&gt; projects so far.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;3-diff&quot;&gt;3. Diff&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The tool checks what already exists in AWS and compares it with what your code declares. Only the differences get applied. This is essentially what &lt;strong&gt;CloudFormation&lt;/strong&gt; does — syncing desired state with current state — but without the provisioning engine overhead. Getting this right was the biggest conceptual challenge: figuring out the right granularity of comparison and making sure updates are idempotent.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;4-deploy&quot;&gt;4. Deploy&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Direct &lt;strong&gt;AWS SDK&lt;/strong&gt; calls create, update, or reconfigure resources — &lt;strong&gt;Lambda&lt;/strong&gt; functions, &lt;strong&gt;DynamoDB&lt;/strong&gt; tables, &lt;strong&gt;API Gateway&lt;/strong&gt; routes, &lt;strong&gt;IAM&lt;/strong&gt; policies. Because every AWS call goes through the typed &lt;strong&gt;Effect&lt;/strong&gt; wrappers, every possible error is known. If a function already exists — I log it and move on. If there’s an internal AWS error — I stop the deploy. No guesswork, no catch-all try/catch blocks.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;try-it-out&quot;&gt;Try It Out&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;effortless-aws&lt;/strong&gt; is currently in alpha. It works — I’m using it myself to deploy real projects: the documentation website, and a couple of personal projects that use Lambda functions, DynamoDB tables, streams, and triggers. Everything deploys and runs. But it’s still in active development: there may be rough edges, and I’m constantly testing and fixing things.&lt;/p&gt;
&lt;p&gt;The roadmap is full of features I want to add, and the project is evolving fast. I’ve put together a website with documentation that covers what’s available today — handlers for Lambda functions, DynamoDB tables, static websites, and more.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;effortless-aws&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/effect-ak/effortless&quot;&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://effortless-aws.website&quot;&gt;Website &amp;#x26; Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Tools I used to build this:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://effect.website&quot;&gt;Effect-TS&lt;/a&gt; — typed functional programming for TypeScript&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/dsherret/ts-morph&quot;&gt;ts-morph&lt;/a&gt; — TypeScript AST analysis and manipulation&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://esbuild.github.io&quot;&gt;esbuild&lt;/a&gt; — fast JavaScript/TypeScript bundler&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/aws/aws-sdk-js-v3&quot;&gt;AWS SDK for JavaScript&lt;/a&gt; — official AWS SDK v3&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/kondaurovDev/aws-sdk&quot;&gt;Typed AWS SDK wrapper&lt;/a&gt; — Effect wrappers with typed errors for AWS SDK&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I’m confident in the ideas behind this project and I’ll keep building it. If you’re a developer who wants to ship faster — to spend your time solving real problems instead of writing infrastructure config — give it a look. I’d love your feedback.&lt;/p&gt;</content:encoded><category>aws</category><category>open-source</category></item><item><title>Best use of LLMs isn&apos;t code</title><link>https://kondaurov.site/blog/best-use-of-llms/</link><guid isPermaLink="true">https://kondaurov.site/blog/best-use-of-llms/</guid><pubDate>Wed, 01 Apr 2026 20:18:36 GMT</pubDate><content:encoded>&lt;p&gt;The best use of LLMs has nothing to do with code.&lt;/p&gt;
&lt;p&gt;I’ll be honest — I don’t love the hype around LLMs. And yes, I know it sounds tedious to remind people that an LLM is &lt;strong&gt;not&lt;/strong&gt; intelligence. It’s a language model trained on a massive amount of data. But I think it matters to keep saying it.&lt;/p&gt;
&lt;p&gt;Here’s what actually changed for me.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;the-real-problem&quot;&gt;The real problem&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;I’ve always had more ideas than time. Side projects, libraries, architectural experiments — things I wanted to explore but couldn’t justify spending weekends on. They just sat there, accumulating.&lt;/p&gt;
&lt;p&gt;LLMs didn’t magically turn those ideas into products. They didn’t write production code for me. They didn’t “solve” anything.&lt;/p&gt;
&lt;p&gt;What they did: they gave me &lt;strong&gt;someone to think with&lt;/strong&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;brainstorming-at-2-am&quot;&gt;Brainstorming at 2 AM&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;I could take a half-formed idea and brainstorm it. Poke holes in it. Explore adjacent solutions. Ask “what if” without committing hours to a prototype.&lt;/p&gt;
&lt;p&gt;Some ideas I threw away — and that’s &lt;strong&gt;valuable too&lt;/strong&gt;. Some I shaped into concrete libraries and tools.&lt;/p&gt;
&lt;p&gt;I once read that ideas should be shared, not hoarded. That you grow them by discussing them openly. LLMs became that for me — a thinking partner available at 2 AM when the idea won’t let you sleep.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-caveat&quot;&gt;A caveat&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;LLMs are biased. Without clear instructions and constraints, they can confidently lead you in the wrong direction. You should never trust what comes back on the first iteration.&lt;/p&gt;
&lt;p&gt;But here’s the thing — that’s exactly why brainstorming with them works. You’re not asking for answers. You’re asking for &lt;strong&gt;angles&lt;/strong&gt;. The output isn’t the point. The thinking it triggers in &lt;strong&gt;your&lt;/strong&gt; head is.&lt;/p&gt;
&lt;p&gt;I used to think I understood system design, architecture, distributed systems. I worked with these things — but working with something and truly understanding it are not the same. Brainstorming with an LLM exposed the gaps I didn’t know I had. It broke my &lt;strong&gt;illusion of understanding&lt;/strong&gt; and replaced it with something real.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-most-people-miss&quot;&gt;What most people miss&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The real power isn’t vibe-coding. It’s not “look, it wrote a React app.” It’s &lt;strong&gt;iteration&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Building anything meaningful — a startup, a product, a career — is the result of &lt;strong&gt;thousands of iterations&lt;/strong&gt;. You think, you try, you fail, you learn, you refine.&lt;/p&gt;
&lt;p&gt;LLMs compress that loop. They let you iterate on the &lt;strong&gt;idea itself&lt;/strong&gt; before you write a single line of code — stress-test it, find angles you hadn’t considered, discover where it connects to something bigger.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-i-learned&quot;&gt;What I learned&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;That’s not artificial intelligence. That’s a &lt;strong&gt;damn good tool&lt;/strong&gt;.&lt;/p&gt;</content:encoded><category>llm</category></item><item><title>Hiring without feedback is broken</title><link>https://kondaurov.site/blog/hiring-without-feedback/</link><guid isPermaLink="true">https://kondaurov.site/blog/hiring-without-feedback/</guid><pubDate>Mon, 30 Mar 2026 20:31:09 GMT</pubDate><content:encoded>&lt;p&gt;I pass every screening call. I answer every technical question. Then I get rejected — and nobody tells me why.&lt;/p&gt;
&lt;p&gt;The pattern is always the same. Screening goes well — experience aligns, good conversation, we move forward. Technical round — databases, architecture, system design. Nothing that makes me pause. I answer everything.&lt;/p&gt;
&lt;p&gt;Then — a week of silence. And either a polite “we went with someone closer to our stack,” or nothing at all.&lt;/p&gt;
&lt;p&gt;You can’t debug a process that returns no error message.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;the-strange-part&quot;&gt;The strange part&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The technical questions rarely have anything to do with the actual position. Generic architecture topics. Surface-level database questions. Nothing that tests how I’d solve their specific problems or work with their stack.&lt;/p&gt;
&lt;p&gt;It feels like many interviewers don’t have a plan. No rubric, no structure — just a conversation with some technical vocabulary sprinkled in. And if the questions don’t meaningfully filter candidates by skill, then what are they actually filtering by?&lt;/p&gt;
&lt;p&gt;I think a lot of technical interviews are cultural fit assessments disguised as technical ones. The questions are easy enough that most experienced engineers will answer them. The real evaluation is happening somewhere else — tone, energy, how you communicate. But nobody says that out loud.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-it-matters&quot;&gt;Why it matters&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If that’s what you’re evaluating — own it. Call it a culture interview. Set expectations. Let candidates show up as themselves instead of preparing for a technical deep dive that never comes.&lt;/p&gt;
&lt;p&gt;Because right now, you’re rejecting people and they have no idea what they got wrong. Not because the feedback is missing — but because the criteria were never on the table.&lt;/p&gt;
&lt;p&gt;That’s not a hiring process. That’s a guessing game.&lt;/p&gt;</content:encoded><category>career</category></item><item><title>Automate your resume, don&apos;t write it</title><link>https://kondaurov.site/blog/automate-your-resume/</link><guid isPermaLink="true">https://kondaurov.site/blog/automate-your-resume/</guid><pubDate>Sat, 28 Mar 2026 21:12:26 GMT</pubDate><content:encoded>&lt;p&gt;You don’t need to write a resume.&lt;/p&gt;
&lt;p&gt;Every time I sat down to update mine, something felt off.&lt;/p&gt;
&lt;p&gt;Not “I don’t know what to write” off. More like — this process doesn’t make sense. I’m an engineer. I think in steps, I build systems, I debug things. But writing a resume? I was just rearranging bullet points, hoping the order would somehow matter.&lt;/p&gt;
&lt;p&gt;No clear path. No way to verify the result. Just gut feeling and formatting choices. And don’t even get me started on templates — Awesome CV or minimalist? One column or two? Every choice feels arbitrary, and none of them change what you actually have to say.&lt;/p&gt;
&lt;p&gt;It always bugged me. And it took me a while to figure out why.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;the-insight&quot;&gt;The insight&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A resume is a &lt;strong&gt;build artifact&lt;/strong&gt;. It’s compiled output — not &lt;strong&gt;source code&lt;/strong&gt;. I’d been editing the build this whole time. That’s like patching minified JavaScript instead of fixing the original TypeScript module. No wonder it felt off.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;so-i-wrote-the-source&quot;&gt;So I wrote the source&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;I created a &lt;strong&gt;source file&lt;/strong&gt; for my career — a markdown document where I wrote down everything. Every job: what I did, why I joined, why I left, what I’m proud of, what was hard. No formatting, no optimization. Just the facts.&lt;/p&gt;
&lt;p&gt;Funny thing — that’s exactly what recruiters dig for in screening calls. Your motivations, your transitions, the context behind the bullet points. Except now it exists as a document before the call even happens.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-markdown&quot;&gt;Why markdown&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Markdown is plain text — it’s readable by humans and by LLMs equally well. Once your career is in a structured markdown file, generating a resume becomes a pipeline: feed the source to an LLM, get a content plan tailored to the role, then render to PDF. The creative part is already done — the rest is &lt;strong&gt;mechanics&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Applying for a backend role? The LLM pulls your system design work, infrastructure experience, performance wins. Fullstack position? It shifts the weight toward product delivery and cross-stack ownership. Cover letter? Same source, different output format.&lt;/p&gt;
&lt;p&gt;One source file. Multiple builds. Each one tailored — without rewriting anything by hand.&lt;/p&gt;
&lt;p&gt;I committed mine to a git repo. Versioned. I never have to remember what I did 5 years ago — I just read the file.&lt;/p&gt;
&lt;p&gt;The resume stopped feeling weird once I stopped treating it as something I write. It’s something I &lt;strong&gt;generate&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;There are dozens of resume builders out there — paid and free. They solve the formatting problem. But none of them solve the real one: where do you store the context that never fits in a resume? The reasons you joined. The reasons you left. What you actually learned vs what the bullet point says.&lt;/p&gt;
&lt;p&gt;How do you keep that information alive?&lt;/p&gt;</content:encoded><category>resume</category></item><item><title>From 85% rejections to interviews</title><link>https://kondaurov.site/blog/from-rejections-to-interviews/</link><guid isPermaLink="true">https://kondaurov.site/blog/from-rejections-to-interviews/</guid><pubDate>Tue, 24 Mar 2026 23:14:19 GMT</pubDate><content:encoded>&lt;p&gt;I kept getting rejected — and I couldn’t figure out why.&lt;/p&gt;
&lt;p&gt;Job searching is hard. Remote job searching is harder by an order of magnitude.
I was sending out 30 applications in a couple of days. Getting 25 auto-rejections back. My experience was relevant, my skills were real.&lt;/p&gt;
&lt;p&gt;Then I realized: the problem wasn’t my resume. It was my process.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;what-was-actually-happening&quot;&gt;What was actually happening&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;I’d see a position that looked like a fit. Apply. Move on to the next one. No deep analysis, no second look. Just volume.&lt;/p&gt;
&lt;p&gt;But “looks like a fit” hides a lot of traps. Buried in a wall of keywords — hybrid required. On-site in a country I’m not in. “Fluent in X” where X is a technology I’d need months to ramp up on. Easy to miss when you’re skimming 20 listings a day.&lt;/p&gt;
&lt;p&gt;I wasn’t getting rejected because I was unqualified. I was getting rejected because I was careless.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-problem-with-job-listings&quot;&gt;The problem with job listings&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Let’s be honest — most job descriptions are unreadable. Companies dump massive walls of keywords, mix hard requirements with nice-to-haves, bury the actual deal-breakers somewhere in paragraph four. Reading 20 of these a day is draining, and the more you read, the less you actually see.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;so-i-built-a-system&quot;&gt;So I built a system&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;I’m building a platform for myself — think of it like a shopping cart, but for job vacancies.&lt;/p&gt;
&lt;p&gt;I browse jobs as usual, and when something catches my eye, I save it through a Chrome extension. The extension grabs the page content directly from the DOM — no copy-pasting, no reformatting. The raw listing goes into my personal database.&lt;/p&gt;
&lt;p&gt;From there, an LLM parses the vacancy — extracting hard skills, soft skills, location requirements, work format, deal-breakers. All the things a human eye skips over when scanning fast. It turns a wall of text into a structured breakdown I can actually reason about.&lt;/p&gt;
&lt;p&gt;Before I apply, I sit with the parsed result. Do I actually want this? Does it match what I’m looking for? Is there a hidden requirement that disqualifies me?&lt;/p&gt;
&lt;p&gt;It’s a simple shift: instead of apply-first-think-later, I think first and only apply when it makes sense.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-result&quot;&gt;The result?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Instead of 30 applications and 25 auto-rejections, I send 5 and get 2–3 screening interviews.&lt;/p&gt;
&lt;p&gt;Same resume. Same experience. Fewer applications. Better outcomes.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-i-learned&quot;&gt;What I learned&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Maybe the hiring system isn’t broken. It’s just more complex than it used to be. And spraying applications into the void doesn’t work — not because the system is unfair, but because it rewards precision.&lt;/p&gt;
&lt;p&gt;The vacancy comes first. Study what’s actually required. Then decide if it’s worth applying.&lt;/p&gt;
&lt;p&gt;It turns out the bottleneck was never my resume. It was whether I was applying to the right positions in the first place.&lt;/p&gt;</content:encoded><category>career</category></item><item><title>The generalist&apos;s hiring problem</title><link>https://kondaurov.site/blog/generalist-hiring-problem/</link><guid isPermaLink="true">https://kondaurov.site/blog/generalist-hiring-problem/</guid><pubDate>Mon, 16 Mar 2026 21:44:23 GMT</pubDate><content:encoded>&lt;p&gt;I know too many things. Apparently that’s a problem.&lt;/p&gt;
&lt;p&gt;I started with PHP. Then spent years in the JVM world — Scala, Java, building distributed systems. Did backend, DevOps, CI/CD, built frontends with React and Vue. Now I’m deep into TypeScript, serverless, and cloud infrastructure.&lt;/p&gt;
&lt;p&gt;I could pick up Go or Python on any project — learning new tools has never been the hard part. The hard part is convincing someone to let you in the door.&lt;/p&gt;
&lt;p&gt;But try explaining that to an ATS.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;what-actually-happens&quot;&gt;What actually happens&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;You find a great position. You read the description and think — I’ve done this. The architecture, the scale, the kind of problems they’re solving. You’ve been there. Maybe not in this exact language, but you know how to build this.&lt;/p&gt;
&lt;p&gt;But the listing says “Go required.” Your last two years say TypeScript. You apply anyway. You don’t get the call.&lt;/p&gt;
&lt;p&gt;I get it — recruiters have 200 resumes to review. They need filters. But the filters are keyword-shaped, and people aren’t.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;i-think-the-system-rewards-narrow-specialists-and-punishes-curiosity&quot;&gt;I think the system rewards narrow specialists and punishes curiosity.&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;You’re either “a Go developer” or “a React developer.” If you’re both — you’re neither. And if you’ve been five things over fifteen years — good luck fitting that into a keyword match.&lt;/p&gt;
&lt;p&gt;The irony is — the best engineers I’ve worked with were exactly like this. Curious, cross-functional, able to jump between layers of the stack. But that profile doesn’t survive a 6-second resume scan.&lt;/p&gt;
&lt;p&gt;I can’t change the system. But I can engineer my way around it. That’s exactly what I’m building.&lt;/p&gt;</content:encoded><category>career</category></item></channel></rss>