When conducting an interview that involves running code on an actual computer, I like to offer the candidate a problem that produces some sort of “fun output.” Seeing their program output the correct result provides a nice sense of completion and leaves the candidate feeling good about their work. Problems that require producing a plaintext from a ciphertext obviously have this property, but for a more general phone-screen-level question, one of my favorite exercises is to have the candidate draw shapes.

This exercise is extremely adaptable. It can be made short or long, math-light or math-heavy, and works in any programming language (I’ll give examples here in JavaScript and Python). The point of the problem is to have the candidate draw shapes, pixel by pixel, so if they want concepts like “line” or “arc,” they will have to build those themselves.

The first step is to define some way to draw pixels on a canvas. You can provide this as starter code or have the candidate write it. In JavaScript I built on top of the canvas API while in Python I defined a pixel array and printed it to stdout.

Here’s my Python code:

class Canvas:
  def __init__(self, width, height):
    self._width = width
    self._height = height
    self.clear()

  def clear(self):
    self._canvas = [[" "] * self._width for _ in range(self._height)]

  def drawPixel(self, x, y):
    self._canvas[y][x] = "O"

  def __repr__(self):
    return "\n".join("".join(row) for row in self._canvas)

And my JavaScript code:

/** Draw on an HTML5 canvas, pixel by pixel
    el: the canvas element to draw on
    width, height: the dimensions of the canvas, in physical pixels
    pxSize: the size of each pixel on our canvas (defaults to 3px)

    Note: a 300x300px canvas with pxSize=3 will have 100x100 "virtual pixels"
*/
class CanvasView {
  constructor (el, width, height, pxSize=3) {
    this._canvas = el;
    this._canvas.width = width;
    this._canvas.height = height;
    this._ctx = this._canvas.getContext('2d');
    this._ctx.fillStyle = 'black';
    this._pxSize = pxSize;
  }

  pxSize() {
    return this._pxSize;
  }

  drawPixel(x, y) {
    this._ctx.fillRect(x * this._pxSize, y * this._pxSize, this._pxSize, this._pxSize);
  }

  clear() {
    this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
  }
}

I’ll give the rest of these examples in JavaScript, but the same ideas apply in any language.

My initial assumption was that the easiest starting point for candidates would be drawing a line, but after going through the exercises I think starting off with a circle is the best way to go.

/** Draw a circle
    cv:     the CanvasView to draw on
    x, y:   the center of the circle
    r:      the radius of the circle (in virtual pixels)
*/
function drawCircle(cv, x, y, r) {
  let numPoints = 4 * Math.PI * r;
  for (let i = 0; i < numPoints; i++) {
    let angle = (i/numPoints) * (2*Math.PI);
    let xp = x + r * Math.cos(angle);
    let yp = y + r * Math.sin(angle);
    cv.drawPixel(xp, yp);
  }
}

There are a lot of interesting pieces already in this first function. The candidate has to figure out how many pixels to draw (hint: what is the circumference of the circle measured in pixels?) and they have to remember some basic trigonometry (or else the x^2 + y^2 == r^2 formula for a circle). I’ve made a few modifications to my function, like using twice as many points in order to get a more even line. It’s always nice to see the candidate take initiative to make their drawing “prettier,” and visual problems encourage that initiative

Once the candidate has a nice circle, a natural extension is to draw a spiral.

/** Draw a spiral
    cv:     the CanvasView to draw on
    x, y:   the center of the spiral
    r:      the radius of the outermost curl of the spiral (in virtual pixels)
    curls:  the number of turns the spiral should make
*/
function drawSpiral(cv, x, y, r, curls) {
  let numPoints = 4 * Math.PI * r * curls;
  for (let i = 0; i < numPoints; i++) {
    let angle = (i/numPoints) * (2*Math.PI) * curls;
    let xp = x + r * i/numPoints * Math.cos(angle);
    let yp = y + r * i/numPoints * Math.sin(angle);
    cv.drawPixel(xp, yp);
  }
}

Only a few changes are required here, but you can first ask the candidate to draw one loop of a spiral, avoiding the need for the curls variable, and then add support for multiple turns.

One relatively easy extension is to draw arbitrary functions. It’s important to emphasize that the functions should be defined in code like y = (x) => x * x, not as strings like "y=x^2", although that’s an interesting problem too! Even though this is the easiest part of the problem, I don’t ask it first because if I did, candidates might implement drawCircle() using two functions (+/- sqrt(r^2 - x^2)) and then get stuck writing drawSpiral().

/** Draw a function
    cv:      the CanvasView to draw on
    f:       the function to draw
    x0, xf:  the domain of the function to draw
*/
function drawFunction(cv, f, x0, xf) {
  for (let i = x0; i <= xf; i++) {
    cv.drawPixel(i, f(i));
  }
}

Finally, I’ll return to drawing straight lines. This can also be specified using drawFunction(), but I’ll write it out explicitly here. This function uses knowledge of the distance function and how to find the direction of a vector, but otherwise, if the candidate has made it this far they should be able to add more shapes without much trouble.

/** Draw a line
    cv:     the CanvasView to draw on
    x0, y0: the starting point of the line
    x1, y1: the ending point of the line
*/
function drawLine(cv, x0, y0, x1, y1) {
  let dist = Math.hypot(x1 - x0, y1 - y0);
  let resolution = cv.pxSize();
  let theta = Math.atan((y1 - y0)/(x1 - x0));
  for (let i = 0; i < resolution * dist; i++) {
    let xp = i/resolution * Math.cos(theta);
    let yp = i/resolution * Math.sin(theta);
    cv.drawPixel(xp, yp);
  }
}

There are lots of other fun possibilities at this point, like animating drawings over time. By drawing larger and larger concentric circles, for example, the candidate can implement the “dynamite eraser” effect from Kid Pix. For completeness, I’ll include the code that was used to generate the picture at the top of this page.

/* Remember, this canvas is 101x101 virtual pixels */
let canvas = new CanvasView(document.querySelector('canvas'), 303, 303);
drawCircle(canvas, 50, 50, 50);
drawSpiral(canvas, 50, 50, 50, 5);
let sq = (x) => 100-x*x/100;
drawFunction(canvas, sq, 0, 100);
let sine = (x) => 50 + 50*Math.sin(x/25*Math.PI);
drawFunction(canvas, sine, 0, 100);
drawLine(canvas, 0, 0, 101, 101);

You can play around with this yourself by opening the JavaScript console on this page and creating a new CanvasView. To draw a circle in the canvas at the top of this page and then clear it, you can run the following.

let cv = new CanvasView(document.querySelector('canvas'), 303, 303);
drawCircle(cv, 50, 50, 50);
cv.clear();