Full Width [alt+shift+f] Shortcuts [alt+shift+k]
Sign Up [alt+shift+s] Log In [alt+shift+l]
33
I've been working with TypeScript for a long long time. I think I'm not too bad at it. However, to my despair, some low-level behaviors still confuse me: Why does 0 | 1 extends 0 ? true : false evaluate to false? I'm very ashamed, but I sometimes confuse "subtype" and "supertype". Which is which? While we're at it, what are type "narrowing" and "widening", and how do they relate to sub/supertypes? If you want an object that satisfies both { name: string } and { age: number }, do you & or |? Both make some sense, since I want a union of the functionality in both interfaces, but I also want the object to satisfy left & (and) right interfaces. How is any different from unknown? All I get is imprecise mnemonics like "Avoid Any, Use Unknown". Why? What, exactly, is never? "A value that never happens" is very dramatic, but not too precise. Why whatever | never === whatever and whatever & never === never? Why on earth is const x: {} = true; valid TS code? true is clearly not an empty object. I was doing some research on never, and stumbled upon Zhenghao He's Complete Guide To TypeScript’s Never Type (check out his blog, it's super cool!). It mentions that a type is just a set of values, and — boom — it clicked. I went back to the basics, re-formulating everything I know about TS into set-theoretic terms. Follow me as I: Refresh my knowledge of set theory, Map TS concepts to their set counterparts, Start simple with booelan, null and undefined types, Extend to strings and numbers, finding some types that TS can not express, Jump into objects, proving my assumptions about them wrong, Finally gain confidence writing extends caluses, And put unknown and any where they belong. In the end, I solve most of my questions, grow much cozier with TS, and come up with this brilliant map of TS types: Set theory First up, a refresher on set theory. Feel free to skip if you're a pro, but my algebra skills are a bit rusty, so I could use a reminder of how it works. Sets are unordered...
23rd Jan 2023

Stay updated

Get a weekly newsletter with the top 5 articles worth reading every week.

More from Vladimir Klepov as a Coder

The systemic decay of tech hiring

Complaining about tech interviews is the favorite pastime of software engineers. We know they're broken, we've seen exactly how they're broken, but, despite a decade of collective hand-waving, we haven't fixed them. In fact, we haven't even understood why they're broken: "interviewers are stupid" is a tempting explanation, but most people I've worked with are quite smart, so it can't be the only reason. I stepped into hiring manager shoes as the job market was descending into AI panic. I've run over a hundred interviews, made a few dozen hires (with a few epic fails), and eventually led a redesign of our process for AI reality. After months of experiments and reflection, I think I see how this works. In this article, we'll trace how an industry full of smart, well-intended engineers created the lunacy of the modern tech interview. This story has it all: chance and fate, fear and bravery, good people crushed by the system that's set up to degrade. I might not have an easy fix, but at least we'll understand the mechanics of this death loop. Let's go! Hiring is not deterministic We engineers expect actions to have a predictable outcome. Assign a variable in code, beep bop, its memory register value changes. Cause and effect. Naturally, we want an algorithm for hiring that reliably separates good engineers from bad ones. Nice as it would be, this is not a realistic expectation. Imagine: I'm a good engineer, your interview process is great, you hire me. One month in, I decide that work is not really my thing, and I won't try that hard any more. The process was fine, but it failed to predict my performance, because the future is inherently uncertain. Hiring is, in essence, a binary classification problem: based on some observations, predict whether a candidate will perform well on our project. Apart from the good outcomes, we have two types of errors: False positive: we hire a candidate who turns out really bad at the actual job. False negative: we don't hire a candidate who would have been a great fit. With enough hires, we're statistically certain to make both types of errors. But their effects are drastically different. The error asymmetry A bad hire (false positive) is costly and visible. On top of the wasted hiring budget, a truly bad engineer creates security risks, damages architecture, and brings down production, all in a day's work. That's the kind of mistake that gets everyone's attention and causes unpleasant questions along the lines of "Vladimir what were you thinking hiring that guy"? Missed good hires (false negatives), on the other hand, are practically invisible. Usually, the rejected candidate just vanishes. In the rare case we accidentally learn they turned out to be a great success somewhere else, it's easy to shake it off — just because they're a great fit over there, doesn't mean they would've been any good over here. We might get some extra time-to-hire and interview load, but this is nothing compared to the costs of a bad hire, right? So, the conventional wisdom is "false positives, avoid at all costs; false negatives, fine". After a bad hire, we'd run a postmortem to understand what went wrong, and how to avoid all this trouble in the future. Focusing on false positives collapses the hiring problem into one dimension: if a low performer sneaked through, the bar was too low, and we must raise it by adding complexity. This makes tech interviews progressively harder. But is harder always better? The tale of two complexities Welcome to our software engineering interview. Here, a surprise awaits: to pass, you must win a game of chess against Grandmaster Anatoly Karpov, former Chess World Champion. This kind of interview will be very hard, but would it be good at selecting productive software engineers? Probably not, because chess is very loosely related to software engineering. While real-world interviews rarely involve chess (I wouldn't mind if they occasionally did), the core principle is clear. Interview complexity has two parts: Relevant complexity predicts the candidate's performance. Incidental complexity randomly eliminates candidates by unrelated criteria. Both kinds make the interview harder, but only the relevant produces useful signals. Incidental complexity produces random noise, and is essentially a time-consuming equivalent of making hiring decisions through dice rolls. "Got it, don't involve chess in the interview and I'll be fine" — I hear you say. But in practice, isolating relevant complexity can be tricky. Say, we're hiring for a realtime dashboard team, and we add a pretty relevant topic — WebSockets. Are we filtering on genuine experience, or on recent exposure to the technology? Does current WebSocket knowledge really predict the overall performance, say, 3 months in? Maybe we've done the right call, maybe we're playing chess — probably a bit of both. So, we're stuck piling up complexity based on our gut feeling. Every new topic inevitably adds some incidental complexity, causing more false negatives. This is bad enough for a single team, but once we zoom out to the organization level, things escalate. Scaling the noise All across the company, hiring managers are ramping up complexity. At some point, the inefficiency and inconsistency become obvious: some teams go so hard they spend all their bandwidth on endless interviews and rejections; others devolve into low-performer silos. In a noble attempt to balance the complexity, spread the load evenly, and cancel out individual biases, we introduce a shared interviewer pool composed of engineers from different teams. When done naively, this makes things worse. Say, a candidate writes all the code in a single huge function. Interviewer A thinks this rushed slop demonstrates a lack of decomposition skills. To interviewer B, this is pragmatic speed — the candidate prioritized delivering the product over architectural meandering. When hiring for their respective teams, both interviewers may be right. But now the context needed to ground this subjective decision is gone. If the interviewers swap candidates, we get two rejections. When both interview any candidate, at least one is likely to call a "no hire", because their preferences don't intersect. If the interviewers blindly act on each other's "green flag", they get inept candidates. Everybody loses. To counteract, we standardize the process through a shared problem pool and scoring rubrics. A perfect rubric is both relevant and objective, but this turns into an impossible balancing act. Leaning into alignment too hard dumbs down the interview with easily measurable questions like "can list the nine JavaScript data types". Any space for nuance and judgement is also space for subjectivity and misalignment: we're stuck arguing whether the single huge function is a valid balance of speed and maintainability. Balancing between dumbery and misalignment, we get a bit of both. The alienation Sadly, misalignment is children's games compared to the structural collapse that lies ahead. A hiring manager fully responsible for their hiring is not a perfect setup, but it gets one thing right: process and outcome are owned by the same actor. The labor of interviewing, the benefits of a good hire, and the pain of a bad one are all yours. A shared interviewer pool fractures this incentive structure. The individual interviewers get the accountability for a bad hire, but gain nothing from a good one. This leaves no incentive to make a more adventurous hire every now and then — when in doubt, just say "no". Group ownership of the hiring decision creates a perfect setup for bystander effect. Facing a barely-there candidate who narrowly misses all the red flags, an interviewer might think: if they're really bad, one of the other 5 interviewers will surely reject them. Once all the interviewers act on this logic, the barely competent hire happily progresses. The interplay between these effect results in a schizophrenic process that favors mediocrity. We reject "spiky" candidates who don't strictly fit our one-size-fits-all profile — say, a great systems engineer with subpar SQL. Meanwhile, borderline candidates get hired via bystander effect. Facing the full cost of running the interviews with a net-negative outcome, some interviewers grow disengaged. Our rubric, designed to help a motivated hiring manager stay objective, backfires. To a burnt-out cog in the hiring machine, it gives an easy way out: just mechanically check the boxes on our "green / red flag" list, and you'll be fine. Unknowingly, we've set things up for the final act. The snake bites its tail We've seen how the fear of bad hires makes interviews harder and more random, and ultimately cements them into a rigid process that's not owned by anyone in particular. But to close the feedback loop and see how this causes more bad hires, we must look at the other party — the candidates. Faced with brutal multi-stage hiring processes with lots of incidental complexity and a significant chance of random rejection, candidates adapt by overfitting to the incidental complexity of the interviews (aka interview prep). Instead of mastering what you do best, you grind leetcodes — or, even worse, practice cheating with AI. Instead of telling a messy war story, you polish a STAR advertisement with doctored success metrics. This is a classic example of Goodhart's law: when a measure becomes a target, it ceases to be a good measure. Before you know it, you're hiring great interview takers instead of great engineers. A strict rubric additionally sets things up for a nightmare scenario. For top companies, it's very easy to find not just generic interview prep, but a crowdsourced list of specific tasks used in the interviews. Once that happens, the process decays into who's done their homework. And behold, the snake has swallowed its tail: in an effort to avoid bad hires, we inadvertently set in motion the exact machinery that eventually made bad hires more likely. Time for some bar raising! Now that I've laid out all the pieces of the puzzle, I should propose the fix. Well, here you go. To make no hiring mistakes, don't hire. To avoid challenges of growth, don't grow. Once we break these rules, the snake starts eating its tail. We trade bad hires for random rejections. We trade nuance for alignment. We can make the snake eat slower, or pull out a few inches of the tail, but we can't save the snake, because it's driven by probability and the human mind. The best we can do is embrace the mess and keep at least some decency. Tomorrow, I'll log into zoom. I'll give the candidate the best problem we've got. I know it's not 100% relevant, but at least it's refreshing. The candidate won't cheat with AI because we explicitly allow and encourage AI-assisted solutions. If they hit any red flags on our rubric, I'll note this in my report. If they show exceptional promise in some unscored aspect, I'll note that too. At any rate, I'll be as friendly and engaged as I can, because even failing miserably at a random task doesn't mean you're a bad engineer. And even being a bad engineer doesn't mean you don't deserve respect.

22nd Jun 2026 2 votes
Growing my team 4x has been a pain. Can we do better?

My name is Vladimir, and I'm an engineering manager of a team building a banking app. Following the success of our core banking product, we've decided to expand to other financial services. In the last four months my team has grown 4x, going from a 4-person team to 4 teams totalling 15 people. It was, frankly, a shitshow, and now I see many things we could have done better. At first sight, increasing your team is a perfect way to speed up your product development. In practice, scaling is very challenging. Today, I'll share the pains of growth we've ran into: Disbalanced growth across functions. Teams becoming too large to manage. Processes breaking down. Mass onboarding challenges. Seniority skew. Now that it's all done, I can't say these problems were unexpected, or that our solutions have been incredibly inventive, but a first-hand account is worth writing down. Whether you're a leader expecting your team to scale, or just curious about the daily challenges of an engineering manager, hope you'll find something for yourself. Let's go! Scale evenly across functions You lead a 5-man engineering team. You'd like to build 2x the stuff you're building. The obvious solution is to hire 5 more engineers. Problem solved? Not so fast. Anything you build flows across several stages, each handled by a certain role, for example: product manager -> design -> engineering -> QA -> product analytics -> (back to) product manager. If you have a balanced flow with your current team, enlarging just the engineering will not make you more productive: Downstream functions (QA / analytics) can't keep up with the increased production — they must either put in overtime or downgrade quality standards. Upstream functions (product / design) can't fill the backlog fast enough, and the eng team has nothing to focus on, either slacking or refactoring the refactorings. With rapid growth, some temporary disbalances are unavoidable. It's fine to spend a few weeks or months in a disproportionate state, but overall aim for balanced scaling. Here are some tips to smooth the transition and even use the disbalance to your advantage: If you have any control over it, give product & design a hiring head-start. You can get their artifacts ready for development with a limited series of grooming meetings, and once the new engineers are on board, you'll have some great useful tasks to feed them. Catch up on your tech debt. Have too many engineers and not enough product tasks? Don't despair: use the time to clean up some old bugs, do the overdue refactorings, and prepare the codebase for the speed-up. Expand the area of responsibility. If you lack QA specialists, it might be time for the engineers to practice their testing skills. Oversized non-technical component (product / design) is more problematic, but you can give them some no-code tools to replace the eng team in some scenarios — e.g. build an admin UI where PM can edit the texts, create new banners, and so on, without involving your team. Update team structure Say you have a normal-sized team (4–7 engineers) with your average meeting structure (whole-team planning, grooming, and retro + weekly 1x1 with every team member). Making it a 15-person team won't work at all. An hour-long 4-person retro has 10 minutes of speaking time per member — enough to make a point. In a 15-person retro, it's 3 minutes — not practical. You either exclude some people, or extend the meetings — both poor options. It's harder to agree on any decision, because you now have 3x the possible objections. The 1x1s alone eat up 7 hours (almost a full day!) of your time a week. Managing communication of 15 people plus all the external stakeholders is time-consuming. The team must be split. What does it mean to be a separate team, anyways? Ever heard of "high cohesion and low coupling" principle in software architecture? I think this also applies to teams: Shared information space. Team members know what you're working on, where you're going, who your peers are, the system structure and so on. Own a well-defined part of the product and the codebase. A look at a random feature is enough to guess the responsible team. Control your processes — meeting structure, releases, etc. You can't be responsible for what you can't control. Have all the capabilities needed for your day-to-day work. Begging someone each time you need to deploy, change the API or add a banner is not very effective. It's best to split by product domain: customer acquisition team owns the landing page and signup, daily banking team owns the main app, and so on. You could split by layer (product + infra) or by function (backend + frontend + mobile) — I feel these compromise points 2 and 4, but let's not die on this hill today. At any rate, a team over 8–10 people must be split into sane-sized chunks to keep going. A note on grouping You can split a team by (A) building the new team out of newbies or (B) mixing newbies and oldies in each new part. Prefer mixing: it distributes the knowledge across the organization, and the social connections from the original team prevent siloing. You could argue that (A) keeps the original high-performing team intact, but it does so by slowing down the new team and undermining your long-term flexibility. Where to get new leaders? Splitting a team into 3 parts creates 2–4 leadership positions, depending on your place in the new structure. Ideally, you have senior members of the original team to lead the new teams, because it's a rare opportunity for career growth on a management track, and they can easily hire and onboard new members of their teams. If you don't have a suitable candidate (everyone is either very junior, or hates management), it's fine to hire externally — following a few hiccups, I recommend hiring people with prior leadership experience, because adjusting to a new product and a new role at once can be too much. Split iteratively You don't have to produce a fully separate team right away. As usual, move step by step — you get faster results, and can adapt to the issues that arise. Here's one possible sequence: Assemble a domain team, appoint one as the lead (you can call it trial-lead, to give them a chance to cop out). See how they like their new roles, and if the headcount needs tweaking. Run a retro for the new team to catch communication issues or cross-team dependencies early. Separate the domain backlog and kanban boards (or wherever you track the tasks). You'll need it for further process splitting, and to assess the team's load and velocity. Split planning and daily meetings, so that the teams don't waste each other's time on discussing irrelevant tasks. Gradually transfer the remaining processes (1x1s, onboarding, postmortems) to the new lead. Split the codebase, so that the new team can fully own its service. Update your processes Just like your single-team structure, the processes you have will likely fail for a larger team, especially if you have many newbies. Example: we had a liberal release process — if you want some feature in production, you deploy it. As the team grew, the release frequency dropped — the newbies were afraid to touch prod, the oldies were hoping one of the other 14 people would do it. Before I give you my solution to this puzzle, let's look at the general advice for process scaling. Localize processes to the new teams. Owning processes makes a team more effective. Retros, plannings, kickoffs, demos, daily stand-ups, releases, documenting, on-call duty, whatever you can split, do split. Yes, your overall team loses sight of the stuff going on across the system, causing duplication and poor decisions, but in return we can focus on a specific business area, and maximize productive time instead of drowning in discussions. If you need to offset the downsides, introduce a cross-team sync here and there (still working on this one). Let the new teams experiment with processes. What worked for your original team doesn't matter, that team is dead and gone. What works for one of your teams won't necessarily work for the other, because they work in different conditions. For example, our CA team has many time-bound tasks from marketing. The core banking team focuses on building quality software, and fixing the bugs as they arise. Very different teams. Start with a copy of your current processes (just to start somewhere!), and introduce team retros as early as possible to tweak the process as needed. Stricten the centralized processes. Back to our problem with deployment — we couldn't isolate the release process to sub-teams, because splitting a monolithic front-end into independently deployable parts is technically challenging. We introduced a more structured release process: The releases are automatically built and ready to deploy every morning. No more decisions to make. Daily rotation of release managers responsible for getting the release to production. With regular training, you get better at releasing. The release process is clearly documented. The newbies have a clear path to follow, making it less stressful. This works for other centralized processes — writing documentation, debugging with customer support, maintaining shared libs. Overall: hand over as much process as you can to the sub-teams, and introduce clear rules for the remaining centralized processes. The onboarding valley Surprisingly, fast hiring can reduce your team's productivity in the short term. The newbies are not yet up to speed, and the oldies now spend time explaining your codebase and reviewing code. This will fix itself over time, but here are a few strategies to get past the bump faster. Prefer slower growth. Adding one person every few weeks is much better than adding 6 people simultaneously, because: The "onboarding load" stretched over time occupies a smaller share of your team's resource. A few weeks in, new hires can already help onboard someone else. In some cases, they'll do a better job than any oldie, because their memories of one-off tasks like setting up the dev environment are fresh. Every onboarding exposes new roadblocks in your process, helping you smooth the next ones. On paper, batch onboarding might seem like a time-saver, as you can make a lecture explaining the basics to many people at once. In practice, unless your product is very small, or the tasks are very repetitive, every newbie faces very different challenges, drowning you with a wave of questions. Encourage peer-to-peer onboarding. As a leader, you might think onboarding is your personal responsibility. I call BS — peer-to-peer onboarding is clearly better: More "onboarding resource" leads to faster, better onboarding. The load on you, personally, decreases, freeing time to do other impactful things and, you know, live. Team members get a safe environment to practice their mentorship skills. People get to know each other, instead of only talking to you. ICs with recent hands-on experience do a better job at explaining the specifics than you. You can pair a formal "mentor" to every newbie, or direct questions to a team group chat. If you want to control the overall onboarding, at least route specific questions to team members experienced in that area instead of trying to come up with all the answers yourself. Write the docs. The best way to make onboardings cheaper is writing stuff down instead of explaining it over and over again, with your mouth. Some particular things to focus on: Onboarding checklist — the things every new team member must do: get a VPN certificate from the security dude, join this and that chat, clone a repo here and there, boom you're done. Only include essential steps — adding somewhat useful stuff obscures the actually important things. Document your existing business processes, system architecture, technical conventions, team and communication structure. It's better than explaining in real-time, because you get higher-quality charts, relevant links, and you can collaborate to put the knowledge of multiple team members in one place. Tooling and automation. The more automated a process or convention is, the less onboarding you need. Example: if you build your releases locally and upload somewhere via FTP using the keys you get from Piotr the devops, it's time to set up decent CD instead of documenting the current state of affairs. Pro tip: encourage newbies to improve and update the docs as they follow along — it's a great first contribution to your team! Senior vs junior hires It seems sensible to focus exclusively on senior hires. Experienced engineers get up to speed quicker, because they're already familiar with the basics, have a lower risk of making catastrophically poor decisions, and can bring good practices and ideas from across the industry to your team. Not so fast — here are some reasons to hire junior developers. With little exposure to the industry, they can easily adapt to whatever culture and processes you have. Anyone on your team can mentor a junior hire, while getting e.g. a junior engineer with decent knowledge of the product to mentor a newly hired senior engineer might be awkward, not very productive, or even taken as an insult. And of course, you can hire more junior engineers on the same budget. Overall, aim for a balanced team composition in the mid-term. You don't want your team to be a kindergarten, but a nursing home is no good either. Remember that people tend to gain experience, so the junior engineers you hire will become middle in no time. Today, we've discussed the challenges of rapid team growth — and ways to address them: Hiring more engineers won't speed you up unless product, design and QA grow to match. Start by growing product & design. If product lags behind, use the spare time to clean up the tech debt. Expand the area of responsibility of the oversized roles. Teams over 8–10 people are hard to manage. Split into chunks of 3–7 people, preferably by business domain. Mix old and new members in each team. Split step-by-step instead of going all in. The processes of your original team won't accommodate a larger team. Localize the processes to the sub-teams as much as possible. Let the teams tweak their processes to suit their needs. Stricten the remaining centralized processes. Onboarding is time-consuming, and can slow you down. Go slow: onboarding a person every week is easier than 6 people at once. Write the docs instead of explaining stuff over and over. Peer-to-peer knowledge transfer is better than onboarding everyone personally. Hiring only senior engineers is not a silver bullet. Aim for a healthy experience distribution in the mid-term. Hope these tips help you get past the scaling issues and up to speed in no time.

18th Apr 2024 60 votes
From engineer to manager: what I love, what I hate

It's been almost 2 years since I moved to a team lead role, then to a full-time engineering management position after the expansion of our team. I've been a front-end developer for 7 years before that, and initially I took the "advanced individual contributor" career track before doing the management turnaround. How's it been? Bumpy, but fun. In this article, I'll share the things I love and hate about my current job. Love Let's start with the positive side of management positions. There's plenty to love, honestly. Impact First things first, I adore the power to improve the product we're building, and the overall team well-being that comes with a management position. As an engineer, you'll sometimes find yourself in a tough spot with little to no power to change things. Early morning standups are a chore? Code quality sucks? The new feature makes no sense? As a manager, the power to change is yours: you get both the formal and informal authority to change things for the better, and to make yourself and your team happier. Your words have weight. If you, an IC, say "guys, we really should write tests", everybody goes "oh, crazy old Vladimir, all grumpy again, haha, where do you see tests fit here?". If you, an EM, casually say "guys, we really should write tests", you might be surprised to find the tests unexpectedly growing in different places. Pleasant. Career opportunities Being an engineering manager is a more promising career opportunity than an engineering track. This might be controversial, but hear me out: The EM is normally higher-paid than the basic team-level engineering grades (junior / middle / senior). There are higher IC grades (staff, principal, president of code, whatever) in the EM+ salary bands. These staff+ IC jobs are concentrated in larger tech companies, because those have tougher technical challenges. Almost every software team in every company has a leadership position. The management career ladder is "taller" than that of ICs. Yes, the chances of becoming a CTO are slim, but it's an opportunity that's just not there for a pure IC with no management experience. All in all, I believe the demand for EMs to be steadier than for staff+ engineers, and this path gives you more opportunities at the later stages of your career. On a related note... Transferable skills Management skills are more widely useful than an IC engineering role. A decent front-end engineer with React experience won't have trouble moving to another front-end framework, and can probably transition to a back-end / mobile engineering role with -1 grade (a year handicap or so). That's not bad. What roles are available to someone with engineering management experience? First, you can easily take on a team with a wildly different focus — mobile developers, infrastructure, ML engineers. You'd need some time to get up to speed on the big-picture technical struggles of your new team, but most companies would take this shot. If you don't want to be an EM any more, you're well-positioned to move to a project or product management role. If the entire tech market falls into decline, many management skills would still work for other industries. While I don't see a big flow of tech managers moving into construction business (tech does pay well), there's one alternate path to consider — entrepreneurship. Involvement with people and business decisions makes for great training before starting your own business. So, being a manager gives you quite a bit of career flexibility, and makes you less vulnerable to future technological shifts. Less knowledge rot Suffering from front-end fatigue? Can't keep up with the newest shiniest frameworks and tools? Management's got you covered! The "hot new" agile / kanban / scrum methodologies are 20–30 years old. The basic meeting types (demos, dailies, 1-on-1s) have been developing for centuries. At the core, you have teamwork and human interactions, which haven't changed that much since the beginning of humanity. My grandfather was a big railroad boss in the 70s, and we can sensibly discuss some of my work challenges. "Oh, you have this talented slacker? Give him some big important task, let's see what he's worth." When it comes to computers, he's more like "I'd like a shovel big enough to throw all your silly gadgets into stratosphere." So, if you're tired of keeping up with the latest hot thing in tech, a management role can provide a well-deserved relief. Do keep an eye on what's happening on the tech side of things, but there's no urgency, and no need to get real deep. New challenges Frankly, after 4–5 years of working in a particular tech area, you can solve the vast majority of practical problems well enough. If you want some work challenge, you can: Slightly alter your stack — say, a new FE framework. But it's unlikely to keep you engaged very long. Make a broader career shift — e.g. frontend to backend. This would probably give you another couple years of fun, but such transitions are, in my experience, either random (e.g. your BE dev quits and someone has to fill the role), or hit your salary. Invent problems out of thin air — rewrite everything using a new library, or handle 9000 RPS "for the future". Fun, but most of the time it's more harm than good for your team and business. Of all the possible career moves a seasoned engineer can make, switching to management gives you the most new challenges (years worth of new stuff to learn) without hitting your salary. Hate As much as I like the challenges and impact of my new role, and the practical career benefits, I'll be the first one to admit it has downsides as well. Corporate BS As an engineer, I hated bloody corporate BS: individual performance reviews, useless deadlines, company-enforced restrictions on processes and tech stack. Well, congratulations, as a manager you are the sheriff of these practices, whether you believe in them or not. As a leader of a team in an org with performance calibrations, I must nominate 1 person who hasn't been working hard enough every 6 months. This human chess is soul-sucking, but I can't make it go away — if I don't offer a sacrificial teammate, someone will be picked randomly further down the process. Crazy shit. Sometimes you can negotiate a bit, or hack the process, e.g. assign "below-expected performance" on a round-robin basis, but to your team you'll sometimes be the corporate monster. Sigh. And I haven't even been through the real tough stuff like layoffs, closures and reorganizations. Awkward social situations I've made it to an engineering management position by being good at building stuff. I've been prepared to help with technical decisions, give career guidance, tune processes and set up automation as needed. In fact, a large portion of my job is debugging social tensions and psychological insecurities of people. Your junior engineer comments out a few tests to deploy a feature preview. The QA person sees this, and is very pissed because your whole team apparently does not respect the QA role and the value they provide. Restore trust. A project manager makes an unsuccessful joke that hurts your designer, who's now crying. Make the PM apologize. Am I a kindergarten teacher or something? Boy, I'm no psychologist, and I can't say I'm exceptionally good with people. This part of my job is quite hard, trying to fake it til I make it here. Office hours Life of an engineer is relatively relaxed. If you don't have anything urgent, you can go lay on the grass for half a day, thinking about the future of your project or something. You can miss a few meetings on short notice, no questions asked. Now, you're an EM. Try going and lying on the grass for a few hours. You come back to a messenger full of problems: your intern can't work because she forgot how to npm install; a senior manager wants to discuss some potential feature; release has derailed. Also, you can't really skip a meeting you're supposed to facilitate / organize without some up-front preparation. This might improve as your team matures and builds better processes, but in general you feel office hours much more as a manager, and your work-life balance directly depends on how good you are at your job. Long feedback loop The final thing I hate about management is the long feedback loop of your actions. Most engineering tasks show the result quite fast: new features take weeks to months, and if that's too long for you — fix a bug and see happy users the next day, or refactor some code and watch complexity decrease in a few hours. Amazing! You're a manager? Well, very few of your actions produce a visible result in under a month. Suppose your team has grown too large, and you want to split it up. You must pick a well-rounded set of engineers for the new team, talk to everybody involved to see how they feel about such a change, arrange new regular meetings, set up processes and communications, do some jira magic, maybe isolate the codebases of sub-products. If you think it can be done in a week, well, you're wrong. Then, even the right changes can make things get worse before they get better. Say you're understaffed, and you decide to hire. In the short term, you spend hours and hours interviewing, and a new team member won't get up to speed right away, sucking out precious time for onboarding. It's sometimes hard to see the long-term goal behind the short-term inconvenience. So, while engineering problem-solving is often fairly straightforward, management changes are more similar to large-scale refactorings. You won't see any quick improvements, which can be frustrating. To sum up, moving from an IC engineering role to a management position has been a rollercoaster ride for me, with both bright and bleak spots. Here's what I love: The wider impact on the product and team. Management is a great long-term career track: it gives you more job opportunities than a staff+ IC, the flexibility to move between different technical areas and roles, and skills that will be relevant across various industries for years to come. If you're bored with your field of tech expertise, moving to a management role is a great way to bring the challenge back into your job. And here's what I hate: Enforcing corporate decisions and policies can be soul-sucking. Dealing with social tensions and psychological insecurities of people isn't something I was ready for. It's hard to go offline even for a few hours without preparing in advance. Your actions have long and non-linear feedback loops with very delayed gratification. Now, is this career move the right one for you? If you enjoy challenge and responsibility, and you get an opportunity — I'd say go for it! Yes, management is not a fit for everybody (I'm not even sure it fits me TBH), but it's a great experience that would surely expand your skill set and make you see engineering work from a new angle. If you totally hate it, you have plenty of time to go back into coding =)

16th Feb 2024 60 votes
I conducted 60 interviews in 2 months — here's what I learned

It's hard to believe, but, starting mid-october 2023 I conducted 60 technical interviews and hired 10 people into our team. It's been extremely tiring: around 80 hours of active interviewing, plus writing interview reports, plus screening CVs and take-home assignments, plus onboarding new members — all while doing my normal work stuff. Still, I feel like I learnt a lot in the process — things that would help me as a candidate in the future, and might help you land your next job. Note that I'm a fairly relaxed interviewer, and, as an internal startup of a large tech company, we generally have a more humane hiring process, so your mileage may vary. Still, I've done my best to pick the tips that I feel are universally applicable. Here are nine insights I took out of this experience, in no particular order: Be generous with your "expected income". Say you're a solid higher-middle engineer, and you ask for a senior salary. My thought process: OK buddy, it's a bit more than reasonable now, but I won't have to fight for your promotion 8–12 months from now when you get there, and I don't have to spend another 12 hours of my own time (and leave my team understaffed for another few weeks) looking for a real hardcore senior, so I'll let you have it. Now suppose you ask for a junior salary. It's suspicious — why is your bar so low? Is there someting about your work performance you're not telling us? So, do your research on reasonable salaries for your level of experience, and aim slightly above that. Ask the right questions. I always leave time for the candidate to ask me questions — obviously, this lets the candidate probe what it's like to work at our team, but it's also the best opportunity for me to learn what really matters to the candidate. I've never been much of an asker myself, but now I see that "Thanks, I have no questions" does not look good — if anything, it paints you as someone who doesn't care. Here's a short list of good questions: What does the daily work in this role look like? Harmless. What features are you building next? Caring about the overall product, nice. Sometimes the answer is "I can't disclose this secret", but not that often. Anyting about processes or team structure: how many people are on the team? How often do you release? What regular meetings do you have? Interested in organization, might want to be a team lead someday, great. Anything tech-related: which framework do you use? Why did you pick framework X? How do you test your app? Especially suitable for junior- to middle developers who are most involved in hands-on work. What kind of tasks do you see me doing? Again, just a good neutral question, because responsibilities for any role differ wildly between companies. What growth / promotion opportunities does this position have? Cool trick, flipping the feared "where do you see yourself in 5 years" question against the hiring manager. Here are a few questions that are not very good: Do you use jira and github? It's a minor detail, won't you be able to work with youtrack and gitlab? Do you sometimes work late? Only if something breaks, but overall this question makes you seem a bit lazy. People on poor teams that routinely overtime aren't likely to answer this question honestly, at any rate. Social skills matter. I understand that not everybody is super outgoing, but if we already feel awkward 1 hour into our acquaintance, why work together — to feel awkward for months to come? Just a few tips anyone can follow: Be energetic. You're tired, I'm tired, we're all tired of endless interviews. Are you just tired today, or generally always too tired to get anything done? I know it's easier said than done, but try and show me all the energy you have left. Show respect. People enjoy being respected. Very easy one: you have a great product. Sounds like you have a great engineering culture. This is one of the most interesting interviews I've ever seen. Like, I know you don't necessarily mean that, but subconsciously I'm very pleased: "oh yes, I'm very proud of my interview process, thanks for noticing" On a related note... Provide conversation opportunities. Q: Do you use TDD? Bad answer: "no". Good answer: "no, but I've heard of it. Interesting approach. Does your team use TDD?" Now you get to spend 5 minutes talking on your terms instead of being bombarded with random questions, and you come off as someone curious about stuff. On another related note... It's easy to hurt people. People normally ask you about stuff because they care about it. So, again, the interviewer askning "do you use TDD?", presumably, likes TDD and uses it. So, the worst answer: "no, TDD sucks, it's pure waste of time for idiots." A rare interviewer might appreciate you having a strong opinion on a topic, but to most this just paints you as a jerk, kinda like "Here's a photo of my children — "I hate children, and yours are especialy horrible". Not smart. Smart talk is not your friend. Saying stuff like "our front-end guild evaluated several cutting-edge approaches to testing universal applications" only makes you seem smart if you can elaborate on that topic: what these approaches were, the pros and cons you found, what tradeoffs you made for your final decision. If you can't answer a follow-up question beside "we settled on jest, not sure why", it was better to stay away from that topic altogether. Related: "in code reviews, I always consider the optimality of the algorithm selected" (proceeds to estimate the time complexity of comparison-based sorting as O(1). I never ask this unless the candidate boasts about her algo skillz). Admit your mistakes. Don't know an answer? Your code has a bug? It's always better to admit it and then try to come up with something at the spot than trying to talk your way out of it. Event loop? Sure thing, I'm an expert on loops. It's the way events are looped. Uses logarithmic weighing. Again, this makes you look like a candidate with big mouth and small hands. I have seen a couple of people who could talk their way out of any situation, but I honestly think with such skills you'd do better in a different line of work, like international relations, or selling financial services. Note that you really should give it your best shot — giving up at the first sign of trouble is not a good impression. If you genuinely have no idea — see conversation opportunity: "Event delegation? Tough luck, never heard of it. Would you tell me about it so that I learn something new today?" Make yourself memorable. It's hard to keep detailed profiles of 10 candidates in mind — after a good interview streak all I remember is the general impression (great / OK / horrible) and a few truly notable things. This guy worked for some crypto scam that went bust, that girl had a cute dog that was trying to eat the camera. The worst you can do is be a totally neutral candidate — we've had an interview, but I can't remember any details. So try and sneak some anecdote, or wear a silly scarf — something to remember. This point is especially important for intern / junior positions — online JS bootcamps do a good job of covering the basics, and it's really hard to differentiate these candidates. The memorable thing doesn't have to be professional, or even positive (even though it sure won't hurt) — your best bet would be some original personal project. Ask for feedback on the spot. Asking how you did at the end of the interview doesn't hurt. Yes, some interviewers will be hesitant to answer — at large companies, the feedback is normally sent through the recruiter, and you're never sure if sidestepping this process would get you into trouble. Besides, if the feedback is not complimentary, you're essentially asking for conflict at the spot, and people normally avoid conflict when possible. Still, it's a chance to adjust your expectations (if the interviewer says, looking you in the eyes, that you've done great, it's a good sign), and you might get actually useful tips that would probably get lost passing through the written report, and then through the non-technical recruiter.

6th Jan 2024 51 votes
Svelte reactivity — an inside and out guide

I've been working with svelte exclusively for a year now, but I still manage to shoot myself in the foot every now and then when using reactive state. Some of the confusion is due to my prior experience with React, but some points are confusing on their own. Today, I dive into svelte internals to understand what's really going on. When you mutate svelte object state, do you really mutate the JS object? Why doesn't array.push trigger an update? How are $-dependencies determined? Does setting a variable to its current value trigger an update? Does a block using one object field, state.field, only run on changes to this field? Why do $-variables behave a bit weirdly under JS scoping rules? We won't talk about stores too much, we still have a lot to discuss before we get there. If you're in a hurry, here's a customary cheat-sheet: Let's go! Object state is mutable One thing that caught me off guard, coming from react, is that object state is mutable — the exact object defined as the initial value will stay there as long as you don't reassign the variable: // data is always the same object let data = { active: 0 }; const toggle = () => data.active = !data.active; <button on:click={toggle}> {data.active ? 'on' : 'off'} </button> See repl. By the way, this behavior also applies to stores. This is in contrast to react, where most optimizations rely on "purity" — a thing is only considered changed if its reference changes. So, svelte state is not an observable object built by JS means, but a regular JS variable with an update event bus attached separately. So far so good — the svelte way is close to how JS works. But what actually triggers the update event? Updates are triggered by assignment operator The big selling point of svelte is its concise syntax. As a showcase example, you often see what looks like simple JS, but changing the variable magically updates the component: let isOn = 0; <button on:click={() => isOn = !isOn}> {isOn ? 'on' : 'off'} </button> The magic disappears once you get to mutable objects — calling array.push() or any other mutating method does not trigger an update: let items = []; function addItem(text) { items.push(text); } {items.join(' ')} <button on:click={() => addItem('hehe')}> more! </button> This seems very familiar to react developers — of course, mutating the object does not change its reference, so further updates are skipped! To add to the confusion, react-style immutable update like items = [...items, text] fixes the problem. However, svelte state is mutable, and the real issue is the lack of an assignment operator which actually triggers the update, so the weird self-assignment pattern works just as well: let items = []; function addItem(text) { items.push(text); // #enable_magic items = items; } Any flavor of assignment operator (+=, ++, and so on) works, but it must be present for the update to happen. Another thing to note is that update logic is dynamic, not a strict "if callback X runs, fire listeners to variable Y" rule. The assignments are compiled to a call like $$invalidate(0, items), so conditional assignment only triggers updates if it's executed: // only triggers an update if text is truthy function addItem(text) { if (text) { items = [...items, text]; } } // always triggers an update function addItemAlways(text) { items = text ? [...items, text] : items; } Dependencies are determined statically Unlike invalidation, which happens dynamically at runtime, $-blocks dependencies are determined statically at compile-time. If you have some logic that only depends on text if isTracking is true... let isTracking = false; let text = ''; $: if (isTracking) { console.log(text); } ...the block runs on every text change regardless of isTracking value, because both referenced variables, text and isTracking, are recognized as the block dependencies. This is in contrast to runtime reactivity, as seen in MobX — there's no way to even see the dependency on text if the code accessing it does not run. One inconvenient side-effect of this dependency detection mechanism is the lack of visibility into implicit function dependencies. Say you have this code: let items = ['hello', '', 'friend']; const getNonempty = () => items.filter(x => !!x); $: nonempty = getNonempty(); Since the $-expression defining nonempty does not explicitly mention items, it will not run on items change. Fix: avoid functions in $-blocks, or make sure they're pure (only depend on their arguments), or (worst case) make the function itself derived: $: getNonempty = ... Primitive updates are pure Based on the points above, you'd assume that assigning to the variable always triggers an update, but that's not the case. When repeatedly setting a variable to true, only the first change results in an update: let isEnabled = false; // only run on the first click afterUpdate(() => console.log('updated')); $: console.log({ isEnabled }); <button on:click={() => isEnabled = true}> {isEnabled ? 'clicked' : 'click me'} </button> In general, setting a primitive variable to its current value does not trigger an update. Here's the svelte code that implements this optimization. Object updates are not pure But in that case, how on earth can this support object mutation and self-assignment that don't change the object reference? Well, the trick is simple — it doesn't. Wrapping isEnabled with an object removes the optimization, and now the update happens on every click: let state = { isEnabled: false }; // runs on every click afterUpdate(() => console.log('updated')); $: console.log(state.isEnabled) <button on:click={() => state.isEnabled = true}> {state.isEnabled ? 'clicked' : 'click me'} </button> The code implementing the comparison always says "it's changed" when it sees an object. Reactivity is variable-grained As we've just seen, object state is always treated as a whole — there is no field-level reactivity. If you have an $-block that only depends on a single field of the object, it will run on any change to the object: const fields = { clicks: 0, name: '' }; // runs on every "clicks" change $: console.log(fields.name); <button on:click={() => fields.clicks++}> clicks: {fields.clicks} </button> <input bind:value={fields.name} /> If we only want the $-block to run on actual name change, we can achieve that by extracting name to a separate reactive variable. This variable will have its own "change event", independent of other object fields. In our example it's better to just define the fields as separate variables from the start, but in general this can be done using a $-variable: const fields = { clicks: 0, name: '' }; // uses primitive comparison under the hood: $: name = fields.name // runs on "name" change only $: console.log(name); <button on:click={() => fields.clicks++}> clicks: {fields.clicks} </button> <input bind:value={fields.name} /> Note that object-to-object mapping does not optimize anything and just runs on every dependency change. This is because the "pure" optimization is only applied when all dependencies are primitive (computation is not triggered), or the output is primitive (further dependencies are not triggered). $-variables are an illusion If you've spent some time moving $-variables around, you know they behave weirdly from JS scoping perspective: You can reference the "scope" variables (declared with let / const) declared after the $-expression (in normal JS, this is TDZ) You can access $-variable anywhere in the script, but the value outside $-blocks is undefined Here's an example to illustrate this behavior: // you can reference variables before they're declared $: name = state.name; let state = { name: '' }; // logs "undefined" console.log(name); The trick is, again, simple — $-variables are just sugar over a simple let declaration and an $-block that assigns to the variable. The variable declaration is hoisted to the top (which is funny, because this perfectly emulates a var declaration), and $-blocks become callbacks defined after the synchronous initialization code. The un-sugared equivalent of the code above will be: // hoisted let declaration let name; // init block let state = { name: '' }; console.log(name); // $-blocks $: { name = state.name; } If you want to go one level down, the output in both cases is: let name; let state = { name: '' }; console.log(name); $$self.$$.update = () => { if ($$self.$$.dirty & /*state*/ 1) { // you can reference variables before they're declared $: name = state.name; } }; One funny side effect of this is that you can assign to the $-variable from e.g. another $-block, or a callback. The variable is not strongly bound to its definition, the last assignment wins. This would be quite confusing, though, and I recommend against such trickery. To summarize, here's the result of my research: Svelte object state is truly mutable. "Reactive variables" are just regular JS variables with change event bus attached as a sidecar. Updates are only triggered by assigning to reactive variables — assignment is compiled to $$invalidate(var_index, value) call. Reactive block dependencies are determined at compile-time based on variables used inside the block. "Pure" optimization only applies to primitive values. Variable is the smallest unit of reactivity — changing a single object field triggers all the blocks using this object. $-variables are compiled to a hoisted let declaration and a callback called after the setup code. Hope you've learnt something useful today — I sure have. Next time, we'll dissect svelte stores — follow me on twitter to stay updated!

22nd Apr 2023 41 votes

More in programming

Abusing ID3 chapters to turn videos into glanceable podcasts

I listen to a lot of podcasts, and I like how they fit around other tasks. I press play, lock my phone, and put it down. I’m free to wash the dishes, fold the laundry, or shop for groceries. Unfortunately, more and more information is only published as a video. Technical talks, conference sessions, video essays – they don’t work in an audio-only podcast app. I could convert these videos to MP3 files, but that breaks down the moment a video isn’t pure spoken word. If a speaker says, “Look at this slide” or holds up a diagram, an audio-only file leaves me stranded. I don’t want to give up the podcast player I like, nor stare at a screen for an hour – but I do want the information in these videos. To solve this, I’m abusing my podcast player’s chapter support. This gives me the best of both worlds: I can listen to a video as audio-first, and glance at my lock screen if I need a moment of visual context. The idea: Chapters every few seconds MP3 files can have ID3 metadata, and ID3 metadata can include chapters. A chapter covers a particular time range, and it can have an associated title, description, and cover art. My podcast app of choice is Overcast, which can’t play videos, but it does have robust chapter support. I can jump between chapters, navigate a table of contents, and see per-chapter cover art. To get videos into Overcast, I’m creating MP3 files with a new chapter every few seconds, and the per-chapter cover art is a corresponding frame from the video. As I play the file, I get a slow, stop-motion-like rendition of the original video. If my phone is locked, I can glance at my lock screen and see the current frame in the Now Playing screen. Overcast is developed by Marco Arment, and I got this idea from Forecast, his app for adding chapters to podcasts. In particular, I was struck by its ability to create chapters that don’t display in the chapter list – ideal if I don’t want a table of contents with hundreds of entries. As I was developing my script, I compared my output to the output from Forecast to ensure I was creating the chapters correctly. The code: FFmpeg and Mutagen There are three steps in this process: Convert a video file to an MP3 Extract images from the video at a fixed interval Insert the images as hidden chapters in the MP3 file Let’s go through each in turn. 1. Convert a video file to an MP3 Converting a video file to an MP3 is a single FFmpeg command: ffmpeg -i video.mp4 audio.mp3 This is consistently the slowest step of the process, and I do wonder if I could use different settings or an alternative encoder to make it go faster – but it’s not slow enough to be worth further investigation. 2. Extract images from the video at a fixed interval Extracting images from a video needs a more complicated FFmpeg command: ffmpeg -i video.mp4 \ -vf 'fps=1/5,scale=iw*sar:ih,scale=min(iw\,945):min(ih\,945):force_original_aspect_ratio=decrease' \ thumbnail_%04d.jpg This extracts an image every 5 seconds, downscales any image larger than 945 pixels square (while preserving the original aspect ratio), and saves the results as sequentially numbered JPEG images (thumbnail_0001.png, thumbnail_0002.png, and so on). The key is the -vf flag, which defines two FFmpeg filters: The fps filter selects one frame every 5 seconds (fps=1/5). The first scale filter scales the width based on the sample aspect ratio (scale=iw*sar:ih). Without this filter, frames can be stretched and distorted. The second scale filter scales the input video, preserving the original aspect ratio (force_original_aspect_ratio=decrease), and ensuring the output images fit within 945×945px or the size of the input video, whichever is smaller. My limit is 945 pixels because that’s the largest size that cover art is shown on my iPhone. This filter still isn’t completely correct – it sometimes creates images from portrait videos that are smaller than I’m expecting – but it’s good enough. These are only thumbnails for glancing at, and if I want to change it later, I can always do the image resizing outside FFmpeg. 3. Insert the images as hidden chapters in the MP3 file Inserting the chapters into the MP3 file is more complicated. Although FFmpeg has basic support for ID3 metadata, as far as I know, it can’t insert chapters with per-chapter artwork. Instead, I’m going to reach for Python and the Mutagen library. Here’s the code to add a chapter to an MP3 file: from mutagen.id3 import APIC, CHAP, ID3, PictureType audio = ID3("audio.mp3") with open("thumbnail_0001.jpg", "rb") as f: img_data = f.read() image_frame = APIC(mime="image/jpeg", type=PictureType.OTHER, data=img_data) chapter_frame = CHAP( element_id="chp1", start_time=0, end_time=5 * 1000, sub_frames=[image_frame] ) audio.add(chapter_frame) audio.save() This creates a single chapter that lasts the first 5 seconds (0 to 5000 milliseconds), and the per-chapter cover art is thumbnail_0001.jpg. If we ran this in a loop, we could add images for every 5 second slice of the original video. This code is inserting two frames into the ID3 metadata: The CHAP (chapter) frame contains the timing information, and it can have subframes for metadata like title, chapter art, or associated URL. The APIC (attached picture) subframe contains information about a picture, which can either be a blob of image data or a URL to an image on the web. Normally, you’d also insert a CTOC frame which defines a table of contents, but I don’t want a TOC with hundreds of 5-second chapters, so I’m deliberately not doing this here. This is allowed by the ID3 spec – you’re not required to insert a CTOC frame if you’re using chapters, and you can have chapters that aren’t listed in your table of contents. To work out which frames I needed, I used Forecast to create some chapters by hand, and I inspected their frames. In particular, loading an MP3 and calling Mutagen’s pprint() method shows a human-readable list of frames, and then I could drill into the individual fields: from mutagen.id3 import ID3 audio = ID3("audio.mp3") print(audio.pprint()) I wrapped all this code in a project called glancecast, which allows you to convert a video file with a single command, with optional flags to set the frame length and chapter art size: $ python3 glancecast.py interesting_talk.mp4 interesting_talk.mp3 The process takes a minute or so to complete, most of which is spent transcoding the video file to MP3. The resulting MP3s are usually 40 to 50 MB in size, which is very reasonable. The outcome: How it looks in practice Here’s what one of these “glanceable” podcasts looks like in Overcast and on my lock screen: Maggie Appleton presented this talk over two years ago and it’s been on my “talks to watch” list ever since. Once I put it in Overcast? I listened to it in less than a day. It’s not a lot of extra information, but enough that I can quickly glance down and get the gist of what a speaker is saying. Both views update with a new frame every few seconds, or I can put my phone in my pocket and ignore the screen. I’ve used this approach for half a dozen videos so far, and I’m happy with the results. I expect to keep using it, because I have a long queue of videos I’ve been meaning to watch. If you’d like to try this, check out glancecast for the full code and instructions. [If the formatting of this post looks odd in your feed reader, visit the original article]

11 hours ago 1 votes
AI Isn’t Replacing Open Source

Andrew Baker, the current Group CIO at Capitec Bank wrote an interesting piece on AI and open source, and how these tools that generate code according to one’s specification may replace the general reliance on open source implementations done by contributors around the world. I’d really recommend reading it. I have great admiration and respectContinue reading "AI Isn’t Replacing Open Source"

18 hours ago 1 votes
Confessions of an Unrepentant Slop Snob

A framework for thinking about when AI involvement is additive or a violation

yesterday 1 votes
Planning with Agents: Divided Worlds, Boundary Objects, and Thicker Interfaces

Why we need richer, thicker interfaces and better boundary objects for collaborative planning with agents

yesterday 1 votes
Foundations of Agent Friendly Codebases

A look at 10 foundational pillars that enable agents to operate more competently and more efficiently in any codebase.

2 days ago 1 votes
📚 BoredReading

You seem to be enjoying this.

Join free to unlock everything.

Create free account

Already have an account? Sign in