Skip to content

Instantly share code, notes, and snippets.

@lastmjs
Last active August 25, 2019 18:11
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lastmjs/b0a45f89961e9ed9e4e8efd65a08555d to your computer and use it in GitHub Desktop.
Save lastmjs/b0a45f89961e9ed9e4e8efd65a08555d to your computer and use it in GitHub Desktop.
Full Stack Mentor - Candidate Assessment

JavaScript

Hey Student,

Oh dear, this is a tricky one. It's definitely messed me up in the past. What's happening here is an issue of scoping and closures. A few things to point you in the right direction:

  1. Study block scoping versus function scoping in JavaScript
  2. Study JavaScript's variable declarators, var, let, and const, and understand the differences between their uses
  3. Study up a bit on closures in JavaScript
  4. With your new knowledge, try to make btnNum block scoped instead of function scoped

After all that, you should have your problem solved. Let me know if you have any more questions after you've studied up, and we'll get this all sorted out. Good luck!

- Mentor

Rails

Hey Student,

All of these different ways of creating associations are just that, ways of creating associations. First, make sure you understand associations very well at a high level. Do you know what a one-to-one association is? One-to-many? Many-to-many? If you don't feel like your understanding of those is good enough, take some time to study. Once you feel good about those three types of associations, proceed to the next paragraph.

Perfect, now that you understand what we're trying to do at a high level, let's get into the nitty gritty. through is a way of implementing a one-to-one or a many-to-many association. has_many alone is used to implement a one-to-many association. There are multiple ways of implementing associations between entities or tables in a database. through will always create the association through a third model, a model that is not on either side of your one-to-one or many-to-many relationship. The relationship is literally made through that third model. This takes some time to think through, and is a bit confusing at first, but stick with it until you understand.

has_one :through

Take a look at this documentation, especially the diagram. Do you see how there are three models? Do you see how two of the models are associated through the third model? The Supplier has one Account. It also has one AccountHistory. The Account one-to-one relationship is made directly. Literally there is just a foreign key placed on each Account record going back to the Supplier. The AccountHistory one-to-one relationship is not direct. There is no foreign key back to a Supplier in the AccountHistory model. There is only a foreign key back to the Account model. But because the Account model has a foreign key back to the Supplier model, we are able to derive the one-to-one relationship from the Supplier to the AccountHistory model. Thus we have created a one-to-one association, from Supplier to AccountHistory, through the Account model.

has_many :through

Take a look at this documentation, especially the diagram. This is similar to the has_one :through in that there are three models. Do you see how two of the models are associated through the third model? The Physician and the Patient have no foreign key to each other or to the third model, which is the Appointment model. The Appointment model has a foreign key back to Physician and Patient in each row. This effectively links Physician and Patient together in a many-to-many relationship.

Why would you do these things? Read this. Also, there is another reason mentioned elsewhere in that documentation, and potentially more reasons we haven't thought of. I'll leave this to you. With your new knowledge, why do you think introducing a third model with through could be useful?

- Mentor

SQL

Hey Student,

Yes, SQL injection is one of those classic dangers with servers that take user input and create SQL queries. The key here is user input. Think of all of the ways that users can input their own information on the client and have it reach your server. These will include any forms that you have, any text or number inputs, textareas, image uploads, etc. If the user is allowed to input ANYTHING, and that anything eventually makes its way to your server, then you need to be careful. SQL injection is possible if any of that user input becomes part of a SQL query that is executed on your server. Essentially, SQL injection is any user manipulation of your SQL queries. If taking user input and combining it into a query allows the user to control the query in any way, then the user has "injected" their own SQL into the query. Let's look at an example, which you can research in more detail here:

// This is a function in our server code.
// We'll assume that the user has input their username on the client, hit submit, 
// and now we're in the middle of processing that request.
// We store the username in the userName variable

function getUser(userName) {
  const query = `SELECT * from users WHERE name = '${userName}';`;
  
  // Now we will execute the query against our database and get the results.
  // We will then return the results.
}

Can you see the issue above? If we haven't done anything to check what userName contains, it could really contain anything. It could contain a username, it could be empty, it could be a number, or it could be a malicious piece of a SQL query. Imagine if userName were equal to ' OR '1'='1. Our query would look like this:

SELECT * from users WHERE name = '' OR '1'='1';

Can you see the problem here? We wrote the query intending to return only one user, the user whose name is equal to the username entered by the user on the client. But if this query executes, it will return all users, because '1'='1' is always true. This is not what we intended, and thus we have given unforeseen control to the user.

It could get worse. What if userName were equal to '; DROP TABLE users; SELECT * from userinfo WHERE 't' = 't? The query would then look like this:

SELECT * from users WHERE name = ''; DROP TABLE users; SELECT * from userinfo WHERE 't' = 't';

Can you see what this would do? The users table would be dropped, meaning everything in it would be deleted. That's bad. So as you can see, if we allow unrestricted user input into our SQL queries, bad things can happen. That is SQL injection.

Now how can we prevent this? First off, remember that user data entered on the client can contain ANYTHING. It must be properly sanitized or otherwise made innocuous before being put into a SQL query. I believe most languages or libraries will have some built-in way of rendering user input harmless for you. If your language/library has any notion of a prepared statement, then use that. Essentially prepared statements are created from functions or classes that handle the sanitization/innocuation for you, ensuring that your queries are built in a way that is relatively safe. Basically, that's how you do it, you sanitize your input. There are other things that you can do to mitigate risks from SQL injection attacks, like not using a SQL database (using a document or non-relational database, or using a different query language), ensuring database permissions are setup appropriately, and others. But I would start with looking into sanitizing your inputs, either on your own or with prepared statements.

- Mentor

React/jQuery

Hey Student,

The distinction between a library and a framework is really not black and white. There is no consensus on it, and people still debate it, including myself. In my opinion, it's really a spectrum of abstraction, and how much control you give up versus how much you keep for yourself. The more control you give up, the less flexible you become. But by giving up control, you also allow a lot to be taken care of for you.

Frameworks

Frameworks lie on the side of more abstraction and taking more control away from the user. Think about it this way, a framework calls your code. You put your code into a framework, the framework calls you, and that's that. You're at the mercy of what the framework allows. This may work well for a variety of use cases, but the moment you need to do something that the framework does not allow, you're going to run into trouble. It could be extremely difficult to figure out how to work around the framework. That's the nature of abstraction. With more abstraction, you can get a few pre-determined use cases to work very well, but any unforeseen use cases may not be handled elegantly or at all.

Libraries

Libraries lie on the side of less abstraction and taking less control away from the user. Your code calls the library. Your code becomes the framework within which the library operates. You have a lot of flexibility now. Whenever you need some functionality from the library, you can use it exactly where you need it. This increased flexibility gives you a lot of responsibility for structuring your code properly, and could lead to poorly managed spaghetti code if you don't know what you're doing.

What we really want to do is balance flexibility and rigidity, more abstraction and less abstraction. I believe there is a happy medium between a framework and a library in their most extreme definitions, and we want to find and use technologies that lie within this happy medium. Keep that in mind as you ponder over jQuery and React, and where they lie within the spectrum.

jQuery

With our new knowledge of the difference between frameworks and libraries, let's try to classify jQuery. While answering the following questions, think about jQuery's intended purpose, what it is really trying to do for you:

  • Does jQuery lie more toward a lot of abstraction or a little abstraction?
  • Does jQuery take control away or give you control?
  • Does jQuery call your code, or does your code call jQuery?
  • Can you decide where and when to use jQuery?
  • Do you find it easy for your code to become unmanageable while using jQuery?

Think about the answers to these questions and then let's discuss them.

React

Let's do the same analysis for React. Ask yourself the same jQuery questions but for React. Come to your own conclusions and then let's discuss.

Once you've come to your own conclusions, let's get together and reason through this. I definitely have my own opinions, so we can compare and contrast and hopefully come to a sound understanding. Good luck.

- Mentor

Algorithms

Hey Student,

Big O notation is a huge topic, worthy of many hours of study. But, there are a few basic principles that are simple to understand and that will be very helpful in a variety of situations.

The first thing to understand is that Big O is always meant to describe the worst-case scenario of the performance of an algorithm. That makes things a little bit easier, we just have to think of the worst thing that could possibly happen. The next thing to understand is that Big O describes growth. This growth is referring to either execution time or the amount of memory used. These are usually referred to respectively as time complexity and space complexity.

What do we mean by growth? When analyzing an algorithm's Big O, we must first decide which type of complexity we want to measure. Then we decide what input we are measuring against. We usually refer to this input as n. That's why you see complexities such as O(n), O(n^2), O(log(n)), etc. Like I said before, the n refers to the input that we are measuring against. Growth is how much our execution time or the amount of memory grows as n grows. Let's do an example:

Time complexity

Here's our algorithm:

function sumUpToN(n) {
  let sum = 0;
  
  for (let i=0; i <= n; i++) {
    sum = sum + i;
  }
  
  return sum;
}

This algorithm adds all of the numbers from 0 to n inclusive, and returns the sum. What is its time complexity? We need to think about how long it will take for this algorithm to complete as our input n grows larger and larger. And what do we mean by time? Usually the number of computational steps it takes to complete an algorithm is directly correlated with how much time it will take an algorithm to complete. So, as n grows, we analyze the number of steps it takes to complete this summing algorithm.

Let's look at the first piece of the algorithm:

  let sum = 0;

Does this piece grow in computational steps as n grows? No, it is constant. It does not matter what n is, this piece will always require the same amount of computational steps. We'll assign this piece of the algorithm O(1), because it is constant and does not grow as n grows.

Let's look at the next step:

  for (let i=0; i <= n; i++) {
    sum = sum + i;
  }

Does this step grow as n grows? Yes it does! Because we are using n in our conditional check in the for loop, the for loop will "grow" and take longer and longer to complete as n gets larger. We'll assign this piece a complexity of O(n). Does that makes sense? Think about it long and hard until it does. Feel free to ask some questions, but get that point well understood.

And finally:

  return sum;

Does this piece grow as n grows? No. We'll assign this piece of the algorithm O(1). It should be obvious why, I'll leave it to you to convince yourself.

Now that we've found the Big O complexity of each piece of the algorithm, let's analyze the algorithm as a whole. We've got three pieces to the algorithm that we've analyzed individually, O(1), O(n), and O(1). Remember, we are looking for the worst case scenario. All we need to do now is look at which complexity is the worst. Which one grows more as n grows? O(n), of course! O(1) doesn't grow at all, and so we don't even need to consider it in our final complexity score. The final Big O complexity of this algorithm is O(n).

Space complexity

Alright, can you do the same analysis using the same algorithm, but for space complexity? This time you won't be analyzing the number of computational steps per piece of the algorithm, but the amount of memory. I'll leave this as an exercise to you. Let's come together once you've taken a shot at it and see what you come up with.

Once you've done a simple example of time and space complexity, can you come up with an example of an algorithm that has a time or space complexity of O(n^2)? How about O(log(n))? Come up with some examples and let's discuss them. Good luck out there.

- Mentor

CSS

Hey Student,

CSS is so tricky, but we can get through this. Always remember your first principles. If we fail to understand the primitives, the basics, then we will most likely fail to understand the root causes of our problems over and over again. You'll see that the solution to your problem is quite simple once understood, and it really goes back to first principles. And so, first, a few things to study up on in case you're weak in them:

  • Block-level elements
  • Inline elements
  • display: inline-block
  • float

It looks like one of our main issues is that the section with the class pricing doesn't have the height that we're expecting it to have. We're expecting the height of that section to encompass the height of its children divs, but it's not. If the height did encompass those children, we would see the background-color: tomato property being applied correctly, encompassing all of those children in a red background.

I don't want to give it away entirely, because I think you can figure this out and deepen your understanding. I'll give you seem hints though. The float is your issue. Think about what you're trying to accomplish with the float. Is there another way to accomplish that? Floating removes your elements from the calculations of the space inside of their parents. You need to figure out how to get those children divs to occupy that space. Study up on those four key points above, and you should be able to get this. Let me know what questions you have after studying up. Good luck!

- Mentor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment