@dimitarvp has some excellent points, his post being shared in the newsletter brought me here.
Interviewing can fail in many stages, and different employers can have wildly different approaches. I was an absolute shoe-in for one position, both to their and my benefit, and then I bungled the money negotiation and they stuck with their low offer and it never happened. So each step needs some being prepared. Interestingly they didn’t do one of these silly “So, how would you code this?” interviews which I loathe. They wanted to see some github code that I wrote, so I showed them some Haskell I had written for a private project. It helps to have something to showcase for those who are more real.
But those were perfect conditions, and I bungled it. Now consider most interviewers, they are just a waste of time. What kind of skill is it to have immediate solutions for random algorithmic problems in your head? Some companies are truly beyond the pale here, and it’s just bloody stupid. When you get one of those interviewers, just know they have no real ability to assess you, and unless you want to join “coding leagues” in your spare time, see it as a lottery: You either know what they ask you and can implement it in short order, or not. It’s like a random test for some college level material without having had a course before or knowing which will come up.
I had an argument with an interviewer once over what OOP was. Turns out my definition was matching up with Wikipedia, but he did not hear a word he wanted to hear. The whole interview was dominated by his false assumptions and by the end of it I didn’t want to hear from them again.
Going away from the weirdness and arbitrary nature of interviews, here’s how I got senior:
I try my best to keep my code short and readable, and if I find myself writing too much code I look for ways to fix that. So this led me through most of the Enum, List, Map, and Stream APIs, higher order functions, eventually macros, and writing code that generates elixir files for compilation, etc. The actual human-maintained part of my codebase has only grown by about 10% in two years although we keep adding features, and the generated code is many many times bigger than the human-written one.
elixir is an incredibly capable and fast string processing language, for example, I’d never have guessed. And there’s a lot of tools for code generation, including compiling code on the fly. In OTP20 I used macros to create functions that return constant terms so they would go into term storage and be looked up in constant time. This would compile at runtime. In OTP21 they introduced a key/value API for accessing the term storage safely and with concurrency-safe deletion if you need it, and I replaced all my code because this solution had more capabilities and was shorter. Yes, I deleted some cool code. But the project didn’t need it, nobody would have to maintain it anymore, so out it goes.
That is very vital IMO: When you learn to do better, refactor your code base to be better. I was the first person to code elixir at my department, others learned along. They had gotten the hang of recursion and were comfortable with it when I finally understood how OTP works. It was really back-to-front for all of us - until we got it, of course - now we can hardly remember working any other way. I realized how much the OTP callback module pattern cleans up code, and rewrote eventually everything to make use of it where it made sense, which met some resistance from coworkers, but we were all better for it in the end. For comparison: Another department more on the research side we got into touch with later couldn’t figure out OTP and gave up on the whole of Erlang even though it fit our domain perfectly…
If you see a better way, keep applying it until you find the next better way. So we went from writing our own recursions to OTP and List/Enum/Stream, then on to get_in/2 and update_in/3, we redid the way we work with conditions and case/if/cond and &&/|| statements many times, we eventually used more function captures where it didn’t affect readability too much, and most of all, I tried to pipe everything and optimized a lot of code for that.
I recently wrote a specific framework for state machines in our project because I wanted this part of the code, the actual tests in a testing system, to be more declarative. While I like gen_statem
I didn’t like what it did for readability in those parts of the code, and I probably have had four implementations by now for receiving multiple messages in one join state because I wasn’t pleased yet.
That’s how I got “senior” (the term has no real meaning in my company, I just felt that at some point I definitely crossed that mark for elixir). I never settled, code-wise. I want code to become more readable, shorter, reasonably efficient. I want computers to do repetitive work instead of me handwriting code over and over again. Sometimes that means reading elixir source code from the language itself, which has some constructs I always find make the code obscure, but it teaches you a lot.