Recently, I was in the process of finding a new job, and because my main expertise is javascript, all my applications required a test in frontend (usually in React.js) and Node.js. They are not very different – some basic API in node.js and (sometimes) data crunching, and for frontend it is some small “real-world application”. I will focus mostly on the latter, because in my applications we paid more attention to it.
Usually, you are given some task which somehow reflects the domain of the company, so you build a small piece of functionality, which theoretically can be reused somewhere in their product. It is the opposite of the whiteboard interview, which has a pretty notorious reputation, and is supposed to be an interesting experience for both parties – you solve some more or less “real” problem, and they can evaluate you based on the skills you show applied to their tools and problems. It is a pretty interesting trend, though – I can only say from experience for front-end and Node.js jobs, but people tend to ask you for more hands-on tasks, rather than just canonical CS questions – data structures, algorithms, etc. My experience comes mostly from applying for more senior positions, though, so maybe it makes a difference, but it was always different during my career from what I’ve read online about countless whiteboard interviews.
In reality, though, it is not that good. Definitely, famous Javascript fatigue contributes to it. Such tasks very rarely provide you any boilerplate, usually leaving you with vague phrases like “use whatever you want”, but subtly impying that showing your own skills will be appreciated. Also, it is also kind of implied nowadays that you are proficient with modern javascript, so you will need babel – at least for modules, otherwise it brings too many unnecessary problems, and then webpack-dev-server solves a lot of problems, so it makes sense to use webpack and all that ecosystem.
Here comes the first fun part. What should you choose? Write your own? Use a popular boilerplate? If so, which? I like to understand what is going on, and from my experience building any serious webapp requires “ejecting” configuration, or a complete rewriting, so in test tasks I also start with a blank folder, quickly scaffolding basic configuration (e.g. babel, jsx, hmr, sass, css modules). Although it doesn’t take long for me to start a new project, at the same time refreshing basics, it takes some – and some parts, like HMR, are still pretty magical, despite all great efforts – so, sometimes you have to debug your configuration, not application (I definitely made this mistake couple of times, once spending around 25% of coding time on it, which prevented me from implementing a couple of things. Everything ended up very well, because it was an on-site interview, but it easily might not be so good). Sometimes just libraries were updated and now are incompatible, but you will spend at least some time trying to figure it out (and debugging experience is not that pleasing in the current state of tools in JS).
The main problems here are expectations. You don’t know precisely what they expect from you here (and as I mentioned before, they rarely give you any clues), so it might be considered a bad thing that you created it by yourself – same for the opposite case, when you take one! Also, because the application is going to be run, a building step is also part of your responsibilities, and there are a lot of possible optimisations, aside from NODE_ENV=production
– for instance, you can be asked, why you have not used cross-env, why did you not apply babel plugins for react in production mode, and why did you not optimise SVGs. The answer for all of these is extremely simple – time! These all are very important steps and should not be overlooked when deploying the real application, but polishing it can take too much time.
The second part is an actual application. I personally spend on any test task no more than one working day (so, around 8 hours, and sometimes more, if I tinkered too much around the configuration or some specific task sparked my curiousity in implementation details), and if I feel that I don’t have enough time, I start to write a more general code, omitting some details, putting TODOs and commentaries, explaining how I’d do it, having more time. And here comes the second problem – usually we have 1/2 screens application, where we can somehow interact with API, and display different results based on that. Again, the same problem as I mentioned before – it is usually pretty unclear, what exactly they mean by “maintainable application, easy to extend and to support”. Sometimes they expect you to bring redux/mobx with custom middleware, wrapping everything to the functions (so afterwards, when you will bring server-side-rendering, it will be easy to create new store per each request), and creating all sorts of wrappers for everything, which is possible to generalize. Seriously, once I got feedback, that in a product card component (which had image, title, subtitle, description and other stuff), instead of one component, it should be separated to ProductBanner
and ProductDescription
. And don’t get me wrong, it is a valid point, but it only might be helpful – unless they are somewhere reused, it will only add one level of indirection. And again, this might be helpful, especially if there is a lot of logic, tied to banner and description, but this was just the purest component ever!
So, this is called high-level thinking
. Anecdotally, this is what most companies are waiting for from you, so after some number I started to skew naturally to more over-engineered solutions, at some point it bit me back – once I started to list all possible solutions to state management, my interviewer stared at me with a question “why not just to use local state in the container, this is a small application!”. So, they liked the right approach for the right task (I realized only afterwards, that I tended to over-engineer everything, and many thanks to them for this insight!).
The last arguable thing is CSS. Ah, beautiful CSS – so many discussions are about CSS, and so many efforts were made recently to completely omit writing it in the usual way, that sometimes especially progressive companies frown at you when they see it. I personally use sass for these kinds of projects, in combination with css-modules. The reason I do that is because I still believe, in case you don’t need dynamic theming, and you are not going to use some sorts of shared packages of components, CSS (or any preprocessor) is a much superior way. First of all, editors have amazing support for it – emmet, snippets, linting, etc. Second of all, css modules eliminate problem of unique class names – basically, we just need to choose unique names in the scope of a single file (which, given modular structure of react application, is a pretty small scope), so this problem is also solved. And the last one – you don’t really have to do anything new, except adding loader in your webpack configuration and importing classnames in your components. All CSS-in-JS solutions will likely require more efforts from you, at least in the form of writing the actual CSS. So, while I agree that sometimes (especially for big projects with very specific needs) CSS-in-JS is a really good solution, instead of monkey-patching CSS tree on the fly; for such small tasks I believe that it is absolutely impossible to predict, whether CSS-in-JS is a good choice or not. Also, to close the discussion about it, I’d like to point out that these solutions are in active development right now, so you’ll likely spend a lot of effort on updating to new versions/new libraries.
But I digressed a little with my point of view here. The point is that some companies accepted it as a new dogma, that application without CSS-in-JS is bad by default, and point out on it as a drawback – and again, no information in the description says that it will be useful or needed in the future! Also, the same applies for the opposite case – in case you, with the best intentions bring CSS-in-JS, your reviewer might consider it a very bad choice! Of course, if you have a chance, you can discuss it with your reviewer and explain the rationale behind your choice and the possible problems, with weighing the risks and pains in a negative scenario, but again, people can evaluate it the wrong way!
So, I learned a couple of lessons here – I highly recommend starting with create-react-app, or something similar, but widely recognized and without a lot of additional stuff (so the reviewer won’t be lost in different features of boilerplate). It might not be ideal for you, but it will definitely be good enough for your test task. The second lesson is to actually ask people, but very thoroughly, about the level of details they want from you – e.g. do they want some sceleton of the application with solid abstractions from you, so it will be easy to add server-side rendering, reuse all possible components, add theming and so forth; or we are solving only this small piece of functionality, and no need to introduce complex solutions unless really needed. The last one, I think, is to provide some sort of rationale.md
, which will explain the reasoning behind the biggest choices, so the reviewer can understand your code better.
Also, from this rant you might get the impression that I was rejected after submitting my test tasks – in fact, the experience was the opposite, everyone wanted to proceed! But the whole point of this article is that often people think about rational choices as drawbacks, as I did not think about it; same applies for things which I made correctly just because. So, given enough unlucky choices, you have a chance to be rejected (the opposite case works too, for instance, people can assume that you made some deep choices based on exhausting prior art, but only because they agree with these choices) – and this is the saddest conclusion from it.
I was in the position of interviewer, trying different types of interviews, and I reviewed plenty of applications made by candidates, and I can say that nothing is as good as the actual programming of a relatively simple task. The size of it depends on the time available, so if you have the whole day, feel free to implement one of those “real-world” tasks, with constant pair-programming, trying to understand rationale behind candidate’s choices, and don’t forget to provide some boilerplate! If you have just one-two hours, then you’ll only have time to discuss high-level details, and you won’t be able to see how the candidate is “fluent” in actual coding, how good they are in basics, what their understanding is of data structures, language details (e.g. it is perfectly fine to ask about difference between arrow functions and function expression during the coding, but you’ll likely miss it discussing the arcitechture) – and I personally think they are too important to miss them, so then take something pretty simple, like publish-subscribe design pattern implementation – you can ask about classes, how to store handlers, how garbage collector in Javascript works, where it is used, etc.
Either way, actual programming with a candidate, sitting together (or on Skype, it still applies), trying to understand their reasoning and questioning them, will give you much more than trying to analyze their application code. So far, I believe, the best interview process for me was to get some short test, where they evaluate that they’d like to talk with me, some initial call, and then some coding together. I can ask them in that case questions about their rationale, evaluate their level, and also explain my process of thinking.