def checkPalindrome(self,lst): return lst[:] == lst[::-1] This is an extremely expensive implementation. `lst[:]` is needlessly creating a copy of `lst`, then `lst[::-1]` creates a *second* complete copy of `lst` (but reversed). There are many ways of approaching this, but I would go for something like this: def checkPalindrome(self, lst): return all(start == end for start, end in zip(lst, reversed(lst))) It [`zip`][1]s `lst` with a [`reversed`][2] list iterator: >>> lst = "abcd" >>> list(zip(lst, reversed(lst))) [('a', 'd'), ('b', 'c'), ('c', 'b'), ('d', 'a')] Then checks to see if [`all`][3] the pairs are equal. If they are, the list is a palindrome. This method is still inefficient though since only half of each needs to be checked. It can be further improved by introducing [`itertools.islice`][4] to get a "view" of the first half of the zipped lists: from itertools import islice def checkPalindrome(self, lst): half_pair_view = islice(zip(lst, reversed(lst)), len(lst) // 2 + 1) return all(start == end for start, end in half_pair_view) `islice` is like normal list slicing except instead of creating a copy, it just allows you to iterate over a limited portion of the original iterable. This code is more efficient because every function involved here is "lazy": they does only as much work as they need to. `reversed`, `zip`, `islice`, and the generator expression all return an iterator that can produce elements (but do little work up front). `all` also exits as soon as it gets a Falsey result, so it's comparable to a `for` loop that contains a `break` in some branches. This is key here because we only want to do as much work as is necessary to determine whether not they're palindromes. Making two full copies of the list does a large amount of work; far more than is required to check if the string is a palindrome. ----- `checkOddChars` is a textbook use-case for a list comprehension: def checkOddChars(self, lst): return [i for i in lst if lst.count(i) == 1] If you ever find yourself initializing an empty list, then iterating over another iterable and adding to the list, you likely want a comprehension. This is quite an expensive function too. `count` needs to iterate the entire list each time; once for every element in `lst`. This also double counts any repeated elements. I'm not sure off the top of my head what a better solution is though. ----- del lst[0] This is also *quite* expensive. Ideally, you shouldn't delete from a list except for at the very end. Lists don't support efficient deletes, and the closer to the beginning of the list you delete from, the worse it is. I'd switch to using a [`dequeue`][5] instead of a list, which avoids the overhead of popping from the beginning. [1]: https://docs.python.org/3/library/functions.html#zip [2]: https://docs.python.org/3/library/functions.html#reversed [3]: https://docs.python.org/3/library/functions.html#all [4]: https://docs.python.org/3/library/itertools.html#itertools.islice [5]: https://docs.python.org/3.8/library/collections.html#collections.deque