• Take a line of input (function arguments or stdin, etc.).
  • Remove comments in it, where a comment starts and ends with ".
  • Do not remove comments in string literals, which start and end with '. String literals do not contain escapes, because smalltalk has nice, easily lexable syntax ;).
  • Output this string.
  • The number of "s not inside strings and the number of 's not in comments in the input are both guaranteed to be even.
  • Because removing comments makes programs more golfy, this is : shortest code per language wins.


input => output

123 => 123
1"2"3 => 13
1""23 => 123
'12' => '12'
'1"2'3"" => '1"2'3
'1"2"3' => '1"2"3'
a"1'2'3"a => aa
"a"b"c" => b
'a'b'c' => 'a'b'c'
a'"'b'"'c => a'"'b'"'c
a"'"b"'"c => abc
Regex is boring as usual. Try something new, or answer in smalltalk!
  • \$\begingroup\$ sandboxed ;) \$\endgroup\$
    – Wezl
    Commented Jun 10, 2021 at 13:10
  • 1
    \$\begingroup\$ This would be a good use-case for graphical programming languages for statecharts! Unfortunately, graphical programming languages are not good for golfing. \$\endgroup\$ Commented Jun 10, 2021 at 13:28
  • 4
    \$\begingroup\$ @MartinRosenau Remember that this isn't a competition between languages. I'd still be interested in a solution using one of those languages, even if it isn't competitive with a dedicated golfing language. \$\endgroup\$
    – Wezl
    Commented Jun 10, 2021 at 13:36

13 Answers 13


Smalltalk, 325 bytes

[:k|k inject:OrderedCollection new into:[:a :b|(a isEmpty not and:[a last=0])ifTrue:[(b=$')ifTrue:[a removeLast. a add:b. a]ifFalse:[a removeLast. a add:b. a add:0. a]]ifFalse:[(a isEmpty not and:[a last=$"])ifTrue:[(b=$")ifTrue:[a removeLast. a]ifFalse:[a]]ifFalse:[(b=$')ifTrue:[a add:b. a add:0. a]ifFalse:[a add:b. a]]]]]

Some hot garbage smalltalk code.

It essentially uses a stack, pushing a 0 for a placeholder for when it sees '. When it sees ", it keeps in on the top of the stack until it sees another ".

I couldn't get an Eval function (smalltalk docs are terrible), nor could I get regex to work (smalltalk docs are terrible).

You can use GNU Smalltalk to run this, in theory, although I only tested it on the online compiler (linked above).

Here's a (more) readable version that takes from stdin and writes to stdout. As you can tell smalltalk is a beautiful language...

Transcript show:(stdin nextLine inject:
    OrderedCollection new into:
        [:a :b|
        Transcript show: a printString; cr.

        (a isEmpty not and:[a last = 0])ifTrue: [
            (b = $') ifTrue: [
                a removeLast.
                a add: b.
            ] ifFalse:[
                a removeLast.
                a add: b.
                a add: 0.
        ] ifFalse: [
            (a isEmpty not and:[a last = $"]) ifTrue: [
                (b = $") ifTrue: [
                    a removeLast.
                ] ifFalse: [a]
            ] ifFalse: [
                (b = $') ifTrue: [
                    a add: b.
                    a add: 0.
                ] ifFalse: [
                    a add: b.

Note: the regex solution almost works, using the same approach as Jakque, but it fails since smalltalk gives nil instead of an empty string:

Transcript show:(stdin nextLine replacingAllRegex: '".*?"|(''.*?''|.)' with: '%1')
  • \$\begingroup\$ By "the online compiler" you mean codingground? \$\endgroup\$
    – Wezl
    Commented Jun 10, 2021 at 16:12
  • \$\begingroup\$ yes I do mean that \$\endgroup\$
    – Riolku
    Commented Jun 10, 2021 at 16:17
  • \$\begingroup\$ I didn't know about character literals in smalltalk. Because of those, this challenge won't work on real smalltalk code :/ \$\endgroup\$
    – Wezl
    Commented Jun 10, 2021 at 16:35
  • \$\begingroup\$ Transcript show: 'a"''"b"''"c' "works", but single quotes get escaped by placing... another single quote. Edit: nvm it doesn't work since it's not parsed as code, duh. \$\endgroup\$
    – Riolku
    Commented Jun 10, 2021 at 16:45

APL (Dyalog Extended), 30 26 bytes (SBCS)

Anonymous prefix lambda.


Try it online!

{}dfn; argument is :

'''"'∘.=⍵ equality table for ' and " versus the argument

≠\@1⊢ running parity (lit. XOR scan) at the 1st row (lit XOR scan) indicating characters inside strings

<⌿ second row, i.e. comment delimiters, and not first row, i.e. inside string, i.e. active comment delimiters (lit. vertical less-than reduce)

≠\⍛⍱⍨ neither that (active comment delimiters) NOR its running parity (inside comments)

⍵/⍨ use that to filter the characters of the argument

  • \$\begingroup\$ it's nice to see that the regex solution and the cool and refreshing solution are the same length in APL \$\endgroup\$
    – Wezl
    Commented Jun 10, 2021 at 14:36
  • 1
    \$\begingroup\$ @Wzl Shorter, even. \$\endgroup\$
    – Adám
    Commented Jun 10, 2021 at 15:57

Jelly, 17 bytes


Try it online!

A link taking a Jelly string argument and returning a Jelly string without the comments.


⁾'"iⱮ              | Positions of each character in '" (so single quote -> 1, double quote -> 2, anything else -> 0)
         ɗ\        | Cumulative reduce (x,y)
     nao           | - (x != y) and (x or y)
              Ɗ    | Following as a monad:
           Ż       | - Prepend zero
            Ṗ      | - Remove last item from list
             o     | - Or
               Ị   | Less than or equal to 1
                x@ | Original input with 0 or 1 copies of each character as appropriate
  • \$\begingroup\$ sadly you missed the fgitw :/ \$\endgroup\$
    – Wezl
    Commented Jun 10, 2021 at 19:56
  • \$\begingroup\$ @Wzl what do you mean? \$\endgroup\$ Commented Jun 10, 2021 at 19:56
  • \$\begingroup\$ fastest gun in the west \$\endgroup\$
    – Razetime
    Commented Jun 11, 2021 at 3:42

Jelly, 48 bytes


Try it online!

This is definitely not the best approach, even considering Jelly is typically not great with strings.

iⱮ⁾'"o1:0¤>/  Helper Link
iⱮ⁾'"         Find the first ' and "; 0 if not found
     o        Logical OR with
      1:0¤    1 / 0 (infinity) - that way, "not found" is at the end, not the start
          >/  Is the first greater than the second?

ṣ”"2œPj€”"µẹ”'2ị‘œṖµÇ?;ß}¥/$¹f⁾'"$?  Main Link
                                  ?  If
                             f⁾'"$   There are any ' or "
                     ?     $         - If
                    Ç                - " was found first
ṣ”"                                    - Split on "
   2œP                                 - Split over and discard the second item
      j€”"                             - Join each of those with " (basically, this slices out
                                         the part between and including the first and second ",
                                         and leaves the part before and after as two sublists)
                                     - Otherwise
           ẹ”'                         - Find all indices of '
              2ị                       - Get the second of these
                ‘                      - And increment it
                 œṖ                    - And partition the list at that point (basically, this
                                         divides the list at the second ' without removing anything)
                         ¥/          - Then, reduce (apply to first and second as left and right) by
                      ;ß}            - Concatenating the left side with this function applied to the right link
                            ¹        Otherwise, if ' and " aren't found, just return the string itself
  • \$\begingroup\$ can we get an explanation of how it works? \$\endgroup\$ Commented Jun 11, 2021 at 18:19
  • 2
    \$\begingroup\$ @NooneAtAll I usually don't do much work on answers anymore after they've been beaten by so much, but here you go. \$\endgroup\$
    – hyper-neutrino
    Commented Jun 11, 2021 at 18:49

QuadR g, 18 12 bytes

‒6 thanks to Neil.


Try it online!

This is equivalent to the Dyalog APL function '''.*''' '".*"'⎕R'&' ''⍠'Greedy'0

The g flag turns off greedy patterns, essentially making * mean *?. Then the two patterns simply replace '.*' strings and ".*" comments with & themselves and  nothing, respectively.

  • 3
    \$\begingroup\$ okay now we've gotten regex out of the way :P \$\endgroup\$
    – Wezl
    Commented Jun 10, 2021 at 13:22
  • \$\begingroup\$ No non-greedy quantifiers? \$\endgroup\$
    – Neil
    Commented Jun 10, 2021 at 19:29
  • \$\begingroup\$ @Neil Wouldn't make a difference, as '[^']*' matches exactly all the chars between ' and the next '. The important thing is that the two patterns run in parallel, with, for each starting position, preference for the first one. \$\endgroup\$
    – Adám
    Commented Jun 10, 2021 at 19:57
  • \$\begingroup\$ Sure, but Retina lets me use '.*?' which is shorter. \$\endgroup\$
    – Neil
    Commented Jun 10, 2021 at 20:52
  • \$\begingroup\$ @Neil D'uh, of course. Thank you! \$\endgroup\$
    – Adám
    Commented Jun 10, 2021 at 20:56

QBasic, 118 113 112 bytes

FOR i=1TO LEN(s$)
c=c XOR(a=34)<q
q=q XOR(a=39)<c
IF c+k=0THEN?CHR$(a);


Looping over the ASCII code a of each character in a line of input s$, we track three boolean values:

  • c is true inside a comment, false otherwise
  • q is true inside quotes, false otherwise
  • k is the previous iteration's value of c (we need to track this so we can suppress both double quotes instead of just the first one)

The values are updated as follows:

  • If the current character is a double quote (a=34) and q is false, toggle c
  • If the current character is a single quote (a=39) and c is false, toggle q

If both c and k are false, we're not in a comment, so output the character.

(There's a fun trick in the update statements: In QBasic, truthy is -1, so instead of a=39AND c=0, we can get the same result from (a=39)<c. The inequality will be true only when a=39 is -1 and c is 0.)


C, 92 91 bytes


All my submissions so far have been in esolangs, so even though C might not be anything groundbreaking, it's at least new to me. :)

Try It Online!


c;s;                          // for current char and state
main() {
  while ((c=getchar())+1)     // while input is not EOF:
    s=s?            // if state is not 0 (0=default, 1=string literal, 2=comment)
      s-2?putchar(c):0,       // output the current char if state is not 2
      s*(c!=44-5*s) // set s to 0 if the relevant character is found
                    // (39 ['] if state = 1, 34 ["] if state = 2)
     :              // else (state = 0)
      c-34?         // if the current char is not ["]
        putchar(c)==39 // output current char, and if it is ['], set s to 1
       :      // else (current char is ["])
        2     // set s to 2
    ;}        // end expression, statement, and main function
  • 2
    \$\begingroup\$ Looks great! Would you mind adding a "readable" version as well? \$\endgroup\$
    – MechMK1
    Commented Jun 11, 2021 at 14:06
  • 1
    \$\begingroup\$ No problem, although I'm not sure how 'readable' I can make it. \$\endgroup\$
    – Soup Girl
    Commented Jun 12, 2021 at 16:21

Python 3.8 (pre-release), no regex, 75 65 bytes

for i in input():c=b*i;a^="'"==c;b^='"'==a*i;print(end=c*b)

Try it online!

Much cleaner solution than my previous one

how it works :

a and b store the state of the string:

  • a is equal to 0 if the char is inside a literal and 1 otherwise
  • b is equal to 0 if the char is inside a comment and 1 otherwise
  • c store the char multiplied by b. If the char is inside a comment (b is equal to 0), c is equal to the empty string
  • a^= use of the bitwise operator xor to swich the state of a if the current char is ' and the char is not inside a comment
  • same goes for b but with "
  • print(end=c*b) print the char if b is not set to 0. end= is for avoiding the trailing new line

Python 3.8 (pre-release), regex, 52 bytes

lambda s:re.sub("\".*?\"|('.*?')","\\1",s)
import re

Try it online!

Pretty basic: I replace comments by nothing and literals by themselves. Regex substitution are not overriding. Lazy operators do the rest

  • \$\begingroup\$ any particular reason for putting the import after the lambda? I guess it makes no difference; I've just never seen it before \$\endgroup\$
    – Quelklef
    Commented Jun 12, 2021 at 0:49
  • \$\begingroup\$ It's for the TIO link. I can add f=\ in the header to name my lambda function. But yeah, it doesn't make any difference \$\endgroup\$
    – Jakque
    Commented Jun 12, 2021 at 8:42

JavaScript (Node.js), 41 bytes


Try it online!

Trivial, thank EliteDaMyth for -3 bytes

JavaScript (Node.js), 88 86 bytes, No RegEx


Try it online!

If s.indexOf(s) happens to be non-negative, then s==c and f([c,c])==c+c


Retina 0.8.2, 16 bytes


Try it online! Link includes test cases. Explanation: Regex, obviously. Since by default matches can't overlap, the regex can match both strings and comments and won't get confused. It then remains to delete the comments without changing the strings.


TypeScript's type system, 142 bytes

type F<S>=S extends`'${infer Z}'${infer X}`?`'${Z}'${F<X>}`:S extends`"${any}"${infer X}`?F<X>:S extends`${infer C}${infer X}`?`${C}${F<X>}`:S

Try it at the TS playground!

This is a type F that takes and outputs a string type.


type F<S>=

This is the type signature for F. S is the input string type, but since this is a recursive type, S isn't necessarily the original string, but rather whatever part of the string we haven't processed yet.

S extends`'${infer Z}'${infer X}`?`'${Z}'${F<X>}`

Attempt to match S against: single quote + a string called Z + single quote + a string called X. If the match succeeded, return single quote + Z + single quote + recurse, with X as the argument.

This step keeps anything within single quotes.

:S extends`"${any}"${infer X}`?F<X>

If the match failed, attempt to match S against: double quote + a string + double quote + a string called X. If the match succeeded, recurse with X as the argument.

This step discards anything between double quotes.

:S extends`${infer C}${infer X}`?`${C}${F<X>}`

If that match failed, match S against: a character called C + a string called X. This basically just extracts the first character and the rest. If the match succeeded, return C + recurse with X as the argument.


If that match failed, S must be the empty string, so we just return it because the program has finished.

Note that this solution isn't tail recursive, so it hits the recursion limit very quickly for larger strings. Here's a tail recursive version which works up to 999 recursive calls:

TypeScript's type system, 166 bytes

type F<S,O="">=S extends`'${infer Z}'${infer X}`?F<X,`${O}'${Z}'`>:S extends`"${any}"${infer X}`?F<X,O>:S extends`${infer C}${infer X}`?F<X,`${O}${C}`>:O

Try it at the TS playground!


Charcoal, 28 bytes


Try it online! Link is to verbose version of code. Explanation:


Keep track of the current quote character, initially , but can be " or '.


Loop over the input.


If the current quote, or the current character if there is no current quote, is not ", then print the current character.


If the current character is a quote, then...


... turn the current quote on or off as appropriate.

  • If the current quote is empty, then we subtract nothing from the current character, so the current quote becomes the current character.
  • Otherwise, we subtract the current character from the current quote. This ends the current quote only once we find the matching character.

Haskell, 105 bytes

f('"':s)=g s
f(a@'\'':s)=a:h s
f(a:s)=a:f s
g('"':s)=f s
g(a:s)=g s
h(a@'\'':s)=a:f s
h(a:s)=a:h s

Try it online!


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.