Just another Duke WordPress Sites site

Author: Eric Young

4 Things I Learned as a Programming Instructor this Summer

As courses are coming to an end, I want to reflect on 4 things that I learned as a programming instructor this summer. 

1. Every Student Deserves the Chance to Explore, Advance, and Succeed in Computer Science

10 years ago, my first Computer Science teacher told me that I wasn’t cut out for the subject, and because of that, I stopped programming altogether. At Duke, I frequently felt overwhelmed by the imposter syndrome how could I compete with my coding prodigy classmates when I just finally gathered the courage to pick up CS again in college? As my passion for CS and education grew over time, I went into this summer with a deeply personal goal: Become that inspiring first instructor who encourages students to further explore the ever-fascinating field. To me, every student deserves the chance to explore, advance, and succeed in CS, no matter their age or background. Learning CS undoubtedly helps students acquire valuable technical skills in our rapidly changing world. But more importantly, if taught right, the subject teaches problem solving, encourages creativity, and inspires innovation. To make sure my students are not discouraged while learning CS, I have done the following throughout my courses to personalize each student’s learning path: 

  • Varying Difficulties for Assignments:

For each assignment, I release two versions: one for those who feel less comfortable and the other for those who feel more comfortable. Students can submit either or both, in which I count the submission with the highest score. That way, struggling students are not overwhelmed and excelling students can still feel challenged by the problem sets. 

  • Small Group Office Hours and Individual Meetings:

As a huge proponent of creating meaningful relationships with students, I enjoyed holding office hours and individual meetings that allowed me to get to know my students better. I also thought it was helpful to be able to walk through problem sets and projects step by step with students. When I first started learning CS, I often felt lost when given a difficult problem and simply told to solve it. In office hours, I am able to show students how I approach a problem from scratch:

  1. Brainstorm program design through charts and graphs 
  2. Apply decomposition and abstraction
  3. Test functions incrementally
  4. Debug using print statements, test cases, and stack traces
  5. Evaluate program design and efficiency

I believe merely introducing these methodologies is not enough. By going through a practical example and voicing my thoughts out as I code out sample solutions, students can apply effective strategies that I use when they feel stuck in their own development cycles. 

2. Analogies are Amazing 

When the vocabulary is technical, students are less likely to trust that they know the answer, even when they actually do. Putting technical ideas into familiar, real-life settings effectively reduces the fear associated with classroom discussion. Thus, when I’m introducing new CS concepts, I make sure to draw on plenty of analogies to simplify abstract concepts and encourage further discussion. Since students are more likely to personally connect with these everyday examples, I find that they have a much easier time comprehending and implementing code later on. 

Here are some examples of how I use real life analogies to explain CS concepts: 

3. Creativity > Memorization

When I first learned CS, I spent most of the time memorizing templates in order to pass tests. Looking back, I didn’t understand the essence of CS. Having syntax and library functions memorized doesn’t automatically make you a great programmer. Great programmers are problem solvers and innovators, and it doesn’t really matter which programming language they use , because languages are just tools after all. With an innovative mindset, we can do so much more identify worthy problems, come up with transformative ideas, and implement tangible solutions. To be truly impactful, we must be innovative first. Thus, I put a lot of emphasis in fostering student creativity when designing my courses. 

  • Open-Ended Homework and Projects:

I structure the homework assignments that I release into two parts: 1. Guided subproblems that have step by step directions for students to follow. 2. Open-ended features that allow students to inject their creativity. For example, for a Java graphics assignment, the open-ended part was to create a mascot that will be optionally entered into a class-wide graphics contest. For a video game project, the open-ended part was to allow students to add background music, sound effects, and additional levels. While I provide some hints for general directions, the specific ideas and their implementations are up to each student to explore. 

4. Learning Never Stops

In my opinion, the best way to learn is by teaching. Here are some ways that I learned as I taught:

  • Being Challenged by the Unfamiliar: 

At times I was challenged to teach a topic that I don’t feel as comfortable teaching. Since it is crucial for a teacher to be able to demonstrate mastery of the topic and answer student questions eloquently, this responsibility motivated me to spend substantial time reviewing before I teach a topic for the first time. For example, when teaching memory, file manipulation, and image filtering, I thought my most familiar languages, Python and Java, abstracted away too many important low level details that illustrated how computer memory works underneath the hood. Therefore, I chose to teach this segment of the course in C, a language that I wasn’t too familiar with. To teach and come up with assignments in C, I spent a tremendous amount of time studying C style, libraries, pointers, and other features through online videos and resources. Having to teach the topic motivated me to learn more efficiently. 

  • Learning from Students 

In class, I try to maintain a passionate tone and ask the right questions as I lecture. Students often reciprocate with active participation and sometimes their responses or follow-up questions help me learn something that I wasn’t aware of before! It’s perfectly normal to not know something even as a teacher. When I don’t have a confident answer to a question, I preface that I am not completely sure, give students my preliminary thoughts, and try to find out the answer through a quick Stack Overflow search together. This way I not only make sure that I don’t spread misinformation, but both my students and I also walk away learning something new. Inside and outside of class, hearing different takes on a concept and seeing diverse code implementations to solve a problem exposed me to a wide range of perspectives, enhancing my own understanding. Every time I teach, I gain a newfound appreciation for the subject through my students. 

I am a lifelong learner passionate about education and technology. Throughout the process teaching programming, I was able to help others gain technology literacy, improve my own knowledge, and create long lasting relationships!

Algorithms in Real Life: Space v.s. Time 

In Computer Science, we are often challenged to “do better” ― even though our program works, we are asked to make it run faster or use up less space. Since there are often different ways to implement a solution to the same problem, different algorithms that achieve the same goal can yield wide-ranging time and space complexities. Why might the running time of an algorithm matter? Simply put, an algorithm is ultimately useless if it takes forever to run ― we never get our desired output. Why might the space that an algorithm takes up matter? Space can be expensive sometimes. However, as memory is becoming cheaper and cheaper with advancements in technology, programmers are usually willing to trade more space for a faster runtime. Let me illustrate the tradeoffs between time and space complexity by analyzing two different ways of solving the Fibonacci problem. 

 

The Fibonacci Problem:

The Fibonacci Sequence is a famous series of numbers shown below:

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584

As you can see, the first number is 0, the second number is 1, and the next numbers are the sum of the two numbers before it:

  • The 2 is found by adding the two numbers before it (1+1),
  • The 3 is found by adding 1+2,
  • The 5 is found by adding 2+3,
  • And so on… 

When we try to picture the Fibonacci Sequence using squares, we see a nice spiral! 5 and 8 make 13, 8 and 13 make 21, and so on… 

Turns out this spiral property of Fibonacci sequences are also seen prevalently in real life, especially in nature. 

We are now tasked to solve the Fibonacci problem: Given any positive integer n, find the nth number in the Fibonacci Sequence. We could, of course, calculate this by hand. If we want to find the 5th Fibonacci number, we could just start from the first two Fibonacci numbers and follow the definition until we get to the 5th term: 0, 1, 1 (0+1), 2 (1+1), 3 (1+2). However, this start-from-scratch method quickly becomes impractical as n gets large ― it would be a huge waste of time for us to manually calculate the 1000th Fibonacci number! Thus, we turn to a computer to help us solve this problem. 

Naive Recursive Solution: 

Using Python (or another programming language), we can implement a simple recursive program that computes the nth Fibonacci number.

For an input of 5 to our naive recursive solution, we can observe the pattern above. In a tree-like structure, fib5 (fib_rec(5)) is broken down into fib4 and fib3, fib4 is broken down into fib3 and fib2, fib3 is broken down into fib2 and fib1, and finally fib2 is broken down to our base cases: fib1 and fib0, which evaluate to 1 and 0 respectively. The recursive algorithm then uses our known base case values to compute all the other subproblems (e.g. fib2, fib3, etc…) in a bottom up approach. It is important to note that without our base cases specified, our algorithm would not work because there would be no stopping point. In this case, a stack overflow error would be thrown. 

Scanning the diagram from top to bottom, we notice that as we move downwards, each layer of fib items grows exponentially in size. Layer 0 has 1 (2^0) item,  layer 1 has 2 (2^1) items, layer 2 has 4 (2^2) items, layer 3 has the potential for 8 (2^3) items, layer 4 has the potential for 16 (2^4) items, and layer n has the potential for 2^n items. Since adding more inputs means adding more fib layers with items growing exponentially, the time complexity of our solution is O(2^n). This turns out to be very inefficient for even not-so-large input sizes like 50 or 100. For example, while fib_rec(40) takes a few seconds to run, fib_rec(80) takes theoretically years to run. While it is still feasible to solve small Fibonacci numbers with our naive recursive solution, we cannot solve large Fibonacci numbers using this approach. 

Dynamic Programming Solution:

Can we do better? 

When brainstorming for more optimal solutions, we can begin by analyzing the flaws of our less optimal solution. In the recursive tree diagram, we notice that there are a lot of overlapping computations. For instance, fib2 is repeatedly calculated 3 times and fib3 is repeatedly calculated 2 times. While this may not seem like a lot, the exponential increase of overlapping computations will add up when we have larger inputs, such as 50 or 100. What if we store the results of these repeated computations somewhere? Then, we can use them directly instead of recomputing when we need them. This “memoized” (memo = a note recording something for future use) solution is a common problem-solving strategy known as Dynamic Programming (DP). 

Before we define the function fib_dyn(n), we first create an array with null (empty) values that acts as a cache, or a structure that stores data so that future requests for that data can be served faster. Trading space for speed, we store the value of fib0 at index 0 of the cache array, fib1 at index 1, fib2 at index 2, etc… To compute a new fib value, we just need to look up the previous two entries in the cache array. With our base cases defined, we can simply use cache[0] and cache[1] to find cache[2], cache[1] and cache[2] to find cache[3], etc…, successfully avoiding the unnecessary overlapping computations. The time complexity for our DP solution is O(n) (linear) for traversing the cache array, which is significantly faster than our O(2^n) (exponential) recursive solution. The only downside of our DP solution is the added space complexity of O(n) for creating the cache array. This downside, however, is minuscule compared to the amazing benefit of a drastically reduced time complexity. In fact, I was able to run fib_dyn(1000) and get the accurate result almost instantly.

Conclusion:

Here are several key takeaways from this post:

  1. Programmers care about how fast a program runs and how much space it takes up because these attributes dramatically impact user experience. 
  2. When looking for a more optimal solution, examine the flaws of the less optimal solution and identify patterns. Find a way to get around the flaws (with perhaps a new data structure), and develop your improved solution from there. 
  3. Trading space for speed is encouraged in most situations. 

Algorithms in Real Life: What is an Algorithm?

They are everywhere. 

From social media apps and search engines to video streaming sites and dating apps, algorithms are everywhere in our modern, technology-driven world. 

But what exactly is an algorithm? 

How do they work? 

Who makes them? 

And should we be worried about them? 

Let’s figure out. 

Definition

With a quick Google search, we see that an algorithm is defined as “a process or set of rules to be followed in calculations or other problem-solving operations, especially by a computer.” This might sound like a riddle to you. There are so many definitions on the internet that do not clearly explain what algorithms actually do. I’m here to help!

A non-internet based example of an algorithm is a cookbook recipe. A recipe provides a set of instructions to specify how you should put together several ingredients to get a product (eg. sandwich) on the other end. In the peanut butter and jelly sandwich example above, we are given 2 slices of bread, 1 tablespoon of peanut butter, and 2 tablespoons of strawberry jam as inputs to our algorithm. The algorithm we use to make the sandwich might include the following procedures: 

  1. Gather Your Ingredients for the Sandwich
  2. Pull Out Two Slices of Bread
  3. Open Peanut Butter and Jelly
  4. Spread the Peanut Butter Onto One Slice of Bread
  5. Spread the Jelly Onto the Other Slice of Bread
  6. Combine the Two Slices

After completing the instructions specified by the algorithm, we are able to accurately produce our output  ― a delicious peanut butter and jelly sandwich! 

Algorithms in Computer Science

An algorithm in Computer Science does effectively the same thing ― it is a set of instructions that enables a computer program to put together different input sources and output a result. 

While a programmer implements code to specify the algorithm for recommending new YouTube videos or which post you see first on your Facebook feed, users are usually unaware of how an algorithm is designed. As a user, you can treat an algorithm as a “black box” – you provide valid input information and wait to see the magic happen. When we use Google Maps to find the fastest route to a destination, we don’t need to worry about what computations we need to make to locate the shortest path (thank god!). We simply provide the algorithm with relevant input information such as our starting point, destination, and mode of travel and the algorithm automatically generates the best path for us. This principle of removing unnecessary details (e.g. how the shortest path is computed) to boost user experience is a recurring concept in Computer Science known as “abstraction”. 

Correctness

How do we know if an algorithm is well-designed? We can start with analyzing its “correctness”. It is likely that you have experienced the frustrations of dealing with a poorly-designed algorithm yourself. When I was child, my parents purchased a new car GPS navigator. On a road trip, the GPS incorrectly led us to a rural farm and instructed us to drive into a field of crops. Luckily, we quickly disregarded the misguided directions and eventually found our way back on track. In this case, the GPS algorithm was “incorrect”. In other words, it failed to produce the correct output we were looking for ― a feasible path to our final destination. In certain high-stakes situations, incorrect algorithms can even yield life-or-death consequences. On March 19, 2018, an Uber self-driving car running in autonomous mode hit and killed a woman walking on a poorly lit road. According to Uber, the algorithm incorrectly classified the women as “unknown object”, then “vehicle”, and then “bicycle” during the brief encounter. It is this indecisiveness that led to a very late action (stopping the car), which eventually caused the tragedy. Algorithms are useful when they are correct, helping us automate tedious tasks, find valuable information, and perform complex procedures. On the other hand, incorrect algorithms do more harm than good, from wasting time to even taking lives. 

Efficiency

We can also analyze an algorithm by evaluating its “efficiency”. Going back to the Google Map example: What if a search for the fastest route to a nearby destination takes an hour? If driving to the destination only takes 15 minutes on average, there is no point in making the search before traveling there. Even if we choose a slower route from our memory, we would still arrive at the destination faster than waiting for the app recommendation result and taking the fastest route. This shows how even if an algorithm is correct, it is ultimately unhelpful if it takes too much time to run. 

Conclusion

Algorithms are not as difficult to understand as many people think ― they show up in many facets of our daily lives and help us better utilize the resources around us. 

Data Structures in Real Life: Stacks

Stacks

Have you ever used a text editing document (e.g. Microsoft Word, Google Docs) to write an essay? In this situation, it is useful to be able to keep track of the last several commands you performed. That way you can simply back out of a mistake (or mistakes) and “undo” them when applicable. 

How do we keep track of the last several commands performed though? 

We can use a stack! 

Introduction (Beginner):

A stack is a data structure used to store a bunch of data, and then access the data in the opposite order that the data was stored. This process is commonly described as “last in, first out”, or LIFO. 

You can think of a stack as a pile of books placed in vertical order. When we’re piling the books up, we first place (push) book 1, then book 2 on top of book 1, and finally book 3 on top of book 2. Now in order to retrieve book 1, we need to first remove (pop) book 3, then remove book 2. This book analogy illustrates the “last in, first out” nature of the stack data structure – the last book that is placed (pushed) on the pile is the first book to be removed (popped).

Examples (Intermediate):

Now let’s see how a stack can help us undo mistakes in a document-editing scenario. 

Imagine you are an elementary English teacher planning a vocabulary lesson for your first grade students. You start off by writing “cars”, “dogs”, “cats”, then “ICT” on a word document. You suddenly realize that “ICT”, short for Information and Communication Technology, probably shouldn’t be on the vocabulary list for a first-grader. Luckily, you hit the undo button on Google Docs, and “ICT” disappears. This process can be modeled by a stack. As you record the terms, “cars”, “dogs”, “cats”, and “ICT” are pushed onto the stack one by one. When you hit the undo button, you are essentially popping off the most recent word you added to the document (“ICT”) from the stack. 

There are so many more real-life examples of stacks, such as car decks, cafeteria trays, and breakfast pancakes.

The “back button” on a web browser is also an excellent example of stack implementation. When a user visits a new web page, the current page is pushed onto the stack. When the user clicks the “back button”, the last page pushed onto the stack is popped off and loaded into the browser window. When all the pages are popped off of the stack, the back button will gray out, signaling that the stack is empty. The “forward button” works in a similar but slightly different way. While clicking the “back button” pushes the current page onto the stack, clicking the “forward button” pops the top page off the stack. When you visit a new page without clicking either button, the forward button stack is automatically emptied. In class, I usually run through a web demonstration of clicking a series of back buttons and forward buttons to better illustrate how stacks work. 

Memory (Advanced):

If you have some experience in programming, you’ve probably used “Stack Overflow”, a Q&A website for software development. But what exactly does the term “stack overflow” mean in Computer Science? The “stack” here refers to the memory stack, which stores data in a computer’s memory. In computer programs, the memory stack stores local variables contained by a function. Since it is fixed in size, the memory stack is designed to be temporary – when computational tasks are complete, the memory of the variables are automatically erased. 

A stack overflow is a run-time software bug when a program attempts to use more space than is available on the memory stack, which results in a program crash. The most typical case of a stack overflow error happens when your code executes an infinite or very deep recursion (a function invoking itself). Whenever a function is invoked, some part of the stack memory is consumed. Since the memory stack is fixed in size, once all the memory is exhausted with consecutive recursive function calls, a stack overflow error will be thrown. Note that a stack overflow error should be differentiated from a run-time error, which is typically caused by an infinite loop. 

Conclusion: 

From organizing books and undoing document changes to navigating with web browsers and utilizing computer memory, stacks are more relevant to our daily lives than you think! 

Data Structures in Real Life: Arrays

For those who don’t have a Computer Science background, computer programs might seem like far-fetched wizardry. When I took my first Duke CS course, Data Structures & Algorithms, I also had a difficult time wrapping my head around the numerous ideas introduced throughout the course. At the time, data structures (e.g. linked lists, binary trees, stacks, queues) seemed abstract and confusing to me. Looking back, data structures (and CS in general) are so much more relatable to our daily lives than I used to think! As a programming instructor this summer, I want to make sure that my students can realize this sooner. Given this goal, I began to think: How should I teach data structures? 

“How do we access an element in a sorted array?” 

A few hands go up hesitantly…

“How do we look up a page in a book?” 

Most students are willing to share!

When the vocabulary is technical, students are less likely to trust that they know the answer, even when they actually do. Putting technical ideas into familiar, real-life settings effectively reduces the fear associated with classroom discussion. Thus, when I’m introducing new data structures, I make sure to draw on plenty of analogies to simplify abstract concepts and encourage further discussion. Since students are more likely to personally connect with these everyday examples, I find that they have a much easier time comprehending and implementing data structures in code later on.

In the following weeks, I will be introducing several important data structures with the examples I frequently use in class. Let’s start with arrays!  

Arrays

Introduction (Beginner):

Arrays are the simplest and most widely used data structure. Other data structures, such as stacks and queues (which I’ll explain later) are derived from arrays. Because arrays are so prevalently used in programming, they often are amongst the first data structures students learn in an introductory CS course. 

Below is a simple array of size 4, containing elements 1, 2, 3, and 4.

You can think of an array as a row of boxes with each box capable of storing a single element. The type of element that is stored in each box within the array needs to be the same. In other words, if the one box stores a number (i.e. int, double), the other boxes cannot store words (i.e. Strings) because all the boxes within the array need to be consistent in type.  

Each element is assigned a positive numerical value called an index, which corresponds to the position of that particular element in the array. Note that array indices typically start at a value of 0 (starting from 0 instead of 1 is a recurring theme in CS). 

There are two major types of arrays: 

  • One Dimensional Arrays (as illustrated previously)
  • Multi-Dimensional Arrays (arrays within arrays, or nested arrays) 

You can picture a two-dimensional array as a table made of rows and columns, such as a spreadsheet. Since two-dimensional arrays can be a little confusing for beginners, I like using an empty egg carton to demonstrate how a two-dimensional array is traversed. 

 

In a two-dimensional array, each row represents a distinct array of elements. As pictured above, the carton (array) holds two rows of eggs (elements), so it contains two distinct arrays, specifically {0, 1, 2, 3, 4, 5} and {6, 7, 8, 9, 10, 11}. In class, I have students place pieces of candies into different spaces of an egg carton to simulate a piece of array-accessing code step by step. And yes, they get to eat the candies afterwards!

 

Memory (Intermediate):

To understand how arrays work on a low level in memory, you can think of an array as a group of apartments (elements) side by side in an apartment complex (the array). Each apartment has an associated address number (memory address) at its front door. For instance, if the first apartment has an address number of 1000, then the second apartment will have an address number of 1001, and so on. Following this logic, we know that the fifth apartment will have an address number of 1004 and the tenth apartment will have an address number of 1009. A similar phenomenon happens in arrays. Since the elements of an array are stored in contiguous memory locations, if the memory address of the first element of an integer array is 1024, then the memory address of the fifth element will be 1024 + 4*4 ( the size of an integer element is 4 bytes) = 1040. This means that as long as we keep track of the memory location of the first element of the array, we can find any element in the array with a quick calculation (see below).     

Dynamic Array (Advanced):

Unlike static arrays (what we’ve covered so far), which are initialized with a fixed memory allocation (e.g. 10 integer elements; int[10]), dynamic arrays automatically grow when the programmer tries to make an insertion and there is no more space left for a new element. 

How does this iterative resizing process work? Whenever there is no more space to add a new element, a new array is allocated and each element from the original array is copied to the new array. To avoid incurring the cost of resizing many times, dynamic arrays resize by a large amount, such as doubling in size (as illustrated above), and use that space for future expansion. The old array, which is now useless since everything is copied to the new array already, is deleted (memory is freed). As you can see this is a relatively costly process, but resizing doesn’t happen every time you append an element to the original array. Resizing only occurs when the original array is full of elements and thus out of memory.  For those who understand Big-O Time Complexity, appending in dynamic arrays is O(n) instead of O(1) (appending in static arrays) because of its additional infrequent resizing process.

Interestingly, the resizing process in dynamic arrays can be compared to the process in which hermit crabs find new shells. As a hermit crab grows bigger, its shell becomes an ever-tighter fit so eventually the crabs need to move into a bigger one. As more elements are appended to a dynamic array, the array’s pre-allocated memory gets increasingly filled until the last space eventually gets filled, prompting it to resize. This fun analogy helps students better understand the logic behind resizing. 

Most modern programming languages have built-in dynamic arrays (e.g. ArrayLists in Java and lists in Python). However, to illustrate how a dynamic array is specifically implemented, here’s code for a dynamic array I wrote from scratch using Python:

Conclusion:

Whether you’re a beginner, intermediate, or advanced programmer, I hope you gained new perspectives on arrays after reading this post. Stay tuned for next week’s post on stacks! 

Computer Science Resources

Teaching and learning are interchangeable. In learning, I can acquire the necessary knowledge to help others gain valuable skills. In teaching, I can expose myself to various perspectives, learn from students, and develop the humility to accept that I am not all-knowing  I still have a lot of work to do. For example, as I’m teaching programming this summer, I enjoy learning from Computer Science instructors on YouTube, so I can strengthen my CS knowledge while integrating outstanding teaching styles into my own instruction. Here are some channels/videos I benefited the most from:

Theoretical

Theoretical Computer Science serves as the foundation for most of our technological innovations today. Understanding computer systems topics such as memory (eg. RAM, ROM) and operating systems (eg. Mac, Windows) helps us choose the most suitable personal computer. Understanding how the internet works (eg. TCP, IP) helps us take full advantage while avoiding the dangers of the World Wide Web. Since technology has become so integrated into our daily-lives, a strong grasp of CS theory can allow us to make optimal decisions and better navigate our increasingly complex, information-driven world. 

  • Map of Computer Science – Domain of Science

Image by Domain of Science via Flickr

Map of Computer Science is a YouTube video summarizing the field of Computer Science. With beautiful color schemes, engaging sketches, and concise explanations of CS subfields, this video helped me see the bigger picture of CS. At Duke, I took courses in the subfields of theoretical CS (eg. discrete math, algorithms), computer engineering (eg. electrical circuits, computer architecture), and CS applications (eg. data science, machine learning), but I didn’t quite view these subfields as interconnected as I view them now. This map helped me connect the dots and at the same time realize that there’s so much more to learn. The 10-minute video is definitely a worthwhile investment for anyone looking to gain a more holistic view of CS!  

  • Crash Course Computer Science 

Image via Crash Course

Crash Course is an educational YouTube channel founded by John Green and Hank Green. I’ve been watching Crash Course videos since high school and their video series helped me learn a wide range of subjects in a fun way! The Crash Course Computer Science series contains a theoretical overview of all important CS topics, from the history of computing and Central Processing Unit (CPU) design to computer networks and artificial intelligence. Like watching all other Crash Course series, watching the professionally-produced CS series is like watching an entertaining Netflix show! 

Practical

Building a fully functional product that solves a problem or automates a tedious task is one of the best feelings ever! Practical Computer Science mainly involves planning, writing, testing, and debugging computer programs. Coding is not only a result-oriented task. Since there are so many ways to implement a program, it is also an outlet for creativity. 

  • Data Structures & Algorithms – CS Dojo

Image via CS Dojo

Data structures and algorithms are the most commonly tested subjects in coding interviews. I find CS Dojo’s Data Structures and Algorithms series particularly helpful. CS Dojo is an ex-Google software engineer who has accumulated over 1.6 million subscribers on his CS education YouTube channel. I find his teaching style very effective as he simplifies difficult concepts and walks through code step by step, giving plenty of insight into his well-versed problem-solving mindset.

  • freeCodeCamp 

Image via freeCodeCamp

freeCodeCamp’s mission is to help people learn to code for free. Its YouTube channel contains detailed video series on various programming languages and tech stacks. I really like freeCodeCamp’s Harvard CS50 course, which is essentially a crash course on various modern programming languages that include C, HTML/CSS/JavaScript, Python, Flask, and SQL spread across a series of two-hour lectures. 

There are so many great online resources to learn how to code for free. I highly encourage everyone looking to learn CS to check out the YouTube channels/videos I recommended above!

Beyond Taiwan

Slogan via Beyond Taiwan Website

When I first clicked into Beyond Taiwan’s website, its slogan immediately caught my attention. 

Beyond Taiwan (BT) is a non-profit organization dedicated to providing public high school students in Taiwan the resources necessary for an education abroad. These students, unlike their International school counterparts who have direct access to AP courses and college counselors, have significantly less resources to navigate nontraditional college options outside of Taiwan. Started by a group of US college students, BT’s mission is to help under-resourced students achieve their goals of studying abroad through hosting events and programs that foster knowledge transfer and personalized mentoring.

A few months ago, I received a private message from an event organizer inviting me to speak (as a student representative of Duke) at BT’s Online College Fair Event on 06/11. I gladly accepted!

Promotional Post via Beyond Taiwan Instagram

A few weeks before the event, the organizers hosted a training session for guest speakers to get familiar with the virtual event application, Remo, that would be used on event day. As a CS student interested in EdTech, I found Remo’s concept and UI/UX particularly fascinating. Remo Conference is a Live Webinar and virtual networking platform integrated with chat tools that allow questions or voting in real time. At the online college fair, I would be virtually seated at one of the 29 main tables (each occupied by students/official representatives from different schools across the US, Canada, UK, etc…). Students interested in learning more about Duke or the general US college admissions process are encouraged to join the Duke table and I, along with my event partner, would give a short introduction about Duke and host a live Q&A session. Since each table can only seat 8 participants at once, the floor planning also includes waiting rooms and social lounges for participants to meet each other and give each other advice.

Remo Floor Plan via Beyond Taiwan Attendee Handbook

Fast forward to today. After 3 hours of nonstop presenting, answering questions, giving advice, and connecting with high school students, the event turned out to be a great success! Here’s a glimpse of my shameless promotion of Duke. 

Images via Duke Photo Archive

One memorable conversation I had with a participant was about Duke’s Education Program. When the participant identified her interest in teaching, I got super excited and immediately gave her an overview on how Duke supported me in exploring my interest in teaching. From volunteering at Durham Literacy Center as a math instructor to grabbing Duke-sponsored meals with professors in the Education Department to learn more about their research on classroom behavior, I showcased the wide-ranging and highly accessible possibilities at Duke through bits and pieces of my Duke experience. Another memorable event was meeting my past tutees at the event! Some of them saw BT’s promotional post and signed up for the event just to catch up with me. It is truly heartwarming to see that even though it has been two or three years since I last taught them, they still try to support me whenever possible. 

As I’m finishing up this post, I just received a message from an event organizer who happily shared that a participant contacted BT to thank me specifically! According to the event organizer, this student almost gave up on applying to US colleges but after I answered her questions enthusiastically and encouraged her to embrace the process, she feels much more comfortable and motivated. Also, her dream school is now Duke! This type of feedback is what pushes me forward to continue to gain knowledge, skills, and experience to make positive impacts on other people. This is why I am so passionate about education! 

Programming Methodology

I vividly recall my mother sending me to a coding class when I was in middle school. There, I was taught C, a rather low-level and intricate language, especially for beginners. I hated it. The teacher made us memorize templates but never explained why coding related to our daily lives or how we can solve a hands-on problem with programming. In addition, surrounded by classmates who have much more coding experience than me, I was constantly discouraged and eventually refused to take any Computer Science electives all throughout high school. I simply decided CS wasn’t for me. At Duke, I started out as a Biomedical Engineering major, took a CS class just because all my friends were taking it, and to my surprise, realized how fascinating it can be. By the end of sophomore year, I even became a CS major myself! Reflecting on my winding journey, I wish I had a passionate and supportive mentor who could introduce me to the world of programming using interesting examples and fun projects. Now that I have the necessary knowledge and experience to teach introductory-level CS courses, I wanted to give back to my community. That’s why I designed “Programming Methodology”, which I am currently teaching from June to August in Taiwan.

Course Promotional Flyer

In addition, the design of the course is also based on my experience tutoring several high school students in AP Computer Science (AP CS) this past year while in Taiwan. Throughout the tutoring sessions, I noticed that quite a few students were lacking significantly in coding fundamentals and had no idea about good software engineering practices despite having already taken the majority of a one-year AP course at school. When I casually conversed with them, they complained about how the AP CS course at their high schools “taught to the test” and focused heavily on the intricacies of Java syntax instead of teaching a problem solving mindset. When asked about school projects, I vividly recall one of my tutees saying how he had almost zero experience building anything “that actually works” since “the teacher just told us to write down code on paper to prepare for the paper-based AP exam”. He had no clue what an IDE (integrated development environment) was. At this point, I realized that CS education in Taiwan can be improved substantially to encourage more kids to develop an interest in programming and go into Computer Science careers. Traditional Asian education has a stereotype of focusing heavily on memorization instead of creativity. I had a vision to change how coding is taught in Taiwan and allow students to realize that they are capable of innovating.

I went to work. Meeting with my business partner (a Stanford graduate who is also passionate about CS education) every week, we discussed our visions and came up with course material together. Inspired by Stanford’s open-source CS106A course, we spent countless hours creating engaging lecture slides that included interesting analogies to help students digest more abstract programming concepts. From utilizing Karel the Robot’s tasks to teach basic control structures to introducing memory & scope through a tragic “Romeo & Juliet: Java Version story”, we wanted to make coding less daunting and more fun! We agreed that the goal of this course is not to teach students everything about the “Java language”. Instead, the goal is to teach students how to break down a complex problem and implement code using good software practices to boost not only performance, but also readability and reusability. I also envision this course to encourage students who have little to no programming experience and motivate them to further explore the endless possibilities of CS.

Here are some interesting assignments that students will be working on throughout the course!

 

Control a robot (Karel) to complete a set of tasks (such as producing a checkerboard pattern out of any rectangles)

 

Enter a graphics competition with classmates and create a mascot using Java’s Graphics Library

 

Build a customized arcade video game from scratch

I’m super excited about working with diverse students and putting them together in teams to complete innovative projects. Stay tuned for more updates about the course on this blog!