Written by Julie Zelenski
What to expect
Our exams are designed to evaluate your mastery of the course learning goals. The exam questions usually will ask you to complete coding tasks and answer thought questions. The exam questions will focus on material from the assignments, labs, lecture, and reading (this list is in order of decreasing emphasis). As an example, it is pretty much guaranteed the midterm will contain a question that requires you to manipulate raw memory using void* as assignments 2 and 3 did nothing but that. On the other hand, we are not going to test you on some obscure fact out of the reading that was never even mentioned in lecture, lab, or an assignment (such as how to tweak the floating point rounding mode). Answering the exam questions is typically going to require good comprehension of the foundational concepts and the ability to analyze and apply them in a given situation. We are evaluating that not only did you successfully complete the assignments and labs, but that you came away with a comprehension you can demonstrate. We generally do not ask superficial questions about terminology (e.g. "Define overflow") or details that can/should be looked up on demand ("How do you printf a number in hexadecimal number padded to 8 digits?").
We will give you the previous quarter's exam as a sample. It is reasonable to expect fairly formulaic exams from me in terms of question formats and topics. This is intentional-- given the limited time allocated to exams, this is not the time to shake things up with a wacky new type of question! Many questions will ask you to write short passages of C code. Other questions may ask you to analyze C code: to trace its behavior, to compare or contrast two versions, or identify and fix flaws. On the final, we will ask you to similarly analyze assembly instructions or translate between C/assembly. There may also be short-answer thought questions that ask you to reason about system behavior, coding tasks, numeric representation and limitations, performance tradeoffs, and the like.
When scoring answers, we will not be picky about minor syntax and oversights (missing braces around a block of clearly indented code, forgetting to declare an integer variable, we don't ask for #include's etc.), but there are subtleties that matter immensely, e.g., a int** is just one character different than int*, yet there is a world of difference between the two! The lion's share of the points for a problem will be reserved for grading the critical core of the code, and it is worth it to be very careful with the details on those portions. For example, if we ask you to write a function that sums the elements in a CVector, only a tiny fraction of the points will be allocated to the banal chores (initializing the counter, iterating over the right bounds). The bulk of the points will be gained or lost in the tricky details of whether you correctly handles the void*s. Being off-by-one in the loop is a tiny deduction, but being a level of indirection off or applying the wrong cast is going to be a huge hit. Be sure to focus your attention accordingly!
The kind of systems coding we do in CS107 leaves much room for error, spanning the gamut from careless oversights such as forgetting to return the function result to more serious omissions such as failing to allocate memory for a pointer. Nearly everyone loses a few points that could have been avoided with more time and chance to execute/test the code. However a solid first-pass approximation that shows the correct conceptual understanding of the key issues will earn the bulk of the points and, in our analysis, deserves to be strongly distinguished from code that exhibits significant conceptual issues and would require much trial-and-error with compiler/debugger/valgrind to turn into working code.
Before the exam: prep for success
- The long view. The mastery we are looking to assess with the exams isn't created by a night of cramming, it is built up throughout the quarter. Make it a priority to monitor your own progress and use our post-task self check upon completing each lab/assignment to identify holes or confusion to be shored up before moving on. In the ideal situation, you have established solid understanding of the material and the only exam preparation needed is practice applying your skills under exam-like conditions.
- Make reference/summary page as part of exam prep. The resources available to you in an exam setting may be limited one or two pages of your own prepared notes (printed and/or hand-written). We are not expecting you to memorize minutiae and the exam will not focus on those details. Reviewing the topics and determining what information is worth including on your reference page will remind you of where we've been and help you take stock of your comfort level with the course topics. Use this opportunity to identify any areas on which you feel weak and resolve dangling issues before heading into the exam. Even if the exam format allows use of larger pool of resources, preparing your own compact summary of the critical details is a useful study practice and you will appreciate having it at the ready during the exam.
- Recreate the environment. It is not a given that your real-world skills will translate to the exam setting without first becoming familiar with how the experience is different. The best practice is to solve problems without aid of compiler/debugger/Valgrind, in longhand using pencil and paper under a time limit, just as you will have to during the exam.
- Practice makes perfect. Take problems (lecture example, lab exercise, problem from the text, sample exam problem, or tasks of your own creation) and write out a solution under exam-like conditions. Review it to see how you did. This is much more valuable than a passive review of the problem and its solution where it is too easy to conclude "ah yes, I would have done that" only to find yourself adrift during the real exam when there is no provided solution to guide you!
- Get your questions answered. If there is a concept you're a bit fuzzy on, or you'd like to check your answer to lab exercise, or you wonder why a solution is written a particular way, get those questions answered before the exam. Reach out on the forum, come to office hours, or send an email and we're happy to help.
During the exam: in the heat of battle
- Spend your time wisely. Don't get stuck on any particular problem. You'll earn more points from reasonable efforts across on all problems than by perfecting one answer at the cost of leaving others blank. Take into account the point value assigned to each question when budgeting your time. Be conscious of which details are worth slowing down to get it right (e.g. allocating memory, getting the right typecast, correct pointer math, and so on will be weighted heavily in grading) and which tasks can do with just a quick first draft (e.g. an off-by-one loop index or forgetting to initialize a counter would usually be a small deduction). For short answer questions, keep in mind: the more you say, the more chance to say something wrong :-) A short answer problem is typically assigned just a few points and it isn't worth your time to spew everything tangentially related in hopes of salvaging of some shred of credit.
- Pay attention to specific instructions. A problem statement may include detailed constraints and hints such as "use only bitwise operators" or "ignore the case when the string is empty" or "must run in constant time". You may want to underline these instructions to be sure you don't overlook them. These constraints are not intended to make things difficult, typically we are trying to guide you in the direction of a straightforward and simple solution. If we provide a hint or suggestion (e.g. "you may find it helpful to ..." or "consider using ..." or "you can assume ...") then you are free to take or leave that advice. However, stronger statements (e.g. "your solution must ..." or "your solution cannot ...") must be respected. If you disregard these instructions, you are likely to lose points for not meeting the problem specification and/or for errors introduced when attempting a convoluted alternative.
- Ask a question rather than answer the wrong one. If you are uncertain about what a question is asking or find some part of the question ambiguous, it's worth your time to ask the course staff for a clarification so you can be sure you are solving the right problem before you start working on it.
- Document your assumptions. If you're unsure of a language/library detail that you can't easily lookup (e.g. whether strncat null terminates, does right-shift do arithmetic shift) make your best guess and write a note to grader to indicate what you're assuming and we'll do our best to grade accordingly from there. We reserve the right to make a deduction if your assumption is fundamentally wrong or significantly changes the assigned task, but with your assumptions made clear, we will at least be grading your answer within the same context in which you wrote it.
- Syntax mostly doesn't matter, except when it does :-) We are not picky about minor syntax/oversights (especially trivial details that would be flagged by compiler and easily fixed), but some small changes have enormous consequences-- we do not consider an extra * or missing & a trivial oversight and neither should you.
- Substance AND style. For coding questions, the bulk of points is typically won or lost by the correctness of the code. However, there may be deductions for code that is roundabout/awkward/inefficient when more appropriate alternatives exist. We will reward the simple, direct approach for its good design decisions and such code will likely have fewer correctness issues, so the choice of appropriate design can have a large impact. For example, we expect you to leverage appropriate features from the standard libraries; re-implementing that functionality wastes your valuable time and introduces opportunity for error.
After the exam: interpreting and responding to your exam outcome
For most students, the assignment and exam performance are fairly well correlated but sometimes they do diverge a bit. It's pretty rare to rock the exam if your assignments didn't go well, but there are students who go into the exam with solid assignment scores and yet emerge with a disappointing outcome. What might explain this and what can you do about it?
The untold hours you invest in your assignments are hopefully buffing out your coding chops to make you invincible. If you come into the exam with a rock-solid command of pointers and memory, you are absolutely ready to crush the exam. However, it is possible to have successfully completed the assignments without mastering the material. We see students that "debug code into existence", use trial and error experimentation, have deep dependence on tools, require continual access to the course staff, brute force through permutations of * & and typecasts, and so on. If this is you, you want to rethink your strategy. By dint of effort, you may end up with a working CMap and an excellent score, but if the methods don't lead to comprehension, you won't be able to reproduce these results and will have a very difficult time with exams. It's not that you aren't working hard-- far from it, you may be investing more time in the assignments than most, but you need to adjust your strategy to get more out of the time you are spending. The goal of an assignment is not to get the program to work, one way or another, it is about developing an understanding how and why it works, and being able to take that understanding and write similar code in any environment, including one that doesn't have the luxury of a compiler/debugger/Valgrind/TA/sanitycheck to tell your whether the code is correct or not.
You may be on top of most of the material, but some issues remain hazy. There is a difference between a "pretty good" understanding and having the concepts completely dialed. If you're spending a lot of exam time pawing through your materials and puzzling through the concepts, it suggests that there is an opportunity to fortify your foundation beforehand. Or consider: are the errors in your answers borne out of carelessness or indicative of more significant knowledge gaps? It takes courage to be honest with yourself, it's tempting to label every mistake as an oversight: "I just forgot to use char** here instead of char*". Being a level of indirection off may be syntactically small but is conceptually enormous. The fully-formed scar tissue from completely wrestling pointers into submission is a powerful reminder to never make those mistakes again. A more iffy understanding can crack under pressure. Use the exam results to find where your expertise is lacking so you know where you need to shore it up.
The exam results could mean you're not that comfortable in the testing environment. You crank out great assignments, you thoroughly understand the material, but in the exam you get flustered, mis-read the instructions, budget your time poorly, get sloppy, or all of the above. This effects may be overcome by more practice in test-like conditions (longhand, on paper, no tools, time limits). Working on your test-taking skills won't do much for your real-world competence (as it is already good), but it could improve your course grade and these skills may also be of use in interviews. If you're sure that it's just testing prowess that you're lacking, I recommend an approach in moderation -- review your exam, introspect on what happened, brainstorm tactics for next time, do some practice -- but don't let it make you crazy. If you're confident about what you are learning, can you make your peace with the nature of the artifact captured on an exam and let it go? Lucky for us, CS as a field is an incredible meritocracy. If you have good ideas, know how to solve problems, are willing to work hard, can meet deadlines, and communicate effectively, you'll have people begging you to work for them, regardless of the impressiveness of your pedigree/transcript. One of the best colleagues I've had was a dropout from Berkeley (a fact I never let him forget :-) He is a coding ninja, architectural genius, inspiring leader, and all-around nice guy who I'd follow anywhere. Did I mention he dropped out from Cal? I admire someone who couldn't even hack it at Cal? Sheesh!
Do exams matter? Do they measure anything important?
Crushing a CS107 exam is a sure sign that you are on top of your game. Potential employers are usually impressed by a good performance in CS107, as it bodes well for your ability to write solid code to solve problems.
Does proficiency in mentally evaluating code actually help with real-world coding projects? A subset of systems programming is programming for embedded systems (including mobile) where it's very often the case that debugging tools are non-existent. In these scenarios it's very helpful to be able to quickly figure out what a piece of code is doing without having to rely on gdb, valgrind, or even printf() since these tools may be missing entirely.
More broadly, the ability to analyze and reason about code in isolation is a valuable skill for any programmer. Investing the time to understand the code you write and strive for sincere mastery means you are on your way to being a coding rockstar. A former CS107 student put it this way: " Think 10 years down the road to when you're developing a product and your coworker offers you some code that you can't understand. Testing the code seems to show to work, but you have no idea why. You ask your coworker and he can't explain it either but insists that it works, so just use it. Do you have faith in that code? In your coworker?" You want to be the one that people clamor to work with, not just because you contribute solid code, but because you can clearly communicate how and why your code works!