This article was provided by Toptal. Thank you for supporting the partners who make SitePoint possible.
This guide offers a sampling of effective questions to help evaluate the breadth and depth of a candidate’s mastery of web development, including client-side, server-side, transport, and database related topics.
Before we embark on the journey of identifying world-class web developers, we must first define exactly what we mean by “web development”. Are we referring to the development of a web site? Or the development of a web service? Or the development of a complex web-based application? The truth is that web development is an extremely broad term that can legitimately encompass any or all of the above. Accordingly, this guide touches on multiple aspects of web development, some or all of which will be relevant to the specific context in which you may be looking to hire.
This guide intentionally focuses on the conceptual and architectural underpinnings of web development, rather than delving into the specifics of any specific web technologies (such as JavaScript, Ruby on Rails, PHP, and so on). Accordingly, this guide presents “technology-agnostic” web developer interview questions relating to:
Admittedly, doing justice to many of the topics herein would warrant posts of their own. Nonetheless, this guide strives to provide at least a meaningful overview of key issues and topics relating to web development in which a highly-experienced web developer can be expected to be well-versed.
Client-side (browser-based development)
Browser-based development presents numerous unique challenges to the developer, ranging from cross-browser anomalies, to sandbox limitations, to diverse performance characteristics across a wide array of client platforms and devices. Adept client-side web developers will be highly skilled at navigating these obstacles.
Q: Discuss at least three areas of focus on the client side to help reduce page load time.
Performance is central to a user’s experience with any application. Users have become increasingly intolerant of slow page load times and, knowing this, the large search engines actually quantify that time for each of the pages that their bots crawl.
Making the initial page request, getting the initial response contents of a page to the client, parsing that content and making subsequent requests for resource items (which in themselves are a round trip to and from the server), and then running any JavaScript can all contribute to page load time.
There are in fact numerous techniques to employ and areas to focus on to help improve page load time. Here are just a few examples:
- Avoid “render blocking”. When the browser’s parser engine encounters a tag in the HTML that accesses some external resource (such as a
<script>, <image>, <iframe>
, etc.) the engine pauses to wait for that resource to download fully to the client before continuing. Even worse, in the case of a<script>
tag with asrc
attribute, the browser will also execute that script before it moves on to process the rest of the page. This becomes particularly problematic when that script may subsequently make another request. Most browsers provide an async attribute that you can add to those tags to avoid this type of blocking. Another strategy is to identify resources that you can delay loading until they are actually needed. - Optimize images. Probably the largest resources you will load into a page are your images. Optimizing them for transport can prove to be very beneficial, as appropriate sizing can substantially reduce page load times. For example, don’t use a 600px by 400px image for a 120px by 80px thumbnail. It’s also helpful to pick the best compression format and to turn off certain format features.
- Minimize round-trip requests. The round-trip of retrieving resources (images, etc.) from the server can be a huge problem for page load times. Since the fewer requests a page makes, the faster it will be, one technique to help performance is to combine resources together into fewer requests where possible. For example, non-user generated images are a prime candidate here. A round-trip to the server just to retrieve a single 16×16 smiley face emoticon, for example, is extremely inefficient.
Q: Provide examples of cross-browser development challenges, including some tips on avoiding them or addressing them.
Few things in software development are as likely to cause premature hair loss as dealing with cross-browser issues and anomalies. Even if you’re just looking to support fairly current versions of popular browsers (such as IE, Firefox, and Chrome), you are still likely to encounter places where your code or layout simply doesn’t work (or at least, doesn’t work well) in one of the browsers.
The issues are sufficiently inconsistent and gnarly that no single guide can provide a foolproof recipe for avoiding them, but there are certain things you can do to help minimize, and protect yourself from, these issues.
Here are some techniques to help minimize CSS-related anomalies across multiple browsers:
- Browser reset stylesheets. The idea behind a browser reset stylesheet is to put you in control of the default style of all elements. For example, a very minimal starting point for a reset script might be as follows:
* { margin: 0; padding: 0; border: 0; }
- Having this snippet of CSS run before all other stylesheets enables you to override any element properties you desire, knowing that anything you don’t override will still be fairly consistent on all browsers.
- Provide CSS “fallbacks”. When using newer CSS property values (that may not yet be supported by all browsers), it’s good to provide sensible fallbacks for those browsers that don’t yet support the property value you want to use. Older browsers will skip the newer property values that they don’t understand and will simply use the older (fallback) properties that they recognize, whereas newer browsers will understand both the fallbacks and the newer properties and will use the newer ones in place of the older ones. (Note:For this to work properly in newer browsers, the fallbacks must appear before the new properties in your CSS so that newer browsers prefer the newer properties.)
- User browser-specific prefixes for CSS property values. Although the prior two techniques have the distinct advantage of not requiring any browser-specific definitions or code, front-end web development is such that browser-specific solutions to at least some degree are not always avoidable. For example, browsers will sometimes support their own, work-in-progress version of some W3C-standard CSS property value. In such circumstances, the use of browser-specific prefixes these values can be a useful and reasonable approach (commonly used prefixes include -webkit- for Chrome and Safari, -moz- for Firefox, and -ms- for Internet Explorer). For example, the following CSS snippet employs vendor prefixes to provide gradient support in browser versions that do not yet support the W3C standard gradient property (it’s somewhat ugly, but it’s reliable and it works):
/* Vendor prefixes */ background: -webkit-gradient(linear, 50% 100%, 50% 0%, color-stop(0%, #2c99ce), color-stop(76%, #459dcf), color-stop(95%, #74b9e0), color-stop(100%, #abe1fa)); background: -webkit-linear-gradient(bottom, #2c99ce, #459dcf 76%, #74b9e0 95%, #abe1fa); background: -moz-linear-gradient(bottom, #2c99ce, #459dcf 76%, #74b9e0 95%, #abe1fa); background: -o-linear-gradient(bottom, #2c99ce, #459dcf 76%, #74b9e0 95%, #abe1fa); /* W3C Standard */ background: linear-gradient(bottom, #2c99ce, #459dcf 76%, #74b9e0 95%, #abe1fa);
- And if all else fails in IE… IE can be particularly ornery so, if more generic techniques like the ones above still don’t achieve the desired result in IE, you may be forced to use IE conditional comments. These hacks were voluntarily added by Microsoft to its browsers to help address IE’s inconsistencies. Since they come in the form of HTML comments, other browsers will simply ignore them:
<!--[if IE 8]> <link href="/ie8hacks.css" rel="stylesheet" type="text/css" /> <![endif]--> <!--Or, for IE5 through IE7--> <!--[if (gt IE 5)&(lte IE 8)]> <p>Dude, looks like it's time to update your browser!</p> <![endif]-->
On the JavaScript side of the world, there are also ways to deal with cross-browser idiosyncrasies.
Historically, the most common way this was done was through browser detection (a.k.a., “browser sniffing”) to detect what browser version the client is running . While this can work, it has a number of drawbacks. Most notably, it requires the developer to hardcode behavior based on the known/presumed capabilities of the detected browser and version. And it’s also worth noting that browser identity can be spoofed in many modern browsers. It is therefore not surprising that even jQuery advises against using its $.browser
property.
The technique that jQuery advocates, and which is generally the recommended approach these days, is known as feature detection. Rather than relying on potentially flawed a priori knowledge of a browser’s capabilities, feature detection uses the more robust approach of determining dynamically what is and what is not supported by the client’s browser. Here’s an example:
// this function adds an event listener reliably using feature detection
function myAddEventListener(event, listener) {
if (window.addEventListener) {
// Browser supports "addEventListener"
window.addEventListener(event, listener, false);
} else if(window.attachEvent) {
// Browser supports "attachEvent"
window.attachEvent("on" + event, listener);
}
}
/* ... */
myAddEventListener("load", myListener);
And of course, whatever techniques you employ, no matter how bulletproof you believe they are, be sure to test thoroughly in all browsers that you intend to support.
Q: Compare and contrast SASS, LESS and CSS, including advantages and disadvantages of each.
First, to define our terms:
- CSS: Cascading Style Sheets
- SASS: Syntactically Awesome Style Sheets
- LESS Leaner Cascading Style Sheets
CSS refers to a set of static instructions that all W3C-compliant browsers (and others for that matter) understand. That static nature makes CSS simple. For each element that you want to customize, you need to have some combination of style rules coded that together will shape how it will look. That fact limits the opportunity for code reuse (throwing DRY out the window).
Both SASS and LESS are derivative languages of CSS, but where they differ from CSS is that they make use of preprocessing to parse respective instructions into valid CSS. This preprocessing provides both languages with mechanisms for variables, inheritance, mixins, nested rules, logical operators and even loops. These are tools to address some of the major inconveniences of writing CSS, without having to dive into completely different languages such as PHP or JavaScript, since the syntax is very similar to CSS in both cases.
User interfaces and websites are becoming increasingly complex, as is landscape of browser layout engines (webkit, gecko, etc.). The ability to handle some of this complexity with dynamic rules can prove to be a big time saver in managing the look and feel of an application.
A familiar example of the potential benefit of using preprocessed languages is in color management. Changing a site’s color palette in normal CSS can be arduous. Several files will normally need to be combed for the references to a specific color to be changed, and for each reference found, the question “do we want to change this one?” must be asked. On the other hand, with SASS or LESS, one could simply modify a single color variable and all references to that color variable will then reflect that change.
Server-side
The server is normally the workhorse of an application. It authenticates requests, processes data, applies business logic, and builds responses. Servers, and services, that are well architected and designed can make a significant difference in the performance and usability of your system.
Q: Describe some approaches, techniques, and considerations to be considered for server-side caching.
With individual servers often serving the needs of hundreds if not thousands of clients, it is reasonably likely that they’ll receive (and need to respond to) multiple identical requests. Moreover, even requests that are not identical may still overlap in terms of the responses they need to provide. Accordingly, server-side caching can help improve performance by avoiding wasted use of server-side resources to perform the same operations redundantly.
Here are a few common key considerations to take into account when establishing a caching strategy:
- Cache size. Although RAM is generally cheap these days, consideration needs to be given to capacity (i.e., the amount of memory available) when setting your cache size. Allocating too high a percentage of total memory to the cache can have an overall impact on performance that is actually more detrimental than beneficial.
- Expiration of cache entries. Setting appropriate expiry on your cache elements and invalidating key value stores when your data changes can help with capacity as well.
- Cache contents. Of course, deciding what to cache is perhaps the biggest challenge when designing a caching strategy. You might be inclined to cache data that is expensive to generate or compute, but depending how rarely it is used, this could be a waste of precious cache space.
- Granularity of cache entries. Data objects are often comprised of multiple “sub-objects”. Should the object be stored in the cache including all its “sub-objects” or should be cached separately (or perhaps even not at all)? There’s no one-size-fits-all answer here; it all depends on the structure of your database and the nature and frequency of the client queries against that data store.
Q: Explain what sessions are and provide a general description of how they are tracked on the server side.
A session is a mechanism for persisting user data across multiple related requests. The process takes an identifying key as part of the incoming request, which in browser-based interactions typically come in the form of a client cookie.
An application will instantiate a session object into memory. It can then add data to that object about how the current user is interacting with the application. When the application completes its execution cycle, it will close the session, and as such, the data gets serialized and written to some tier that can store the data for use in the next request for that user. The technology need only be able fulfill that storage requirement and so it can be a database, file, or some caching technology.
Transport
All too often, a web developer will have solid skills in a specific technology, but will fall short in their understanding of “how the pieces fit together”. In contrast, a top web developer will have a solid grasp on how requests are made, structured, and responded to.
Q: What is REST and what is a RESTful Web Service? Describe its characteristics.
REST (REpresentational State Transfer) is a client/server architecture in which data and functionality are considered resources and are accessed using Uniform Resource Identifiers (URIs). The resources are acted upon by using a set of simple, well-defined operations. REST is designed to use a stateless communication protocol, typically HTTP.
As discussed in the Java EE Tutorial, the following principles encourage RESTful applications to be simple, lightweight, and fast:
- Resource identification through URI: A RESTful web service exposes a set of resources that identify the targets of the interaction with its clients. Resources are identified by URIs, which provide a global addressing space for resource and service discovery.
- Uniform interface: Resources are manipulated via a specific set request types, the most common of which are:
- GET: Retrieves data from the server (should only retrieve data and should have no other effect).
- POST: Sends data to the server for a new entity. It is often used when uploading a file or submitting a completed web form.
- PUT: Similar to POST, but used to update an existing entity.
- DELETE: Remove data from the server.
- Self-descriptive messages: Resources are decoupled from their representation so that their content can be accessed in a variety of formats. Metadata about the resource is available and used, for example, to control caching, detect transmission errors, negotiate the appropriate representation format, and perform authentication or access control.
- Stateful interactions through hyperlinks: Every interaction with a resource is stateless; that is, request messages are self-contained. Stateful interactions are based on the concept of explicit state transfer. Several techniques exist to exchange state, such as URI rewriting, cookies, and hidden form fields. State can be embedded in response messages to point to valid future states of the interaction.
If measured by the number of web services that use it, the RESTful Web Service (RWS) has emerged in recent years alone as the clear favorite over the previously-championed SOAP protocol. RWS’ relative ease of use is largely to be credited. In fact, REST has had such a large impact on the web that it has mostly displaced SOAP-based and WSDL-based interface design because it’s a considerably simpler style to use.
Q: Give a brief description of each of the following HTTP Request Methods: TRACE, OPTIONS, CONNECT, and PATCH.
Beyond the four standard RESTful Web Service operations already discussed, there are four additional methods that more advanced web developers may be familiar with:
- TRACE: Provides a means to test what a machine along the network path receives when a request is made. As such, it simply returns what was sent.
- OPTIONS: Allows a client to request information about the request methods supported by a service (or for the server where the service resides by using a * wildcard in the URI). The relevant response header is Allow and it simply lists the supported methods.
- HEAD: Same as a GET method for a resource, but returns only the response headers (i.e., with no entity-body).
- CONNECT: Primarily used to establish a network connection to a resource (usually via some proxy that can be requested to forward an HTTP request as TCP and maintain the connection). Once established, the response sends a 200 status code and a “Connection Established” message.
Some common techniques for HTTP server push include:
- WebSocket API. WebSockets make it possible to open an interactive communication session between a client browser and a server. With this API, the client can send messages to a server and receive event-driven responses without having to poll the server for a reply.
- Pushlets. Pushlets are based on an open Source HTTP-based publish/subscribe framework that is AJAX-enabled. The approach takes advantage of persistent HTTP connections, leaving the response perpetually open (i.e., the server never terminates the response), effectively fooling the browser to remain in “loading” mode after the initial page load could be considered complete. The server then periodically sends snippets of JavaScript to update the content of the page, thereby achieving push capability. By using this technique, the client doesn’t need Java applets or other plug-ins in order to keep an open connection to the server. The client is automatically notified about new events, pushed by the server. (One serious drawback of this method, however, is the lack of control the server has over the browser timing out; a page refresh is always necessary if a timeout occurs on the browser end.)
- Long polling. Long polling is really just a variation of the traditional polling technique, but it allows emulating a push mechanism under circumstances where a real push is not possible, such as sites with security policies that require rejection of incoming HTTP Requests. With long polling, the client requests information from the server exactly as in normal polling, except it polls at a much slower frequency. If the server does not have any information available for the client when the poll is received, instead of sending an empty response, the server holds the request open and waits for response information to become available. Once it does, the server immediately sends a response to the client, completing the open request. The usual response latency (the time between when the information first becomes available and the next client request) otherwise associated with polling clients is thereby eliminated.
- Flash XMLSocket relays. This technique, used by various chat applications, makes use of the XMLSocket object in a single-pixel Adobe Flash movie. Under the control of JavaScript, the client establishes a TCP connection to a unidirectional relay on the server. The relay server does not read anything from this socket; instead it immediately sends the client a unique identifier. The client then makes an HTTP request to the web server, including this identifier. The web application can then push messages addressed to the client to a local interface of the relay server, which relays them over the Flash socket.
Database
As mentioned previously, there are many pieces to the web development puzzle, and not every qualified web developer will necessarily be skilled at all of them. Accordingly, many web developers may focus predominantly on the client side and will therefore have little expertise in the database domain. However, efficient database design, access, and manipulation is fairly central to the performance of most web-based systems and, as such, a web developer with strong database expertise can be extremely valuable to your project.
Q: What does it mean to normalize a database? How does one go about doing it? Describe a potential consequence of database normalization.
Database normalization is the process of organizing the fields and tables of a relational database to minimize redundancy. Normalization usually involves dividing large tables into smaller (and less redundant) tables and defining relationships between them. The objective is to isolate data so that additions, deletions, and modifications of a field can be made in just one table and then propagated through the rest of the database using the defined relationships.
A good way to begin to normalize your database is to first assess it against the normal forms of relational database theory. These normal forms provide criteria for determining a table’s degree of immunity against logical inconsistencies and anomalies. A database that satisfies the requirements of a certain level, must also satisfy all the previous levels. The first three of these levels are the ones most commonly used and are as follows:
- First Normal Form (1NF) – Each field should represent one and only one value per entity; i.e., there should only be a single value per field (atomicity). For example, a Person may have two phone numbers. If an application stores both of them in the same field (e.g., the phone_number column of a table), that table does not meet the criteria for 1NF.
- Second Normal Form (2NF) – No partial dependencies of columns on the primary key are allowed. For example, if a table has a multi-column primary key, none of the other columns can be dependent on only a susbet of the columns that comprise the primary key.
- Third Normal Form (3NF) – All non-primary-key fields must be dependent on the primary key. If fields that are not part of the primary index are dependent on other non-primary fields for their values, then that table is not in the third normal form. For example, a table that includes a total column that is a sum of other fields in that row does not meet the criteria for 3NF.
Assessing the compliance of your database with these normal forms can help identify and eliminate redundancies in your data model that can make it more efficient (and incidentally, this can also be beneficial in terms of determining the granularity of the entries in your cache). But as with anything in life, normalization does have its costs. The very act of spreading your data across several tables will likely require an increased number of table joins in your queries, which can also complicate your queries and possibly even your code. It also eliminates some potentially beneficial indexing scenarios, since data from multiple tables cannot be indexed. Retrieval can become expensive as well, and much more complicated when trying to filter or sort data in one table based on the values in another.
That said, the best approach is usually a mix of these (and other architectural patterns) that suit the specific requirements of your application.
Q: Describe a Hash index and a BTree Index. What are some of their relative advantages and disadvantages?
In any tree-based index, records are stored in locations called leaves. The starting point is called the root. The maximum number of children per node is called the order of the tree. The maximum number of access operations required to reach the desired leaf (data stored on the leaf) is called the depth (level). The general tree structure orders these ranges “left-to-right”. A key in a node can lead to a node where all the keys are less that its value to its left, or to a node where all the keys are greater than its value to its right.
The B-tree is a generalization of a binary search tree in that a node in a B-Tree is allowed to have more than two children. B-Tree indices help find information quickly by successively narrowing down data by assessing ranges of values stored in the node keys (nodes in the index contain keys and pointers to their child nodes). A B-Tree search starts at the root node and compares the range of keys in each child node against the key value being sought. When it finds the node whose range contains the desired key value, that node is selected and then its child nodes are assessed. This occurs until the process reaches the leaf pages where there are pointers to the actual data.
In a Hash Index, the values of the indexed column are run through a hash function to generate a location identifier for each key in the hash table. The table is divided into “buckets” and, depending on the technology employed, these buckets either contain the data values themselves or pointers to those values. Hash indexes are fast because the exact location of the bucket is known and the hash keys are ordered sequentially. Once a key location is found, the pre-hashed value (the one we are searching for) is compared to the one we’ve just found in the index to make sure it’s valid.
Hash indices work well, but only for purposes of equality comparisons. As such, hash indices can’t support queries of the form SELECT * FROM table WHERE key LIKE “valu% or SELECT * FROM table WHERE key < value
. In contrast, B-Tree indices allow for much more flexibility in terms of what you can search for. Partial values and ranges of values are easily handled, all with roughly the same speed in a non-join search. In scenarios where either type of index will suite your purposes, there is no consistently “best” choice, since there are scenarios where hash indices outperform B-trees, and vice versa. It largely depends on the nature and structure of your data.
Q: Briefly describe and compare relational, document, and graph databases.
Relational databases organize data into 2-dimensional tables and supports the notion of linking their contents based on known relationships. These relationships facilitate and simplify the integration and retrieval of data from multiple tables with a single query.
While relational databases were a significant advance when they were originally introduced in the 1970s, new ways of storing data have since emerged that allow data to be grouped together more naturally and logically, and that loosen the restrictions on database schema. One of the most popular ways of storing data is a document database model, where each record and its associated data is thought of as a “document”. In a document database, everything related to a database object is encapsulated together. Storing data in this way has the following advantages:
- Documents are independent units which makes performance better (related data is read contiguously off disk) and makes it easier to distribute data across multiple servers while preserving its locality.
- Application logic is easier to write. You don’t have to translate between objects in your application and SQL queries, you can just turn the object model directly into a document.
- Unstructured data can be stored easily, since a document contains whatever keys and values the application logic requires. In addition, costly migrations are avoided since the database does not need to know its information schema in advance.
Document databases generally have powerful query engines and indexing features that make it easy and fast to execute many different optimized queries. The strength of a document database’s query language is an important differentiator.
Another relatively recent advance in database modeling came about with the advent of graph databases. Graph databases, based on graph theory, use graph structures with nodes, edges, and properties to represent and store data. In a graph database, every element contains a direct pointer to its adjacent elements and no index lookups are necessary.
Compared with relational databases, graph databases are often faster for associative datasets, and map more directly to the structure of object oriented applications. They can also scale more naturally to large datasets as they do not typically require expensive join operations. As they depend less on a rigid schema, they are more suitable to manage ad-hoc and changing data with evolving schemas. Graph databases are an especially powerful tool for graph-like queries (e.g., computing the shortest path between two nodes in the graph). Conversely, relational databases are typically faster at performing the same operation on large numbers of data elements.
Wrap-up
It is important to bear in mind that the questions provided herein are intended merely as a guide. Not every “A” candidate worth hiring will be able to properly answer them all, nor does answering them all guarantee an “A” candidate. At the end of the day, hiring remains as much of an art as it does a science.
The questions and answers presented in this guide can be highly valuable in your quest for solid web developers, but are meant to augment an overall effective recruiting strategy, such as described in this post, In Search of the Elite Few.
![Demir Selmanovic](https://uploads.sitepoint.com/wp-content/uploads/2015/10/1445465985demir_selmanovic_bw-36e114442d71fc7190c4e9797475211d-96x96.jpg)
Demir is a developer and project manager with over 15 years of professional experience in a wide range of software development roles. With a degree in Mathematics from the University of Sarajevo Faculty of Math and Science, he excels as a solo developer, team member, team leader, and manager of multiple distributed teams. At Toptal, Demir is in charge of the Toptal Engineering Blog as well as the extensive technical and hiring publications Toptal produces for the development community.