The function isPalindrome is a classic coding interview question. I’ve given this to candidates at various companies. Personally, I’m not a huge fan of the question for a couple reasons, 1) Some candidates can get tripped up by the word “palindrome”. If you haven’t heard of the concept before it can take a second to understand and when you’re under pressure you might get flustered. Two the question is a bit contrived and once you get to a working solution there’s not much conversation that can happen as a follow-up. With those caveats out of the way, let’s have a look at the function and how you might implement it in Go. I’ll give some insight into what I look for when a candidate completes this coding challenge.

Problem Statement Link to heading

The function isPalindrome takes a string as input and returns a boolean. The function should return true if the input string is a palindrome and false if it is not. A palindrome is a word or sentence that is spelled the same forwards and backwards. For example, the work “civic” is the same forwards as backwords. On the other hand, the word sandwich is not. "sandwhich" != "hcihwdnas"

Your job is to write this function below is the method signaure you should use:

func isPalindrome(s string) bool {
	// Your code here 
}

Solution #1 Link to heading

The following is a perfectly valid solution that I would expect from a developer that doesn’t have strong go experience.

package main

import ( “testing” )

// Below is the is one possible solution

// isPalindrome takes a string and returns true if that string is a palindrome and false if it is not a palindrome. func isPalindrome(s string) bool { for i := 0; i < len(s)/2; i++ { if s[i] != s[len(s)-i-1] { return false } } return true

}

// End solution

// Below is just the test harness for the function above

func TestMain(t *testing.T) {

testCases := map[string]struct {
	input    string
	expected bool
}{
	"true_all_lower_case": {
		input:    "civic",
		expected: true,
	},
	"accents": {
		input:    "été",
		expected: true,
	},
	"mixed_case": {
		input:    "Radar",
		expected: true,
	},
	"spaces:": {
		input:    "Step on no pets",
		expected: true,
	},
	"puncuation": {
		input:    "Eva, can I see bees in a cave?",
		expected: true,
	},
	"not_palidrome": {
		input:    "Hello, 世界",
		expected: false,
	},
	"simplified_chinese": {
		input:    "世界世",
		expected: true,
	},
	"arabic": {
		input:    "كُلٌّ فِي فَلَكٍ",
		expected: true,
	},
}
for _, tt := range testCases {
	actual := isPalindrome(tt.input)
	if tt.expected != actual {
		t.Errorf("Expected=%v Got=%v input=%s", tt.expected, actual, tt.input)
	}
}

}

If you run the above code, you can see that the solution doesn’t pass all test cases. So, it’s not perfect, but what insight do we gain from this solution? First and foremost, we know that this software engineer, at the very least, knows the basics. They can write a for loop, and it doesn’t have any indexing issues; they know how to index into a string. We can also tell that this developer is not super familiar with Go. If you index into a string in Go (unlike in some other languages), you don’t get a character, you get a “rune,” which is a single byte. Some characters are represented by a single rune (which is why some of the test cases passed), but not all characters. Next, some of the test cases failed because of whitespace, upper vs lower case, and punctuation. If you’re not super familiar with Go, you might not know which packages to reach for to strip out whitespace and punctuation, and compare strings in a case-insensitive manner.

A slightly better solution Link to heading

Here is a second solution that shows not only the developer can write code, but also that they have some good Go experience.

package main

import ( “regexp” “strings” “testing” )

// Begin Solution

var ( regexpNonAlphaChars = regexp.MustCompile("[[:^alpha:]]") )

func isPalindrome(s string) bool { // Replace all not alpha characters. s = regexpNonAlphaChars.ReplaceAllString(s, “”)

characters := strings.Split(s, "")
for i := 0; i < len(characters); i++ {

	// Compare characters using character folding, ie case-insensitive
	if !strings.EqualFold(characters[i], characters[len(characters)-i-1]) {
		return false
	}
}
return true

}

// End solution

// Begin test harness

func TestMain(t *testing.T) {

testCases := map[string]struct {
	input    string
	expected bool
}{
	"true_all_lower_case": {
		input:    "civic",
		expected: true,
	},
	"accents": {
		input:    "été",
		expected: true,
	},
	"mixed_case": {
		input:    "Radar",
		expected: true,
	},
	"spaces:": {
		input:    "Step on no pets",
		expected: true,
	},
	"puncuation": {
		input:    "Eva, can I see bees in a cave?",
		expected: true,
	},
	"not_palidrome": {
		input:    "Hello, 世界",
		expected: false,
	},
	"simplified_chinese": {
		input:    "世界世",
		expected: true,
	},
	"arabic": {
		input:    "كُلٌّ فِي فَلَكٍ",
		expected: true,
	},
}
for _, tt := range testCases {
	actual := isPalindrome(tt.input)
	if tt.expected != actual {
		t.Errorf("Expected=%v Got=%v input=%s", tt.expected, actual, tt.input)
	}
}

}

In this solution the developer demonstrates their understanding of the distinction between a rune and a character in Go. They also use the regexp and strings module from the standard library, which suggests some familiarity with their usage.

Conclusion Link to heading

While the second solution passes more of the test cases, the real difference between the two solutions is whether it was written by a developer or a Go developer. This may or may not be an important consideration depending on the profile of the candidate that you’re looking to hire.

Some follow up questions Link to heading

Once you get to a working solution sometimes it’s nice to dig a bit deeper to see the depth of the candidates ability. Here are some possible follow up questions:

  1. Where in your code are memory allocations made?
  2. How could you optimize this solution?